From c4dd82dbfbc9c977144fd6ec83a3e012534b46d6 Mon Sep 17 00:00:00 2001 From: ghost2023 Date: Thu, 28 May 2026 16:28:11 +0300 Subject: [PATCH] refactor: rm the unused pages and components --- apps/edr-freight-web/portal/src/App.tsx | 69 +- .../src/components/stations/Station.tsx | 228 ------ .../portal/src/pages/MyPortalPage.tsx | 474 ++++++++++++ .../admin/DeleteDropdownSettingDialog.tsx | 77 -- .../admin/DeleteFileUploadSettingDialog.tsx | 65 -- .../src/pages/admin/DropdownSettingsPage.tsx | 468 ------------ .../pages/admin/EditDropdownSettingDialog.tsx | 336 --------- .../admin/EditFileUploadSettingDialog.tsx | 229 ------ .../pages/admin/FileUploadSettingsPage.tsx | 460 ------------ .../admin/ManageDropdownOptionsDialog.tsx | 339 --------- .../admin/ManageFileUploadFieldsDialog.tsx | 416 ----------- .../pages/admin/fileUploadSettings.mock.ts | 174 ----- .../src/pages/bookings/BookingsPage.tsx | 282 -------- .../consignments/ConsignmentDetailPage.tsx | 287 -------- .../pages/consignments/ConsignmentsPage.tsx | 429 ----------- .../consignments/DeleteConsignmentDialog.tsx | 63 -- .../pages/consignments/NewConsignmentPage.tsx | 235 ------ .../pages/customers/CustomerDetailPage.tsx | 273 ------- .../src/pages/customers/CustomersPage.tsx | 386 ---------- .../pages/customers/DeleteCustomerDialog.tsx | 72 -- .../pages/customers/NewCustomerPage copy.tsx | 223 ------ .../src/pages/customers/NewCustomerPage.tsx | 624 ---------------- .../pages/customers2/CustomerDetailPage.tsx | 273 ------- .../src/pages/customers2/CustomersPage.tsx | 386 ---------- .../pages/customers2/DeleteCustomerDialog.tsx | 72 -- .../src/pages/customers2/NewCustomerPage.tsx | 384 ---------- .../src/pages/customers2/customers.mock.ts | 110 --- .../src/pages/dashboard/DashboardPage.tsx | 676 ------------------ .../src/pages/documents/documents.mock.ts | 95 +-- .../src/pages/portal/MyPortalPage copy.tsx | 486 ------------- .../portal/src/pages/portal/MyPortalPage.tsx | 257 ------- .../src/pages/trains/DeleteTrainDialog.tsx | 61 -- .../portal/src/pages/trains/NewTrainPage.tsx | 240 ------- .../portal/src/pages/trains/TrainsPage.tsx | 372 ---------- .../portal/src/pages/trains/trains.mock.ts | 263 ------- 35 files changed, 539 insertions(+), 9345 deletions(-) delete mode 100644 apps/edr-freight-web/portal/src/components/stations/Station.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/DeleteFileUploadSettingDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/EditFileUploadSettingDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/FileUploadSettingsPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/ManageDropdownOptionsDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/ManageFileUploadFieldsDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/admin/fileUploadSettings.mock.ts delete mode 100644 apps/edr-freight-web/portal/src/pages/bookings/BookingsPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/consignments/ConsignmentDetailPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/consignments/ConsignmentsPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/consignments/DeleteConsignmentDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/consignments/NewConsignmentPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers/CustomerDetailPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers/CustomersPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers/DeleteCustomerDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers/NewCustomerPage copy.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers/NewCustomerPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers2/CustomerDetailPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers2/CustomersPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers2/DeleteCustomerDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers2/NewCustomerPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/customers2/customers.mock.ts delete mode 100644 apps/edr-freight-web/portal/src/pages/dashboard/DashboardPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/portal/MyPortalPage copy.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/portal/MyPortalPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/trains/DeleteTrainDialog.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/trains/NewTrainPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/trains/TrainsPage.tsx delete mode 100644 apps/edr-freight-web/portal/src/pages/trains/trains.mock.ts diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx index ed1c458d1..19b68dee4 100644 --- a/apps/edr-freight-web/portal/src/App.tsx +++ b/apps/edr-freight-web/portal/src/App.tsx @@ -7,65 +7,37 @@ import { } from "react-router-dom"; import { DashboardLayout, type SidebarItem } from "@edr/ui-common"; import { - LayoutDashboard, - Users, CalendarCheck, - Package, MapPin, - Train, Receipt, FileText, - Settings, - UserCircle, - FileUp, - MapPinned, + Home, Loader2, } from "lucide-react"; -import BookingsPage from "./pages/bookings/BookingsPage"; -import MyBookings from "./pages/bookings/MyBookings"; -import BookingDetailPage from "./pages/bookings/BookingDetailPage"; -import NewBookingPage from "./pages/bookings/NewBookingPage"; -import ConsignmentsPage from "./pages/consignments/ConsignmentsPage"; -import ConsignmentDetailPage from "./pages/consignments/ConsignmentDetailPage"; -import TrackingPage from "./pages/tracking/TrackingPage"; -import BillingPage from "./pages/billing/BillingPage"; -import TrainsPage from "./pages/trains/TrainsPage"; -import DashboardPage from "./pages/dashboard/DashboardPage"; import useAuth from "./hooks/useAuth"; -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"; -import FileUploadSettingsPage from "./pages/admin/FileUploadSettingsPage"; -import MyPortalPage from "./pages/portal/MyPortalPage"; + +import MyPortalPage from "./pages/MyPortalPage"; import EDRFreightLandingPage from "./pages/EDRFreightLandingPage"; import SignupPage from "./pages/accounts/SignupPage"; import OnboardingPage from "./pages/accounts/OnboardingPage"; import VerificationOtpPage from "./pages/accounts/VerificationOtpPage"; import SetPasswordPage from "./pages/accounts/SetPasswordPage"; import LoginPage from "./pages/accounts/LoginPage"; -import Station from "./components/stations/Station"; +import MyBookings from "./pages/bookings/MyBookings"; +import BookingDetailPage from "./pages/bookings/BookingDetailPage"; +import NewBookingPage from "./pages/bookings/NewBookingPage"; +import TrackingPage from "./pages/tracking/TrackingPage"; +import BillingPage from "./pages/billing/BillingPage"; +import DocumentsPage from "./pages/documents/DocumentsPage"; import { useEffect } from "react"; const sidebarItems: SidebarItem[] = [ - { label: "My Portal", href: "/", icon: }, - { label: "Dashboard", href: "/dashboard", icon: }, - { label: "Customers", href: "/customers", icon: }, + { label: "Home", href: "/", icon: }, { label: "My Bookings", href: "/bookings", icon: }, - { label: "Consignments", href: "/consignments", icon: }, { label: "Tracking", href: "/tracking", icon: }, - { label: "Stations", href: "/stations", icon: }, - { label: "Trains", href: "/trains", icon: }, { label: "Billing", href: "/billing", icon: }, { label: "Documents", href: "/documents", icon: }, - { label: "Dropdown Settings", href: "/admin/dropdowns", icon: }, - { - label: "File Upload Settings", - href: "/admin/file-uploads", - icon: , - }, ]; const App = () => { @@ -119,28 +91,19 @@ const App = () => { onLogout={logout} > - } /> } /> } /> - } /> - } /> - } /> - } /> } /> } /> - } /> - } /> } /> - } /> - } /> } /> } /> - } /> - } - /> - } /> + {/* } /> */} + {/* } */} + {/* /> */} + {/* } /> */} } /> diff --git a/apps/edr-freight-web/portal/src/components/stations/Station.tsx b/apps/edr-freight-web/portal/src/components/stations/Station.tsx deleted file mode 100644 index da65b3094..000000000 --- a/apps/edr-freight-web/portal/src/components/stations/Station.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { useMemo, useState } from "react"; -import { - AlertCircle, - CircleOff, - Loader2, - MapPin, - Search, - TrainFront, -} from "lucide-react"; - -import Breadcrumbs from "@/components/Breadcrumbs"; -import { useDropdownSettingByCode } from "@/hooks/useDropdownSettings"; -import type { DropdownOption } from "@/types/dropdownSettings"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - DataTable, - DataTableFooter, - Input, - type ColumnDef, - usePagination, -} from "@edr/ui-common"; - -const STATION_DROPDOWN_CODE = "stations_ter"; - -export default function Station() { - const { pagination, setPagination } = usePagination({ pageSize: 10 }); - const [query, setQuery] = useState(""); - - const { data, isLoading, isError, error } = useDropdownSettingByCode( - STATION_DROPDOWN_CODE, - ); - - const stations = useMemo( - () => [...(data?.children ?? [])].sort((a, b) => a.order - b.order), - [data?.children], - ); - - const filtered = useMemo(() => { - const q = query.trim().toLowerCase(); - if (!q) return stations; - - return stations.filter( - (station) => - station.label.toLowerCase().includes(q) || - station.value.toLowerCase().includes(q) || - (station.note ?? "").toLowerCase().includes(q), - ); - }, [query, stations]); - - const total = filtered.length; - const pageCount = Math.max(1, 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), - [end, filtered, start], - ); - - const activeCount = stations.filter((station) => !station.disabled).length; - const disabledCount = stations.length - activeCount; - - const status: "loading" | "error" | "success" = isLoading - ? "loading" - : isError - ? "error" - : "success"; - - const columns: ColumnDef[] = [ - { - id: "station", - header: "Station", - cell: ({ row }) => { - const station = row.original; - return ( -
-
- -
-
-

{station.label}

-

- {station.note ?? "No station note"} -

-
-
- ); - }, - }, - { - id: "value", - header: "Code", - cell: ({ row }) => ( - - {row.original.value} - - ), - }, - { - accessorKey: "order", - header: "Order", - }, - { - id: "status", - header: "Status", - cell: ({ row }) => - row.original.disabled ? ( - - - Disabled - - ) : ( - - - Active - - ), - }, - ]; - - return ( -
-
- - - -
-

- Stations -

-

- Station options loaded from dropdown code{" "} - stations_ter. -

-
- -
- - { - setQuery(event.target.value); - setPagination({ - pageIndex: 0, - pageSize: pagination.pageSize, - }); - }} - placeholder="Search stations..." - className="pl-8!" - /> -
-
- -
- - - -
- - {isError ? ( - - - - Failed to load stations.{" "} - {error instanceof Error ? error.message : "Unknown error."} - - - ) : null} - - - - Station List - - All configured freight stations from the dropdown service. - - - - - {isLoading ? ( -
- - Loading stations... -
- ) : ( - - )} -
-
-
-
- ); -} - -function StationStat({ label, value }: { label: string; value: number }) { - return ( - - -
-

{label}

-

{value}

-
-
- -
-
-
- ); -} diff --git a/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx b/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx new file mode 100644 index 000000000..63fdd014a --- /dev/null +++ b/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx @@ -0,0 +1,474 @@ +import { useMemo } from "react"; +import { Link } from "react-router-dom"; +import { + ArrowRight, + Building2, + CheckCircle2, + Clock, + DollarSign, + Eye, + Mail, + MapPin, + Package, + Phone, + Plus, + Receipt, + Truck, +} from "lucide-react"; + +import { + getCurrentCustomer, + getMyBookings, + getMyInvoices, + getMyShipments, +} from "@/lib/currentCustomer"; +import { formatCurrency } from "@/pages/billing/invoices.mock"; +import type { ShipmentStatus } from "@/pages/tracking/shipments.mock"; +import type { InvoiceStatus } from "@/pages/billing/invoices.mock"; +import type { BookingStatus } from "@/pages/bookings/bookings.mock"; +import { + Button, + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@edr/ui-common"; + +export default function MyPortalPage() { + const me = useMemo(() => getCurrentCustomer(), []); + const myBookings = useMemo(() => getMyBookings(), []); + const myShipments = useMemo(() => getMyShipments(), []); + const myInvoices = useMemo(() => getMyInvoices(), []); + + const activeBookings = myBookings.filter( + (b) => b.status === "Confirmed" || b.status === "In Transit", + ); + const activeShipments = myShipments.filter((s) => s.status === "In Transit"); + const outstandingInvoices = myInvoices.filter( + (inv) => inv.status === "Sent" || inv.status === "Overdue", + ); + const totalOutstanding = outstandingInvoices + .filter((inv) => inv.currency === "USD") + .reduce((sum, inv) => sum + inv.amount, 0); + const totalSpent = myInvoices + .filter((inv) => inv.status === "Paid" && inv.currency === "USD") + .reduce((sum, inv) => sum + inv.amount, 0); + + const recentBookings = [...myBookings].slice(0, 5); + const recentInvoices = [...myInvoices].slice(0, 4); + + return ( +
+
+ {/* Welcome banner */} +
+
+
+
+ {me.company.charAt(0)} +
+
+

Welcome back

+

{me.name}

+

+ + {me.company} + · + + {me.customerType} + +

+
+
+ +
+ + + + + + +
+
+
+ + {/* My KPIs */} +
+ + +
+

Active Bookings

+

+ {activeBookings.length} +

+

+ {myBookings.length} total +

+
+
+ +
+
+
+ + + +
+

In Transit

+

+ {activeShipments.length} +

+

+ {myShipments.length} shipments +

+
+
+ +
+
+
+ + + +
+

Outstanding

+

+ {formatCurrency(totalOutstanding, "USD")} +

+

+ {outstandingInvoices.length} invoices +

+
+
i.status === "Overdue") + ? "bg-red-100 text-red-600" + : "bg-primary/10 text-primary" + }`} + > + +
+
+
+ + + +
+

Total Spent

+

+ {formatCurrency(totalSpent, "USD")} +

+

+ All-time, paid invoices +

+
+
+ +
+
+
+
+ + {/* Active Shipments */} + + +
+ Active Shipments + + Live tracking for your in-flight cargo + +
+ + View all + + +
+ + + {activeShipments.length === 0 ? ( +

+ No shipments currently in transit. +

+ ) : ( +
+ {activeShipments.slice(0, 4).map((shipment) => ( +
+
+ + {shipment.reference} + + +
+

+ {shipment.originStation} + + {shipment.destinationStation} +

+
+ + + {shipment.currentLocation} + + ETA {shipment.eta} +
+
+
+
+
+ ))} +
+ )} + + + + {/* Recent Bookings + Profile */} +
+ {/* Recent bookings */} + + +
+ Recent Bookings + + Your latest freight requests + +
+ + View all + + +
+ + + {recentBookings.length === 0 ? ( +

+ You haven't booked any freight yet. +

+ ) : ( +
+ + + + + + + + + + + + {recentBookings.map((booking) => ( + + + + + + + + ))} + +
ReferenceRouteCargoStatusAction
+ {booking.reference} + + {booking.originStation} → {booking.destinationStation} + + {booking.cargoType} + + + + + + +
+
+ )} +
+
+ + {/* Profile card */} + + + My Profile + Account information + + +
+ } + label="Company" + value={me.company} + /> + } + label="Email" + value={me.email} + /> + } + label="Phone" + value={me.phone} + /> + } + label="Location" + value={`${me.city}, ${me.country}`} + /> +
+ + + View full profile + + +
+
+
+ + {/* Invoices */} + + +
+ Recent Invoices + + {outstandingInvoices.length} outstanding · {myInvoices.length}{" "} + total + +
+ + View all + + +
+ + + {recentInvoices.length === 0 ? ( +

+ No invoices yet. +

+ ) : ( +
+ {recentInvoices.map((invoice) => ( +
+
+ + +
+

+ {formatCurrency(invoice.amount, invoice.currency)} +

+

+ + Due {invoice.dueDate} +

+
+ ))} +
+ )} +
+
+
+
+ ); +} + +function ProfileRow({ + icon, + label, + value, +}: { + icon: React.ReactNode; + label: string; + value: string; +}) { + return ( +
+
{icon}
+
+

{label}

+

{value}

+
+
+ ); +} + +function ShipmentBadge({ status }: { status: ShipmentStatus }) { + const styles: Record = { + "In Transit": "bg-indigo-100 text-indigo-700", + Delivered: "bg-emerald-100 text-emerald-700", + Delayed: "bg-red-100 text-red-700", + }; + return ( + + {status} + + ); +} + +function BookingBadge({ status }: { status: BookingStatus }) { + const styles: Record = { + Pending: "bg-amber-100 text-amber-700", + Confirmed: "bg-sky-100 text-sky-700", + "In Transit": "bg-indigo-100 text-indigo-700", + Delivered: "bg-emerald-100 text-emerald-700", + Cancelled: "bg-red-100 text-red-700", + }; + return ( + + {status} + + ); +} + +function InvoiceBadge({ status }: { status: InvoiceStatus }) { + const styles: Record = { + Draft: "bg-slate-100 text-slate-600", + Sent: "bg-sky-100 text-sky-700", + Paid: "bg-emerald-100 text-emerald-700", + Overdue: "bg-red-100 text-red-700", + Cancelled: "bg-amber-100 text-amber-700", + }; + return ( + + {status} + + ); +} diff --git a/apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx b/apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx deleted file mode 100644 index 1ca4e0d23..000000000 --- a/apps/edr-freight-web/portal/src/pages/admin/DeleteDropdownSettingDialog.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useState, 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; - open?: boolean; - onOpenChange?: (open: boolean) => void; -} - -export default function DeleteDropdownSettingDialog({ - settingLabel, - settingCode, - onConfirm, - children, - open: openProp, - onOpenChange, -}: DeleteDropdownSettingDialogProps) { - const isControlled = openProp !== undefined; - const [internalOpen, setInternalOpen] = useState(false); - const open = isControlled ? openProp : internalOpen; - const setOpen = (next: boolean) => { - if (!isControlled) setInternalOpen(next); - onOpenChange?.(next); - }; - - return ( - - {children ? {children} : null} - - - - - 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/DeleteFileUploadSettingDialog.tsx b/apps/edr-freight-web/portal/src/pages/admin/DeleteFileUploadSettingDialog.tsx deleted file mode 100644 index 84693fa28..000000000 --- a/apps/edr-freight-web/portal/src/pages/admin/DeleteFileUploadSettingDialog.tsx +++ /dev/null @@ -1,65 +0,0 @@ -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 DeleteFileUploadSettingDialogProps { - settingLabel: string; - settingCode: string; - onConfirm?: () => void; - children: ReactNode; -} - -export default function DeleteFileUploadSettingDialog({ - settingLabel, - settingCode, - onConfirm, - children, -}: DeleteFileUploadSettingDialogProps) { - return ( - - {children} - - - - - Delete file upload setting? - - - - This will remove{" "} - {settingLabel}{" "} - ({settingCode}) and all - of its fields. Forms referencing this code will fall back to no - uploads. - - - - - - - - - - - - - - - ); -} diff --git a/apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx b/apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx deleted file mode 100644 index 1b77dcc59..000000000 --- a/apps/edr-freight-web/portal/src/pages/admin/DropdownSettingsPage.tsx +++ /dev/null @@ -1,468 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { - AlertCircle, - Boxes, - CheckCircle2, - Eye, - Filter, - ListOrdered, - Loader2, - MoreHorizontal, - 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 { - useDeleteDropdownSetting, - useDropdownSettings, -} from "@/hooks/useDropdownSettings"; -import type { DropdownSetting } from "@/types/dropdownSettings"; -import { - DataTable, - DataTableFooter, - type ColumnDef, - usePagination, - Button, - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - Input, - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, -} from "@edr/ui-common"; - -type ActiveDialog = "edit" | "options" | "delete"; - -export default function DropdownSettingsPage() { - const { pagination, setPagination } = usePagination({ pageSize: 10 }); - const [query, setQuery] = useState(""); - - const [activeDialog, setActiveDialog] = useState(null); - const [activeSetting, setActiveSetting] = useState( - null, - ); - - const openDialogFor = (dialog: ActiveDialog, setting: DropdownSetting) => { - // Defer past the DropdownMenu's close cycle. Radix's modal lock can leave - // `pointer-events: none` on when a menu closes and a dialog opens - // in the same frame — wait two RAFs and then explicitly reset the body - // style so the dialog interior is interactive. - requestAnimationFrame(() => { - requestAnimationFrame(() => { - document.body.style.pointerEvents = ""; - setActiveSetting(setting); - setActiveDialog(dialog); - }); - }); - }; - const closeDialog = () => { - setActiveDialog(null); - // Keep activeSetting briefly so dialog content doesn't flash empty during - // the close animation; cleared on next open. - }; - - // Belt-and-suspenders for the Radix pointer-events leak: any time the active - // dialog changes, schedule a body-style cleanup after the next paint. - useEffect(() => { - const id = requestAnimationFrame(() => { - if (document.body.style.pointerEvents === "none") { - document.body.style.pointerEvents = ""; - } - }); - return () => cancelAnimationFrame(id); - }, [activeDialog]); - - const { data, isLoading, isError, error } = useDropdownSettings(); - const deleteMutation = useDeleteDropdownSetting(); - - const dropdownSettings = useMemo( - () => (Array.isArray(data) ? data : []), - [data], - ); - - 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), - ); - }, [dropdownSettings, query]); - - const total = filtered.length; - const pageCount = Math.max(1, 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), - 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 }) => { - const s = row.original; - return ( -
- - {s.children?.length ?? 0} -
- ); - }, - }, - { - id: "behavior", - header: "Behavior", - cell: ({ row }) => { - const s = row.original; - return ( -
- {s.multiple ? ( - - ) : ( - - )} - {s.meta?.searchable ? : null} - {s.meta?.clearable ? : null} -
- ); - }, - }, - { - id: "permissions", - header: "Permissions", - cell: ({ row }) => { - const s = row.original; - const perms = s.meta?.permissions ?? []; - return ( -
- {perms.length === 0 ? ( - - ) : ( - perms.map((p) => ( - - - {p} - - )) - )} -
- ); - }, - }, - { - id: "actions", - size: 40, - cell: ({ row }) => { - const setting = row.original; - return ( -
e.stopPropagation()} - > - - - - - - - - View - - openDialogFor("options", setting)} - > - - Options - - - openDialogFor("edit", setting)} - > - - Edit - - - openDialogFor("delete", setting)} - variant="destructive" - > - - Delete - - - -
- ); - }, - }, - ]; - - return ( -
-
- - - -
-

- Dropdown Settings -

-

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

-
- -
-
- - { - setQuery(e.target.value); - setPagination({ - pageIndex: 0, - pageSize: pagination.pageSize, - }); - }} - placeholder="Search by code, label, description..." - className="pl-8!" - /> -
- - - - -
-
- -
- } - /> - } - /> - } - /> - } - /> -
- - {isError ? ( - - - - Failed to load dropdown settings.{" "} - {error instanceof Error ? error.message : "Unknown error."} - - - ) : null} - - - -
- Registered Dropdowns - - Every dynamic dropdown the platform reads from. - -
- - -
- - - {isLoading ? ( -
- - Loading dropdown settings… -
- ) : ( - { }} - pagination={{ - pageIndex: pagination.pageIndex, - pageSize: pagination.pageSize, - pageCount: pageCount, - totalCount: total, - }} - tableOptions={{ - state: { pagination }, - onPaginationChange: setPagination, - }} - containerClassName="border-b shadow-none" - footer={DataTableFooter} - /> - )} -
-
-
- - {/* Controlled dialogs — hoisted out of the DropdownMenu so they can open - reliably after a menu item is selected. */} - {activeSetting ? ( - <> - (next ? null : closeDialog())} - /> - (next ? null : closeDialog())} - /> - deleteMutation.mutate(activeSetting.id)} - open={activeDialog === "delete"} - onOpenChange={(next) => (next ? null : closeDialog())} - /> - - ) : null} -
- ); -} - -function StatCard({ - label, - value, - icon, -}: { - label: string; - value: number; - icon: React.ReactNode; -}) { - return ( - - -
-

{label}

-

{value}

-
-
- {icon} -
-
-
- ); -} - -function BehaviorChip({ - label, - muted = false, -}: { - label: string; - muted?: boolean; -}) { - return ( - - {label} - - ); -} diff --git a/apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx b/apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx deleted file mode 100644 index 14922446c..000000000 --- a/apps/edr-freight-web/portal/src/pages/admin/EditDropdownSettingDialog.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import { useState, type ReactNode } from "react"; -import { Hash, Loader2 } from "lucide-react"; - -import { - Dialog, - DialogClose, - 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 { - CreateDropdownSettingDto, - DropdownSetting, - UpdateDropdownSettingDto, -} from "@/types/dropdownSettings"; -import { - useCreateDropdownSetting, - useUpdateDropdownSetting, -} from "@/hooks/useDropdownSettings"; - -export interface EditDropdownSettingDialogProps { - mode?: "create" | "edit"; - setting?: DropdownSetting; - /** Optional trigger element. When omitted, the dialog renders content only and is fully controlled. */ - children?: ReactNode; - /** Controlled open state. When provided, internal state is ignored. */ - open?: boolean; - onOpenChange?: (open: boolean) => void; -} - -function parsePermissions(raw: string): string[] { - return raw - .split(",") - .map((s) => s.trim()) - .filter(Boolean); -} - -export default function EditDropdownSettingDialog({ - mode = "create", - setting, - children, - open: openProp, - onOpenChange, -}: EditDropdownSettingDialogProps) { - const isEdit = mode === "edit"; - const isControlled = openProp !== undefined; - - const [internalOpen, setInternalOpen] = useState(false); - const open = isControlled ? openProp : internalOpen; - const setOpen = (next: boolean) => { - if (!isControlled) setInternalOpen(next); - onOpenChange?.(next); - }; - const [code, setCode] = useState(setting?.code ?? ""); - const [label, setLabel] = useState(setting?.label ?? ""); - const [description, setDescription] = useState(setting?.description ?? ""); - const [icon, setIcon] = useState(setting?.meta?.icon ?? ""); - const [color, setColor] = useState(setting?.meta?.color ?? ""); - const [permissions, setPermissions] = useState( - setting?.meta?.permissions?.join(", ") ?? "", - ); - const [version, setVersion] = useState(setting?.meta?.version ?? "1.0"); - const [multiple, setMultiple] = useState(setting?.multiple ?? false); - const [searchable, setSearchable] = useState( - setting?.meta?.searchable ?? false, - ); - const [clearable, setClearable] = useState( - setting?.meta?.clearable ?? false, - ); - const [error, setError] = useState(null); - - const createMutation = useCreateDropdownSetting(); - const updateMutation = useUpdateDropdownSetting(); - const pending = createMutation.isPending || updateMutation.isPending; - - const reset = () => { - setCode(setting?.code ?? ""); - setLabel(setting?.label ?? ""); - setDescription(setting?.description ?? ""); - setIcon(setting?.meta?.icon ?? ""); - setColor(setting?.meta?.color ?? ""); - setPermissions(setting?.meta?.permissions?.join(", ") ?? ""); - setVersion(setting?.meta?.version ?? "1.0"); - setMultiple(setting?.multiple ?? false); - setSearchable(setting?.meta?.searchable ?? false); - setClearable(setting?.meta?.clearable ?? false); - setError(null); - }; - - const buildPayload = (): CreateDropdownSettingDto => ({ - code: code.trim(), - label: label.trim(), - description: description.trim() || undefined, - multiple, - meta: { - ...(icon.trim() ? { icon: icon.trim() } : {}), - ...(color.trim() ? { color: color.trim() } : {}), - searchable, - clearable, - ...(version.trim() ? { version: version.trim() } : {}), - permissions: parsePermissions(permissions), - }, - }); - - const handleSubmit = () => { - setError(null); - if (!code.trim() || !label.trim()) { - setError("Code and label are required."); - return; - } - if (!/^[a-z][a-z0-9_]*$/i.test(code.trim())) { - setError( - "Code must start with a letter and contain only letters, digits, or underscores.", - ); - return; - } - - const payload = buildPayload(); - - const onDone = () => { - setOpen(false); - if (!isEdit) reset(); - }; - const onError = (err: unknown) => { - setError( - err instanceof Error - ? err.message - : "Something went wrong. Try again.", - ); - }; - - if (isEdit && setting) { - // Update DTO omits `code` (immutable); strip it before sending. - const { code: _unused, ...updateDto } = payload; - void _unused; - updateMutation.mutate( - { id: setting.id, dto: updateDto as UpdateDropdownSettingDto }, - { onSuccess: onDone, onError }, - ); - } else { - createMutation.mutate(payload, { onSuccess: onDone, onError }); - } - }; - - return ( - { - setOpen(next); - if (!next) reset(); - }} - > - {children ? {children} : null} - - - - - {isEdit ? "Edit Dropdown Setting" : "New Dropdown Setting"} - - - {isEdit - ? "Update the metadata for this dropdown setting." - : "Define a new dynamic dropdown that admins can manage."} - - - -
-
- -
- - setCode(e.target.value)} - placeholder="e.g. cargo_type" - className="pl-10 font-mono" - disabled={isEdit} - /> -
-

- {isEdit - ? "Code is immutable after creation." - : "Stable identifier used in code. Use snake_case."} -

-
- -
- - setLabel(e.target.value)} - placeholder="e.g. Cargo Type" - /> -
- -
- -