feat(frontend): consumir tipos dinâmicos
- remove listas fixas em Dashboard, Header e FilterBar - busca opções via API e reutiliza nos selects e métricas - fallback dos labels agora trata tipos desconhecidosmaster
parent
a43fc58ff7
commit
d4d65ad0f9
|
|
@ -19,6 +19,9 @@ interface HeaderProps {
|
||||||
userError: string | null;
|
userError: string | null;
|
||||||
onServerCreated?: () => Promise<void> | void;
|
onServerCreated?: () => Promise<void> | void;
|
||||||
onProfileUpdated?: (user: User) => void;
|
onProfileUpdated?: (user: User) => void;
|
||||||
|
serverTypeOptions?: ServersType[];
|
||||||
|
applicationOptions?: Applications[];
|
||||||
|
databaseOptions?: DatabaseType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultServerForm: ServerFormState = {
|
const defaultServerForm: ServerFormState = {
|
||||||
|
|
@ -39,20 +42,15 @@ const defaultProfileForm: ProfileFormState = {
|
||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const serverTypeOptions: ServersType[] = ["PRODUCTION", "HOMOLOGATION", "DATABASE"];
|
export const Header = ({
|
||||||
const applicationOptions: Applications[] = [
|
currentUser,
|
||||||
"ASTERISK",
|
userError,
|
||||||
"HITMANAGER",
|
onServerCreated,
|
||||||
"HITMANAGER_V2",
|
onProfileUpdated,
|
||||||
"OMNIHIT",
|
serverTypeOptions = [],
|
||||||
"HITPHONE",
|
applicationOptions = [],
|
||||||
"CDR",
|
databaseOptions = [],
|
||||||
"FUNCIONALIDADE",
|
}: HeaderProps) => {
|
||||||
"VOICEMAIL",
|
|
||||||
];
|
|
||||||
const databaseOptions: DatabaseType[] = ["MYSQL", "POSTGRESQL", "SQLSERVER", "ORACLE", "REDIS", "MONGODB", "MARIADB", "NONE"];
|
|
||||||
|
|
||||||
export const Header = ({ currentUser, userError, onServerCreated, onProfileUpdated }: HeaderProps) => {
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isMenuOpen, setMenuOpen] = useState(false);
|
const [isMenuOpen, setMenuOpen] = useState(false);
|
||||||
const [activeModal, setActiveModal] = useState<ModalType>(null);
|
const [activeModal, setActiveModal] = useState<ModalType>(null);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import api from "../Api";
|
import api from "../Api";
|
||||||
import { ServerTypeLabels, type ServersType } from "../types/enums";
|
import { ServerTypeLabels } from "../types/enums";
|
||||||
import { Code, Database, FlaskConical, Server } from "lucide-icons-react";
|
import { Code, Database, FlaskConical, Server } from "lucide-icons-react";
|
||||||
|
|
||||||
interface ServerCount {
|
interface ServerCount {
|
||||||
type: ServersType;
|
type: string;
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,9 +15,9 @@ export const ServerCardMetrics = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCounts = async () => {
|
const fetchCounts = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get<Record<ServersType, number>>("/api/servers/type");
|
const { data } = await api.get<Record<string, number>>("/api/servers/type");
|
||||||
const normalized = Object.entries(data).map(([type, total]) => ({
|
const normalized = Object.entries(data).map(([type, total]) => ({
|
||||||
type: type as ServersType,
|
type,
|
||||||
total,
|
total,
|
||||||
}));
|
}));
|
||||||
setCounts(normalized);
|
setCounts(normalized);
|
||||||
|
|
@ -38,7 +38,7 @@ export const ServerCardMetrics = () => {
|
||||||
return <div className={Styles.placeholder}>Carregando métricas...</div>;
|
return <div className={Styles.placeholder}>Carregando métricas...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCardIcon = (type: ServersType) => {
|
const handleCardIcon = (type: string) => {
|
||||||
const iconProps = { size: 32, strokeWidth: 1.5 };
|
const iconProps = { size: 32, strokeWidth: 1.5 };
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -64,7 +64,7 @@ export const ServerCardMetrics = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className={Styles.textGroup}>
|
<div className={Styles.textGroup}>
|
||||||
<p className={Styles.value}>{formatTotal(total)}</p>
|
<p className={Styles.value}>{formatTotal(total)}</p>
|
||||||
<p className={Styles.label}>{ServerTypeLabels[type]}</p>
|
<p className={Styles.label}>{ServerTypeLabels[type] ?? type}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,18 @@ interface Props {
|
||||||
onTypeChange: (value: ServersType | OptionAll) => void;
|
onTypeChange: (value: ServersType | OptionAll) => void;
|
||||||
onApplicationChange: (value: Applications | OptionAll) => void;
|
onApplicationChange: (value: Applications | OptionAll) => void;
|
||||||
onDbTypeChange: (value: DatabaseType | OptionAll) => void;
|
onDbTypeChange: (value: DatabaseType | OptionAll) => void;
|
||||||
|
serverTypeOptions?: ServersType[];
|
||||||
|
applicationOptions?: Applications[];
|
||||||
|
databaseOptions?: DatabaseType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeOptions: Array<ServersType | OptionAll> = ["ALL", "PRODUCTION", "HOMOLOGATION", "DATABASE"];
|
const withAllOption = <T extends string>(options?: T[]): Array<T | OptionAll> => {
|
||||||
const applicationOptions: Array<Applications | OptionAll> = [
|
if (!options || options.length === 0) {
|
||||||
"ALL",
|
return ["ALL"];
|
||||||
"ASTERISK",
|
}
|
||||||
"HITMANAGER",
|
const unique = Array.from(new Set(options));
|
||||||
"HITMANAGER_V2",
|
return ["ALL", ...unique];
|
||||||
"OMNIHIT",
|
};
|
||||||
"HITPHONE",
|
|
||||||
"CDR",
|
|
||||||
"FUNCIONALIDADE",
|
|
||||||
"VOICEMAIL",
|
|
||||||
];
|
|
||||||
const databaseOptions: Array<DatabaseType | OptionAll> = ["ALL", "MYSQL", "POSTGRESQL", "SQLSERVER", "ORACLE", "REDIS", "MONGODB", "MARIADB", "NONE"];
|
|
||||||
|
|
||||||
export const ServersFilterBar = ({
|
export const ServersFilterBar = ({
|
||||||
search,
|
search,
|
||||||
|
|
@ -37,7 +34,14 @@ export const ServersFilterBar = ({
|
||||||
onTypeChange,
|
onTypeChange,
|
||||||
onApplicationChange,
|
onApplicationChange,
|
||||||
onDbTypeChange,
|
onDbTypeChange,
|
||||||
|
serverTypeOptions,
|
||||||
|
applicationOptions,
|
||||||
|
databaseOptions,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const typeOptions = withAllOption(serverTypeOptions);
|
||||||
|
const applicationOptionsList = withAllOption(applicationOptions);
|
||||||
|
const databaseOptionsList = withAllOption(databaseOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Styles.wrapper}>
|
<div className={Styles.wrapper}>
|
||||||
<div className={Styles.searchGroup}>
|
<div className={Styles.searchGroup}>
|
||||||
|
|
@ -61,13 +65,13 @@ export const ServersFilterBar = ({
|
||||||
label="Aplicação"
|
label="Aplicação"
|
||||||
value={application}
|
value={application}
|
||||||
onChange={(event) => onApplicationChange(event.target.value as Applications | OptionAll)}
|
onChange={(event) => onApplicationChange(event.target.value as Applications | OptionAll)}
|
||||||
options={applicationOptions}
|
options={applicationOptionsList}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label="Banco"
|
label="Banco"
|
||||||
value={dbType}
|
value={dbType}
|
||||||
onChange={(event) => onDbTypeChange(event.target.value as DatabaseType | OptionAll)}
|
onChange={(event) => onDbTypeChange(event.target.value as DatabaseType | OptionAll)}
|
||||||
options={databaseOptions}
|
options={databaseOptionsList}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import type { Applications, DatabaseType, ServersType } from "../../types/enums";
|
|
||||||
|
|
||||||
export type ServerFormState = {
|
export type ServerFormState = {
|
||||||
name: string;
|
name: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
port: string;
|
port: string;
|
||||||
user: string;
|
user: string;
|
||||||
password: string;
|
password: string;
|
||||||
type: ServersType;
|
type: string;
|
||||||
application: Applications;
|
application: string;
|
||||||
dbType: DatabaseType;
|
dbType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProfileFormState = {
|
export type ProfileFormState = {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { ServersTable } from "../components/ServersTable";
|
||||||
import { ServersFilterBar } from "../components/ServersFilterBar";
|
import { ServersFilterBar } from "../components/ServersFilterBar";
|
||||||
import type { Server } from "../types/Server";
|
import type { Server } from "../types/Server";
|
||||||
import type { User } from "../types/User";
|
import type { User } from "../types/User";
|
||||||
import type { Applications, DatabaseType, ServersType } from "../types/enums";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export const Dashboard = () => {
|
export const Dashboard = () => {
|
||||||
|
|
@ -19,9 +18,12 @@ export const Dashboard = () => {
|
||||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||||
const [userError, setUserError] = useState<string | null>(null);
|
const [userError, setUserError] = useState<string | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [typeFilter, setTypeFilter] = useState<ServersType | "ALL">("ALL");
|
const [typeFilter, setTypeFilter] = useState<string>("ALL");
|
||||||
const [applicationFilter, setApplicationFilter] = useState<Applications | "ALL">("ALL");
|
const [applicationFilter, setApplicationFilter] = useState<string>("ALL");
|
||||||
const [dbFilter, setDbFilter] = useState<DatabaseType | "ALL">("ALL");
|
const [dbFilter, setDbFilter] = useState<string>("ALL");
|
||||||
|
const [serverTypeOptions, setServerTypeOptions] = useState<string[]>([]);
|
||||||
|
const [applicationOptions, setApplicationOptions] = useState<string[]>([]);
|
||||||
|
const [databaseOptions, setDatabaseOptions] = useState<string[]>([]);
|
||||||
|
|
||||||
const fetchServers = useCallback(async () => {
|
const fetchServers = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -31,13 +33,13 @@ export const Dashboard = () => {
|
||||||
if (trimmedQuery.length > 0) {
|
if (trimmedQuery.length > 0) {
|
||||||
params.set("query", trimmedQuery);
|
params.set("query", trimmedQuery);
|
||||||
}
|
}
|
||||||
if (typeFilter !== "ALL") {
|
if (typeFilter !== "ALL" && typeFilter.trim().length > 0) {
|
||||||
params.set("type", typeFilter);
|
params.set("type", typeFilter);
|
||||||
}
|
}
|
||||||
if (applicationFilter !== "ALL") {
|
if (applicationFilter !== "ALL" && applicationFilter.trim().length > 0) {
|
||||||
params.set("application", applicationFilter);
|
params.set("application", applicationFilter);
|
||||||
}
|
}
|
||||||
if (dbFilter !== "ALL") {
|
if (dbFilter !== "ALL" && dbFilter.trim().length > 0) {
|
||||||
params.set("dbType", dbFilter);
|
params.set("dbType", dbFilter);
|
||||||
}
|
}
|
||||||
const endpoint = params.toString() ? `/api/servers?${params.toString()}` : "/api/servers";
|
const endpoint = params.toString() ? `/api/servers?${params.toString()}` : "/api/servers";
|
||||||
|
|
@ -76,10 +78,33 @@ export const Dashboard = () => {
|
||||||
fetchCurrentUser();
|
fetchCurrentUser();
|
||||||
}, [fetchCurrentUser]);
|
}, [fetchCurrentUser]);
|
||||||
|
|
||||||
|
const fetchTypeOptions = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const [typesResponse, applicationsResponse, databasesResponse] = await Promise.all([
|
||||||
|
api.get<string[]>("/api/type-options/SERVER_TYPE"),
|
||||||
|
api.get<string[]>("/api/type-options/APPLICATION"),
|
||||||
|
api.get<string[]>("/api/type-options/DATABASE"),
|
||||||
|
]);
|
||||||
|
setServerTypeOptions(typesResponse.data ?? []);
|
||||||
|
setApplicationOptions(applicationsResponse.data ?? []);
|
||||||
|
setDatabaseOptions(databasesResponse.data ?? []);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = err?.response?.data?.message || "Falha ao carregar opções padrão.";
|
||||||
|
toast.error(message);
|
||||||
|
setServerTypeOptions([]);
|
||||||
|
setApplicationOptions([]);
|
||||||
|
setDatabaseOptions([]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchServers();
|
fetchServers();
|
||||||
}, [fetchServers]);
|
}, [fetchServers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchTypeOptions();
|
||||||
|
}, [fetchTypeOptions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout className="h-screen py-10">
|
<Layout className="h-screen py-10">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -88,6 +113,9 @@ export const Dashboard = () => {
|
||||||
userError={userError}
|
userError={userError}
|
||||||
onServerCreated={fetchServers}
|
onServerCreated={fetchServers}
|
||||||
onProfileUpdated={setCurrentUser}
|
onProfileUpdated={setCurrentUser}
|
||||||
|
serverTypeOptions={serverTypeOptions}
|
||||||
|
applicationOptions={applicationOptions}
|
||||||
|
databaseOptions={databaseOptions}
|
||||||
/>
|
/>
|
||||||
<ServerCardMetrics />
|
<ServerCardMetrics />
|
||||||
<ServersFilterBar
|
<ServersFilterBar
|
||||||
|
|
@ -99,6 +127,9 @@ export const Dashboard = () => {
|
||||||
onTypeChange={setTypeFilter}
|
onTypeChange={setTypeFilter}
|
||||||
onApplicationChange={setApplicationFilter}
|
onApplicationChange={setApplicationFilter}
|
||||||
onDbTypeChange={setDbFilter}
|
onDbTypeChange={setDbFilter}
|
||||||
|
serverTypeOptions={serverTypeOptions}
|
||||||
|
applicationOptions={applicationOptions}
|
||||||
|
databaseOptions={databaseOptions}
|
||||||
/>
|
/>
|
||||||
<ServersTable servers={servers} loading={loading} error={error} />
|
<ServersTable servers={servers} loading={loading} error={error} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,8 @@
|
||||||
export type DatabaseType =
|
export type ServersType = string;
|
||||||
| 'MYSQL'
|
export type Applications = string;
|
||||||
| 'POSTGRESQL'
|
export type DatabaseType = string;
|
||||||
| 'SQLSERVER'
|
|
||||||
| 'ORACLE'
|
|
||||||
| 'REDIS'
|
|
||||||
| 'MONGODB'
|
|
||||||
| 'MARIADB'
|
|
||||||
| 'NONE';
|
|
||||||
|
|
||||||
export type Applications =
|
export const ServerTypeLabels: Record<string, string> = {
|
||||||
| 'ASTERISK'
|
|
||||||
| 'HITMANAGER'
|
|
||||||
| 'HITMANAGER_V2'
|
|
||||||
| 'OMNIHIT'
|
|
||||||
| 'HITPHONE'
|
|
||||||
| 'CDR'
|
|
||||||
| 'FUNCIONALIDADE'
|
|
||||||
| 'VOICEMAIL';
|
|
||||||
|
|
||||||
export type ServersType = 'PRODUCTION' | 'HOMOLOGATION' | 'DATABASE';
|
|
||||||
|
|
||||||
export const ServerTypeLabels: Record<ServersType, string> = {
|
|
||||||
PRODUCTION: "Produção",
|
PRODUCTION: "Produção",
|
||||||
HOMOLOGATION: "Homologação",
|
HOMOLOGATION: "Homologação",
|
||||||
DATABASE: "Banco de Dados",
|
DATABASE: "Banco de Dados",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue