mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 23:35:42 +00:00
355 lines
10 KiB
TypeScript
355 lines
10 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import {
|
|
Boxes,
|
|
CheckCircle2,
|
|
Filter,
|
|
ListOrdered,
|
|
Pencil,
|
|
Plus,
|
|
Search,
|
|
Settings,
|
|
Shield,
|
|
Sparkles,
|
|
Trash2,
|
|
} from "lucide-react";
|
|
|
|
import Breadcrumbs from "@/components/Breadcrumbs";
|
|
import EditDropdownSettingDialog from "./EditDropdownSettingDialog";
|
|
import ManageDropdownOptionsDialog from "./ManageDropdownOptionsDialog";
|
|
import DeleteDropdownSettingDialog from "./DeleteDropdownSettingDialog";
|
|
import { dropdownSettings } from "./dropdownSettings.mock";
|
|
import {
|
|
DataTable,
|
|
DataTableFooter,
|
|
type ColumnDef,
|
|
usePagination,
|
|
Button,
|
|
Card,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
CardContent,
|
|
Input,
|
|
} from "@edr/ui-common";
|
|
|
|
export default function DropdownSettingsPage() {
|
|
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
|
const [query, setQuery] = useState("");
|
|
|
|
const filtered = useMemo(() => {
|
|
const q = query.trim().toLowerCase();
|
|
if (!q) return dropdownSettings;
|
|
return dropdownSettings.filter(
|
|
(s) =>
|
|
s.code.toLowerCase().includes(q) ||
|
|
s.label.toLowerCase().includes(q) ||
|
|
(s.description ?? "").toLowerCase().includes(q),
|
|
);
|
|
}, [query]);
|
|
|
|
const total = filtered.length;
|
|
const pageCount = Math.ceil(total / pagination.pageSize);
|
|
const start = pagination.pageIndex * pagination.pageSize;
|
|
const end = Math.min(start + pagination.pageSize, total);
|
|
|
|
const paginatedData = useMemo(
|
|
() => filtered.slice(start, end),
|
|
[start, end, filtered],
|
|
);
|
|
|
|
const totalOptions = dropdownSettings.reduce(
|
|
(sum, s) => sum + s.children.length,
|
|
0,
|
|
);
|
|
const multipleCount = dropdownSettings.filter((s) => s.multiple).length;
|
|
const searchableCount = dropdownSettings.filter(
|
|
(s) => s.meta?.searchable,
|
|
).length;
|
|
|
|
const columns: ColumnDef<(typeof dropdownSettings)[number]>[] = [
|
|
{
|
|
id: "setting",
|
|
header: "Setting",
|
|
cell: ({ row }) => {
|
|
const s = row.original;
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
|
<Settings />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium text-slate-900">{s.label}</p>
|
|
<p className="text-xs text-slate-500">
|
|
{s.description ?? "No description"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "code",
|
|
header: "Code",
|
|
cell: ({ row }) => (
|
|
<span className="rounded-md bg-slate-100 px-2 py-1 font-mono text-xs text-slate-700">
|
|
{row.original.code}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
id: "options",
|
|
header: "Options",
|
|
cell: ({ row }) => {
|
|
const s = row.original;
|
|
return (
|
|
<div className="flex items-center gap-2 text-sm text-slate-700">
|
|
<Boxes />
|
|
<span className="font-medium">{s.children.length}</span>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "behavior",
|
|
header: "Behavior",
|
|
cell: ({ row }) => {
|
|
const s = row.original;
|
|
return (
|
|
<div className="flex flex-wrap gap-1">
|
|
{s.multiple ? (
|
|
<BehaviorChip label="Multi" />
|
|
) : (
|
|
<BehaviorChip label="Single" muted />
|
|
)}
|
|
{s.meta?.searchable ? (
|
|
<BehaviorChip label="Searchable" />
|
|
) : null}
|
|
{s.meta?.clearable ? (
|
|
<BehaviorChip label="Clearable" />
|
|
) : null}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "permissions",
|
|
header: "Permissions",
|
|
cell: ({ row }) => {
|
|
const s = row.original;
|
|
return (
|
|
<div className="flex flex-wrap items-center gap-1">
|
|
{(s.meta?.permissions ?? []).length === 0 ? (
|
|
<span className="text-xs text-slate-400">—</span>
|
|
) : (
|
|
(s.meta?.permissions ?? []).map((p) => (
|
|
<span
|
|
key={p}
|
|
className="inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary"
|
|
>
|
|
<Shield />
|
|
{p}
|
|
</span>
|
|
))
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "actions",
|
|
header: () => <div className="text-right">Actions</div>,
|
|
cell: ({ row }) => {
|
|
const setting = row.original;
|
|
return (
|
|
<div className="flex justify-end gap-2">
|
|
<ManageDropdownOptionsDialog setting={setting}>
|
|
<Button variant="outline" size="sm">
|
|
<CheckCircle2 />
|
|
Options
|
|
</Button>
|
|
</ManageDropdownOptionsDialog>
|
|
|
|
<EditDropdownSettingDialog mode="edit" setting={setting}>
|
|
<Button variant="outline" size="icon">
|
|
<Pencil />
|
|
</Button>
|
|
</EditDropdownSettingDialog>
|
|
|
|
<DeleteDropdownSettingDialog
|
|
settingLabel={setting.label}
|
|
settingCode={setting.code}
|
|
>
|
|
<Button variant="outline" size="icon">
|
|
<Trash2 />
|
|
</Button>
|
|
</DeleteDropdownSettingDialog>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="min-h-screen p-6">
|
|
<div className="space-y-6">
|
|
<Breadcrumbs
|
|
items={[
|
|
{ label: "Admin", href: "/admin" },
|
|
{ label: "Dropdown Settings" },
|
|
]}
|
|
/>
|
|
|
|
<Card className="p-6 flex-row justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight text-slate-900">
|
|
Dropdown Settings
|
|
</h1>
|
|
<p className="mt-1 text-sm text-secondary-foreground">
|
|
Manage every dynamic dropdown across the platform — labels,
|
|
options, ordering, and permissions.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
|
<div className="relative w-full sm:w-80">
|
|
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
|
|
<Input
|
|
type="search"
|
|
value={query}
|
|
onChange={(e) => {
|
|
setQuery(e.target.value);
|
|
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
|
}}
|
|
placeholder="Search by code, label, description..."
|
|
className="pl-8!"
|
|
/>
|
|
</div>
|
|
|
|
<EditDropdownSettingDialog mode="create">
|
|
<Button>
|
|
<Plus />
|
|
New Setting
|
|
</Button>
|
|
</EditDropdownSettingDialog>
|
|
</div>
|
|
</Card>
|
|
|
|
<div className="grid gap-4 md:grid-cols-4">
|
|
<Card>
|
|
<CardContent className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Settings</p>
|
|
<h3 className="mt-2 text-3xl font-bold text-slate-900">
|
|
{dropdownSettings.length}
|
|
</h3>
|
|
</div>
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
|
<Settings />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Total Options</p>
|
|
<h3 className="mt-2 text-3xl font-bold text-slate-900">
|
|
{totalOptions}
|
|
</h3>
|
|
</div>
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
|
<Boxes />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Multi-select</p>
|
|
<h3 className="mt-2 text-3xl font-bold text-slate-900">
|
|
{multipleCount}
|
|
</h3>
|
|
</div>
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
|
<ListOrdered />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardContent className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm text-slate-500">Searchable</p>
|
|
<h3 className="mt-2 text-3xl font-bold text-slate-900">
|
|
{searchableCount}
|
|
</h3>
|
|
</div>
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
|
<Sparkles />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Card className="gap-0">
|
|
<CardHeader className="flex flex-row items-center justify-between border-b">
|
|
<div>
|
|
<CardTitle>Registered Dropdowns</CardTitle>
|
|
<CardDescription>
|
|
Every dynamic dropdown the platform reads from.
|
|
</CardDescription>
|
|
</div>
|
|
|
|
<Button variant="secondary" size="sm">
|
|
<Filter />
|
|
Filter
|
|
</Button>
|
|
</CardHeader>
|
|
|
|
<CardContent className="px-0">
|
|
<DataTable
|
|
columns={columns}
|
|
data={paginatedData}
|
|
status="success"
|
|
onRowClick={() => {}}
|
|
pagination={{
|
|
pageIndex: pagination.pageIndex,
|
|
pageSize: pagination.pageSize,
|
|
pageCount: pageCount,
|
|
totalCount: total,
|
|
}}
|
|
tableOptions={{
|
|
state: { pagination },
|
|
onPaginationChange: setPagination,
|
|
}}
|
|
containerClassName="border-b shadow-none"
|
|
footer={DataTableFooter}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function BehaviorChip({
|
|
label,
|
|
muted = false,
|
|
}: {
|
|
label: string;
|
|
muted?: boolean;
|
|
}) {
|
|
return (
|
|
<span
|
|
className={
|
|
muted
|
|
? "rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600"
|
|
: "rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary"
|
|
}
|
|
>
|
|
{label}
|
|
</span>
|
|
);
|
|
}
|