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;
|
||||
onServerCreated?: () => Promise<void> | void;
|
||||
onProfileUpdated?: (user: User) => void;
|
||||
serverTypeOptions?: ServersType[];
|
||||
applicationOptions?: Applications[];
|
||||
databaseOptions?: DatabaseType[];
|
||||
}
|
||||
|
||||
const defaultServerForm: ServerFormState = {
|
||||
|
|
@ -39,20 +42,15 @@ const defaultProfileForm: ProfileFormState = {
|
|||
password: "",
|
||||
};
|
||||
|
||||
const serverTypeOptions: ServersType[] = ["PRODUCTION", "HOMOLOGATION", "DATABASE"];
|
||||
const applicationOptions: Applications[] = [
|
||||
"ASTERISK",
|
||||
"HITMANAGER",
|
||||
"HITMANAGER_V2",
|
||||
"OMNIHIT",
|
||||
"HITPHONE",
|
||||
"CDR",
|
||||
"FUNCIONALIDADE",
|
||||
"VOICEMAIL",
|
||||
];
|
||||
const databaseOptions: DatabaseType[] = ["MYSQL", "POSTGRESQL", "SQLSERVER", "ORACLE", "REDIS", "MONGODB", "MARIADB", "NONE"];
|
||||
|
||||
export const Header = ({ currentUser, userError, onServerCreated, onProfileUpdated }: HeaderProps) => {
|
||||
export const Header = ({
|
||||
currentUser,
|
||||
userError,
|
||||
onServerCreated,
|
||||
onProfileUpdated,
|
||||
serverTypeOptions = [],
|
||||
applicationOptions = [],
|
||||
databaseOptions = [],
|
||||
}: HeaderProps) => {
|
||||
const navigate = useNavigate();
|
||||
const [isMenuOpen, setMenuOpen] = useState(false);
|
||||
const [activeModal, setActiveModal] = useState<ModalType>(null);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useState } from "react";
|
||||
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";
|
||||
|
||||
interface ServerCount {
|
||||
type: ServersType;
|
||||
type: string;
|
||||
total: number;
|
||||
}
|
||||
|
||||
|
|
@ -15,9 +15,9 @@ export const ServerCardMetrics = () => {
|
|||
useEffect(() => {
|
||||
const fetchCounts = async () => {
|
||||
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]) => ({
|
||||
type: type as ServersType,
|
||||
type,
|
||||
total,
|
||||
}));
|
||||
setCounts(normalized);
|
||||
|
|
@ -38,7 +38,7 @@ export const ServerCardMetrics = () => {
|
|||
return <div className={Styles.placeholder}>Carregando métricas...</div>;
|
||||
}
|
||||
|
||||
const handleCardIcon = (type: ServersType) => {
|
||||
const handleCardIcon = (type: string) => {
|
||||
const iconProps = { size: 32, strokeWidth: 1.5 };
|
||||
|
||||
switch (type) {
|
||||
|
|
@ -64,7 +64,7 @@ export const ServerCardMetrics = () => {
|
|||
</div>
|
||||
<div className={Styles.textGroup}>
|
||||
<p className={Styles.value}>{formatTotal(total)}</p>
|
||||
<p className={Styles.label}>{ServerTypeLabels[type]}</p>
|
||||
<p className={Styles.label}>{ServerTypeLabels[type] ?? type}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -12,21 +12,18 @@ interface Props {
|
|||
onTypeChange: (value: ServersType | OptionAll) => void;
|
||||
onApplicationChange: (value: Applications | OptionAll) => void;
|
||||
onDbTypeChange: (value: DatabaseType | OptionAll) => void;
|
||||
serverTypeOptions?: ServersType[];
|
||||
applicationOptions?: Applications[];
|
||||
databaseOptions?: DatabaseType[];
|
||||
}
|
||||
|
||||
const typeOptions: Array<ServersType | OptionAll> = ["ALL", "PRODUCTION", "HOMOLOGATION", "DATABASE"];
|
||||
const applicationOptions: Array<Applications | OptionAll> = [
|
||||
"ALL",
|
||||
"ASTERISK",
|
||||
"HITMANAGER",
|
||||
"HITMANAGER_V2",
|
||||
"OMNIHIT",
|
||||
"HITPHONE",
|
||||
"CDR",
|
||||
"FUNCIONALIDADE",
|
||||
"VOICEMAIL",
|
||||
];
|
||||
const databaseOptions: Array<DatabaseType | OptionAll> = ["ALL", "MYSQL", "POSTGRESQL", "SQLSERVER", "ORACLE", "REDIS", "MONGODB", "MARIADB", "NONE"];
|
||||
const withAllOption = <T extends string>(options?: T[]): Array<T | OptionAll> => {
|
||||
if (!options || options.length === 0) {
|
||||
return ["ALL"];
|
||||
}
|
||||
const unique = Array.from(new Set(options));
|
||||
return ["ALL", ...unique];
|
||||
};
|
||||
|
||||
export const ServersFilterBar = ({
|
||||
search,
|
||||
|
|
@ -37,7 +34,14 @@ export const ServersFilterBar = ({
|
|||
onTypeChange,
|
||||
onApplicationChange,
|
||||
onDbTypeChange,
|
||||
serverTypeOptions,
|
||||
applicationOptions,
|
||||
databaseOptions,
|
||||
}: Props) => {
|
||||
const typeOptions = withAllOption(serverTypeOptions);
|
||||
const applicationOptionsList = withAllOption(applicationOptions);
|
||||
const databaseOptionsList = withAllOption(databaseOptions);
|
||||
|
||||
return (
|
||||
<div className={Styles.wrapper}>
|
||||
<div className={Styles.searchGroup}>
|
||||
|
|
@ -61,13 +65,13 @@ export const ServersFilterBar = ({
|
|||
label="Aplicação"
|
||||
value={application}
|
||||
onChange={(event) => onApplicationChange(event.target.value as Applications | OptionAll)}
|
||||
options={applicationOptions}
|
||||
options={applicationOptionsList}
|
||||
/>
|
||||
<Select
|
||||
label="Banco"
|
||||
value={dbType}
|
||||
onChange={(event) => onDbTypeChange(event.target.value as DatabaseType | OptionAll)}
|
||||
options={databaseOptions}
|
||||
options={databaseOptionsList}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import type { Applications, DatabaseType, ServersType } from "../../types/enums";
|
||||
|
||||
export type ServerFormState = {
|
||||
name: string;
|
||||
ip: string;
|
||||
port: string;
|
||||
user: string;
|
||||
password: string;
|
||||
type: ServersType;
|
||||
application: Applications;
|
||||
dbType: DatabaseType;
|
||||
type: string;
|
||||
application: string;
|
||||
dbType: string;
|
||||
};
|
||||
|
||||
export type ProfileFormState = {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { ServersTable } from "../components/ServersTable";
|
|||
import { ServersFilterBar } from "../components/ServersFilterBar";
|
||||
import type { Server } from "../types/Server";
|
||||
import type { User } from "../types/User";
|
||||
import type { Applications, DatabaseType, ServersType } from "../types/enums";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export const Dashboard = () => {
|
||||
|
|
@ -19,9 +18,12 @@ export const Dashboard = () => {
|
|||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [userError, setUserError] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState<ServersType | "ALL">("ALL");
|
||||
const [applicationFilter, setApplicationFilter] = useState<Applications | "ALL">("ALL");
|
||||
const [dbFilter, setDbFilter] = useState<DatabaseType | "ALL">("ALL");
|
||||
const [typeFilter, setTypeFilter] = useState<string>("ALL");
|
||||
const [applicationFilter, setApplicationFilter] = useState<string>("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 () => {
|
||||
setLoading(true);
|
||||
|
|
@ -31,13 +33,13 @@ export const Dashboard = () => {
|
|||
if (trimmedQuery.length > 0) {
|
||||
params.set("query", trimmedQuery);
|
||||
}
|
||||
if (typeFilter !== "ALL") {
|
||||
if (typeFilter !== "ALL" && typeFilter.trim().length > 0) {
|
||||
params.set("type", typeFilter);
|
||||
}
|
||||
if (applicationFilter !== "ALL") {
|
||||
if (applicationFilter !== "ALL" && applicationFilter.trim().length > 0) {
|
||||
params.set("application", applicationFilter);
|
||||
}
|
||||
if (dbFilter !== "ALL") {
|
||||
if (dbFilter !== "ALL" && dbFilter.trim().length > 0) {
|
||||
params.set("dbType", dbFilter);
|
||||
}
|
||||
const endpoint = params.toString() ? `/api/servers?${params.toString()}` : "/api/servers";
|
||||
|
|
@ -76,10 +78,33 @@ export const Dashboard = () => {
|
|||
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(() => {
|
||||
fetchServers();
|
||||
}, [fetchServers]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTypeOptions();
|
||||
}, [fetchTypeOptions]);
|
||||
|
||||
return (
|
||||
<Layout className="h-screen py-10">
|
||||
<div className="space-y-6">
|
||||
|
|
@ -88,6 +113,9 @@ export const Dashboard = () => {
|
|||
userError={userError}
|
||||
onServerCreated={fetchServers}
|
||||
onProfileUpdated={setCurrentUser}
|
||||
serverTypeOptions={serverTypeOptions}
|
||||
applicationOptions={applicationOptions}
|
||||
databaseOptions={databaseOptions}
|
||||
/>
|
||||
<ServerCardMetrics />
|
||||
<ServersFilterBar
|
||||
|
|
@ -99,6 +127,9 @@ export const Dashboard = () => {
|
|||
onTypeChange={setTypeFilter}
|
||||
onApplicationChange={setApplicationFilter}
|
||||
onDbTypeChange={setDbFilter}
|
||||
serverTypeOptions={serverTypeOptions}
|
||||
applicationOptions={applicationOptions}
|
||||
databaseOptions={databaseOptions}
|
||||
/>
|
||||
<ServersTable servers={servers} loading={loading} error={error} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,9 @@
|
|||
export type DatabaseType =
|
||||
| 'MYSQL'
|
||||
| 'POSTGRESQL'
|
||||
| 'SQLSERVER'
|
||||
| 'ORACLE'
|
||||
| 'REDIS'
|
||||
| 'MONGODB'
|
||||
| 'MARIADB'
|
||||
| 'NONE';
|
||||
export type ServersType = string;
|
||||
export type Applications = string;
|
||||
export type DatabaseType = string;
|
||||
|
||||
export type Applications =
|
||||
| 'ASTERISK'
|
||||
| 'HITMANAGER'
|
||||
| 'HITMANAGER_V2'
|
||||
| 'OMNIHIT'
|
||||
| 'HITPHONE'
|
||||
| 'CDR'
|
||||
| 'FUNCIONALIDADE'
|
||||
| 'VOICEMAIL';
|
||||
|
||||
export type ServersType = 'PRODUCTION' | 'HOMOLOGATION' | 'DATABASE';
|
||||
|
||||
export const ServerTypeLabels: Record<ServersType, string> = {
|
||||
PRODUCTION: "Produção",
|
||||
HOMOLOGATION: "Homologação",
|
||||
DATABASE: "Banco de Dados",
|
||||
export const ServerTypeLabels: Record<string, string> = {
|
||||
PRODUCTION: "Produção",
|
||||
HOMOLOGATION: "Homologação",
|
||||
DATABASE: "Banco de Dados",
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue