From e552ac67d58646c2f8c7bbfbd6d20238e8d2914a Mon Sep 17 00:00:00 2001 From: yaschalew Date: Sat, 16 May 2026 16:32:56 +0300 Subject: [PATCH] add dashboard and dropdown settings --- apps/edr-freight-web/portal/index.css | 4 +- apps/edr-freight-web/portal/package.json | 1 + apps/edr-freight-web/portal/src/App.tsx | 7 + .../admin/DeleteDropdownSettingDialog.tsx | 65 ++ .../src/pages/admin/DropdownSettingsPage.tsx | 318 ++++++++ .../pages/admin/EditDropdownSettingDialog.tsx | 187 +++++ .../admin/ManageDropdownOptionsDialog.tsx | 230 ++++++ .../src/pages/admin/dropdownSettings.mock.ts | 388 ++++++++++ .../portal/src/pages/billing/BillingPage.tsx | 2 +- .../src/pages/bookings/BookingsPage.tsx | 2 +- .../pages/consignments/ConsignmentsPage.tsx | 2 +- .../src/pages/customers/CustomersPage.tsx | 2 +- .../src/pages/dashboard/DashboardPage.tsx | 699 +++++++++++++++++- .../src/pages/documents/DocumentsPage.tsx | 2 +- .../src/pages/tracking/TrackingPage.tsx | 2 +- .../portal/src/pages/trains/TrainsPage.tsx | 2 +- .../portal/src/types/dropdownSettings.ts | 33 + pnpm-lock.yaml | 3 + 18 files changed, 1931 insertions(+), 18 deletions(-) create mode 100644 apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/admin/ManageDropdownOptionsDialog.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/admin/dropdownSettings.mock.ts create mode 100644 apps/edr-freight-web/portal/src/types/dropdownSettings.ts diff --git a/apps/edr-freight-web/portal/index.css b/apps/edr-freight-web/portal/index.css index 21deab082..241330c15 100644 --- a/apps/edr-freight-web/portal/index.css +++ b/apps/edr-freight-web/portal/index.css @@ -8,8 +8,8 @@ } *::-webkit-scrollbar { - width: 6px; - height: 6px; + width: 8px; + height: 10px; } *::-webkit-scrollbar-track { diff --git a/apps/edr-freight-web/portal/package.json b/apps/edr-freight-web/portal/package.json index 22fd4c4f1..e3114fb45 100644 --- a/apps/edr-freight-web/portal/package.json +++ b/apps/edr-freight-web/portal/package.json @@ -24,6 +24,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.27.0", + "recharts": "^3.8.1", "tailwind-merge": "^3.6.0", "zustand": "^5.0.0" }, diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx index 9e644251d..f774eacb9 100644 --- a/apps/edr-freight-web/portal/src/App.tsx +++ b/apps/edr-freight-web/portal/src/App.tsx @@ -15,6 +15,7 @@ import { Train, Receipt, FileText, + Settings, } from "lucide-react"; import BookingsPage from "./pages/bookings/BookingsPage"; @@ -31,6 +32,7 @@ import CustomersPage from "./pages/customers/CustomersPage"; import CustomerDetailPage from "./pages/customers/CustomerDetailPage"; import NewCustomerPage from "./pages/customers/NewCustomerPage"; import DocumentsPage from "./pages/documents/DocumentsPage"; +import DropdownSettingsPage from "./pages/admin/DropdownSettingsPage"; const sidebarItems: SidebarItem[] = [ { label: "Dashboard", href: "/", icon: }, @@ -41,6 +43,7 @@ const sidebarItems: SidebarItem[] = [ { label: "Trains", href: "/trains", icon: }, { label: "Billing", href: "/billing", icon: }, { label: "Documents", href: "/documents", icon: }, + { label: "Dropdown Settings", href: "/admin/dropdowns", icon: }, ]; const App = () => { @@ -78,6 +81,10 @@ const App = () => { } /> } /> } /> + } + /> } /> diff --git a/apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx b/apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx new file mode 100644 index 000000000..c6589c4eb --- /dev/null +++ b/apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx @@ -0,0 +1,65 @@ +import type { ReactNode } from "react"; + +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; + +import { Button } from "@/components/ui/button"; + +export interface DeleteDropdownSettingDialogProps { + settingLabel: string; + settingCode: string; + onConfirm?: () => void; + children: ReactNode; +} + +export default function DeleteDropdownSettingDialog({ + settingLabel, + settingCode, + onConfirm, + children, +}: DeleteDropdownSettingDialogProps) { + return ( + + {children} + + + + + Delete dropdown setting? + + + + This will remove{" "} + {settingLabel}{" "} + ({settingCode}) and all + of its options. Forms referencing this code will fall back to + empty options. + + + + + + + + + + + + + + + ); +} diff --git a/apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx b/apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx new file mode 100644 index 000000000..6d63067a5 --- /dev/null +++ b/apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx @@ -0,0 +1,318 @@ +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"; + +export default function DropdownSettingsPage() { + 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 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; + + return ( +
+
+ + + {/* Header */} +
+
+

+ Dropdown Settings +

+

+ Manage every dynamic dropdown across the platform — labels, + options, ordering, and permissions. +

+
+ +
+
+ + setQuery(e.target.value)} + placeholder="Search by code, label, description..." + className="h-10 w-full rounded-2xl border border-slate-200 bg-white pl-10 pr-4 text-sm text-slate-700 outline-none transition placeholder:text-slate-400 focus:border-[#33578D]/50 focus:ring-2 focus:ring-[#33578D]/20" + /> +
+ + + + +
+
+ + {/* Stats */} +
+ } + /> + } + /> + } + /> + } + /> +
+ + {/* Table */} +
+
+
+

+ Registered Dropdowns +

+

+ Every dynamic dropdown the platform reads from. +

+
+ + +
+ +
+ + + + + + + + + + + + + + {filtered.length === 0 ? ( + + + + ) : ( + filtered.map((setting) => ( + + + + + + + + + + + + + + )) + )} + +
SettingCodeOptionsBehaviorPermissionsActions
+ No dropdown settings match your search. +
+
+
+ +
+
+

+ {setting.label} +

+

+ {setting.description ?? "No description"} +

+
+
+
+ + {setting.code} + + +
+ + + {setting.children.length} + +
+
+
+ {setting.multiple ? ( + + ) : ( + + )} + {setting.meta?.searchable ? ( + + ) : null} + {setting.meta?.clearable ? ( + + ) : null} +
+
+
+ {(setting.meta?.permissions ?? []).length === 0 ? ( + + ) : ( + (setting.meta?.permissions ?? []).map((p) => ( + + + {p} + + )) + )} +
+
+
+ + + + + + + + + + + +
+
+
+
+
+
+ ); +} + +function BehaviorChip({ + label, + muted = false, +}: { + label: string; + muted?: boolean; +}) { + return ( + + {label} + + ); +} + +function StatCard({ + title, + value, + icon, +}: { + title: string; + value: string; + icon: React.ReactNode; +}) { + return ( +
+
+
+

{title}

+

{value}

+
+
+ {icon} +
+
+
+ ); +} diff --git a/apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx b/apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx new file mode 100644 index 000000000..8b28b69e1 --- /dev/null +++ b/apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx @@ -0,0 +1,187 @@ +import { useState, type ReactNode } from "react"; +import { Hash } from "lucide-react"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; + +import type { DropdownSetting } from "@/types/dropdownSettings"; + +export interface EditDropdownSettingDialogProps { + mode?: "create" | "edit"; + setting?: DropdownSetting; + children: ReactNode; +} + +export default function EditDropdownSettingDialog({ + mode = "create", + setting, + children, +}: EditDropdownSettingDialogProps) { + const isEdit = mode === "edit"; + const [multiple, setMultiple] = useState(setting?.multiple ?? false); + const [searchable, setSearchable] = useState( + setting?.meta?.searchable ?? false, + ); + const [clearable, setClearable] = useState( + setting?.meta?.clearable ?? false, + ); + + return ( + + {children} + + + + + {isEdit ? "Edit Dropdown Setting" : "New Dropdown Setting"} + + + {isEdit + ? "Update the metadata for this dropdown setting." + : "Define a new dynamic dropdown that admins can manage."} + + + +
+
+ +
+ + +
+

+ Stable identifier used in code. Use snake_case. +

+
+ +
+ + +
+ +
+ +