import { useMemo, useState } from "react"; import { Boxes, CheckCircle2, Eye, Filter, ListOrdered, MoreHorizontal, Pencil, Plus, Search, Settings, Shield, Sparkles, Trash2, } from "lucide-react"; import { ActionIcon, Badge, Button, Card, Code, Group, Menu, Text, TextInput, ThemeIcon, } from "@mantine/core"; import { useDebouncedValue } from "@mantine/hooks"; import { KpiStrip, PageContainer, PageHeader } from "@/components/page"; import EditDropdownSettingDialog from "./EditDropdownSettingDialog"; import ManageDropdownOptionsDialog from "./ManageDropdownOptionsDialog"; import DeleteDropdownSettingDialog from "./DeleteDropdownSettingDialog"; import { useMutation, useQuery } from "@tanstack/react-query"; import { api } from "@/services/api"; import type { DropdownSetting } from "@/types/dropdownSettings"; import { DataTable, DataTableFooter, type ColumnDef, usePagination, } from "@edr/ui-common"; type ActiveDialog = "edit" | "options" | "delete"; export default function DropdownSettingsPage() { const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [query, setQuery] = useState(""); const [debouncedQuery] = useDebouncedValue(query, 300); const [createOpen, setCreateOpen] = useState(false); const [activeDialog, setActiveDialog] = useState(null); const [activeSetting, setActiveSetting] = useState( null, ); const openDialogFor = (dialog: ActiveDialog, setting: DropdownSetting) => { setActiveSetting(setting); setActiveDialog(dialog); }; const closeDialog = () => setActiveDialog(null); // Table data: server-side pagination + search via GET /dropdown-settings/paged. const listQuery = useMemo( () => ({ page: pagination.pageIndex + 1, pageSize: pagination.pageSize, search: debouncedQuery.trim() || undefined, }), [pagination.pageIndex, pagination.pageSize, debouncedQuery], ); const { data, isLoading, isError, error } = useQuery( api.dropdownSettings.listPaged.queryOptions({ input: { query: listQuery } }), ); // Full (unpaged) list feeds the KPI strip only — its aggregates span every // setting, not just the current page. const { data: allSettings, isLoading: kpiLoading } = useQuery( api.dropdownSettings.list.queryOptions(), ); const deleteMutation = useMutation(api.dropdownSettings.remove.mutationOptions()); const dropdownSettings = useMemo( () => (Array.isArray(allSettings) ? allSettings : []), [allSettings], ); const rows = data?.items ?? []; const total = data?.meta.total ?? 0; const pageCount = Math.max(1, data?.meta.totalPages ?? 1); const totalOptions = dropdownSettings.reduce( (sum, s) => sum + (s.children?.length ?? 0), 0, ); const multipleCount = dropdownSettings.filter((s) => s.multiple).length; const searchableCount = dropdownSettings.filter( (s) => s.meta?.searchable, ).length; const status: "loading" | "error" | "success" = isLoading ? "loading" : isError ? "error" : "success"; const columns: ColumnDef[] = [ { id: "setting", header: "Setting", cell: ({ row }) => { const s = row.original; return (
{s.label} {s.description ?? "No description"}
); }, }, { id: "code", header: "Code", cell: ({ row }) => {row.original.code}, }, { id: "options", header: "Options", cell: ({ row }) => ( {row.original.children?.length ?? 0} ), }, { id: "behavior", header: "Behavior", cell: ({ row }) => { const s = row.original; return ( {s.multiple ? "Multi" : "Single"} {s.meta?.searchable ? ( Searchable ) : null} {s.meta?.clearable ? ( Clearable ) : null} ); }, }, { id: "permissions", header: "Permissions", cell: ({ row }) => { const perms = row.original.meta?.permissions ?? []; if (perms.length === 0) { return ( ); } return ( {perms.map((p) => ( } > {p} ))} ); }, }, { id: "actions", size: 40, cell: ({ row }) => { const setting = row.original; return ( e.stopPropagation()}> }>View } onClick={() => openDialogFor("options", setting)} > Options } onClick={() => openDialogFor("edit", setting)} > Edit } color="red" onClick={() => openDialogFor("delete", setting)} > Delete ); }, }, ]; return ( } value={query} onChange={(e) => { setQuery(e.currentTarget.value); setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); }} placeholder="Search by code, label, description…" /> } />
Registered Dropdowns Every dynamic dropdown the platform reads from.
{/* Create — fully controlled, no shadcn trigger child. */} {/* Controlled row-action dialogs. */} {activeSetting ? ( <> (next ? null : closeDialog())} /> (next ? null : closeDialog())} /> deleteMutation.mutate({ id: activeSetting.id })} open={activeDialog === "delete"} onOpenChange={(next) => (next ? null : closeDialog())} /> ) : null}
); }