From a0dbb4ca6fbd177d39ff0dedb610bfde39e959ca Mon Sep 17 00:00:00 2001 From: marshal Date: Tue, 16 Jun 2026 11:04:36 +0300 Subject: [PATCH] backoffice cleen up --- apps/edr-freight-web/backoffice/src/App.tsx | 51 +- .../src/components/fleet/FleetFormDialog.tsx | 10 +- .../src/components/layout/FreightSidebar.css | 16 + .../src/components/layout/FreightSidebar.tsx | 30 +- .../src/components/layout/route-meta.ts | 9 +- .../src/pages/trains/TrainsPage.tsx | 482 ------------------ 6 files changed, 71 insertions(+), 527 deletions(-) delete mode 100644 apps/edr-freight-web/backoffice/src/pages/trains/TrainsPage.tsx diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx index 8eaf0d4e1..744fa17d8 100644 --- a/apps/edr-freight-web/backoffice/src/App.tsx +++ b/apps/edr-freight-web/backoffice/src/App.tsx @@ -37,7 +37,6 @@ import DropdownSettingsPage from "./pages/dropdown_settings/DropdownSettingsPage import FileUploadSettingsPage from "./pages/documents/FileUploadSettingsPage"; import RuleEngineLegacyRedirect from "./pages/ruleEngine/RuleEngineLegacyRedirect"; import RuleEngineResourcePage from "./pages/ruleEngine/RuleEngineResourcePage"; -import TrainsPage from "./pages/trains/TrainsPage"; import TrainScheduleV2ListPage from "./pages/trainScheduling/TrainScheduleV2ListPage"; import BatchBoardPage from "./pages/trainScheduling/BatchBoardPage"; import BatchScheduleDetailPage from "./pages/trainScheduling/BatchScheduleDetailPage"; @@ -78,11 +77,6 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [ items: [ { label: "Train Schedules", - href: "/dashboard/operations/train-scheduling", - icon: , - }, - { - label: "Train Schedules v2", href: "/dashboard/operations/train-scheduling-v2", icon: , }, @@ -106,31 +100,31 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [ href: "/dashboard/locomotives", icon: , }, - { - label: "Trains", - href: "/dashboard/trains", - icon: , - }, - { - label: "Wagon types", - href: "/dashboard/wagon-types", - icon: , - }, + // { + // label: "Trains", + // href: "/dashboard/trains", + // icon: , + // }, + // { + // label: "Wagon types", + // href: "/dashboard/wagon-types", + // icon: , + // }, { label: "Wagons", href: "/dashboard/wagons", icon: , }, - { - label: "Containers", - href: "/dashboard/containers", - icon: , - }, - { - label: "Cargoes", - href: "/dashboard/cargoes", - icon: , - }, + // { + // label: "Containers", + // href: "/dashboard/containers", + // icon: , + // }, + // { + // label: "Cargoes", + // href: "/dashboard/cargoes", + // icon: , + // }, ], }, { @@ -272,7 +266,10 @@ const App = () => { path="booking-requests/:id/contract" element={} /> - } /> + } + /> } /> - setValues((current) => ({ ...current, [field.name]: e.currentTarget.value })) + setValues((current) => ({ + ...current, + [field.name]: e.target?.value ?? "", + })) } error={error} minRows={3} @@ -164,7 +167,10 @@ const FleetFormDialog = ({ label={field.label} value={String(value ?? "")} onChange={(e) => - setValues((current) => ({ ...current, [field.name]: e.currentTarget.value })) + setValues((current) => ({ + ...current, + [field.name]: e.target?.value ?? "", + })) } error={error} /> diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css index 8d14daad9..17dce66c3 100644 --- a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css +++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.css @@ -181,6 +181,22 @@ transition: transform 220ms ease; } +.fsb-chevron-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 0; + margin: 0; + border: none; + background: transparent; + cursor: pointer; + flex-shrink: 0; +} + +.fsb-chevron-btn:hover .fsb-chevron { + color: #64748b; +} + /* ---- Nested branch ---- */ .fsb-branch { margin: 2px 0 2px 22px; diff --git a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx index 708888c62..a2d685d0f 100644 --- a/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx +++ b/apps/edr-freight-web/backoffice/src/components/layout/FreightSidebar.tsx @@ -180,23 +180,37 @@ const FreightSidebar = ({ href={item.href} className="fsb-item" data-active={isActive} - onClick={(e) => navigateTo(e, item.href!)} + onClick={(e) => { + if (hasChildren) { + setExpanded((current) => ({ + ...current, + [item.href!]: true, + })); + } + navigateTo(e, item.href!); + }} > {item.icon && {item.icon}} {item.label} {hasChildren && ( - { e.preventDefault(); e.stopPropagation(); toggleExpanded(item.href!); }} - /> + > + + )} diff --git a/apps/edr-freight-web/backoffice/src/components/layout/route-meta.ts b/apps/edr-freight-web/backoffice/src/components/layout/route-meta.ts index a498580a7..bc57dcdeb 100644 --- a/apps/edr-freight-web/backoffice/src/components/layout/route-meta.ts +++ b/apps/edr-freight-web/backoffice/src/components/layout/route-meta.ts @@ -45,16 +45,9 @@ const ROUTE_META: Array<{ prefix: string; meta: PageMeta }> = [ }, { prefix: "/dashboard/operations/train-scheduling-v2", - meta: { - title: "Train Schedules v2", - subtitle: "Operational train scheduling with full allocation workflow", - }, - }, - { - prefix: "/dashboard/operations/train-scheduling", meta: { title: "Train Schedules", - subtitle: "Create and manage container train schedules", + subtitle: "Operational train scheduling with full allocation workflow", }, }, { diff --git a/apps/edr-freight-web/backoffice/src/pages/trains/TrainsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/trains/TrainsPage.tsx deleted file mode 100644 index 53ee2fa6f..000000000 --- a/apps/edr-freight-web/backoffice/src/pages/trains/TrainsPage.tsx +++ /dev/null @@ -1,482 +0,0 @@ -import { useMemo, useState } from 'react'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { isAxiosError } from 'axios'; -import toast from 'react-hot-toast'; -import { Calendar, RefreshCw, TrainTrack } from 'lucide-react'; -import { - Button, - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@edr/ui-common'; - -import Breadcrumbs from '@/components/ui/Breadcrumbs'; -import { QUERY_KEYS } from '@/constants/QUERY_KEYS'; -import { useRoutes } from '@/hooks/useRoutes'; -import { trainSchedulingService } from '@/services/trainScheduling.service'; - -const inputClassName = - 'w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm text-foreground outline-none transition focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100 dark:focus:ring-emerald-950'; - -const formatDate = (value?: string | null) => { - if (!value) return '-'; - const date = new Date(value); - if (Number.isNaN(date.getTime())) return '-'; - return new Intl.DateTimeFormat('en', { - year: 'numeric', - month: 'short', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }).format(date); -}; - -const parseError = (error: unknown, fallback: string) => { - if (isAxiosError(error)) { - const message = error.response?.data?.message; - if (Array.isArray(message)) return message.join(', '); - if (typeof message === 'string') return message; - const violations = error.response?.data?.violations; - if (Array.isArray(violations)) return violations.join(', '); - } - return fallback; -}; - -const TrainsPage = () => { - const qc = useQueryClient(); - const [routeId, setRouteId] = useState(''); - const [scheduleDate, setScheduleDate] = useState(''); - const [selectedLocomotiveId, setSelectedLocomotiveId] = useState(''); - const [detailId, setDetailId] = useState(null); - const [scheduleSearch, setScheduleSearch] = useState(''); - const [scheduleStatusFilter, setScheduleStatusFilter] = useState('ALL'); - - const routesQuery = useRoutes(); - const locomotivesQuery = useQuery({ - queryKey: QUERY_KEYS.TRAIN_SCHEDULING.locomotives(), - queryFn: () => trainSchedulingService.getAvailableLocomotives(), - }); - const schedulesQuery = useQuery({ - queryKey: QUERY_KEYS.TRAIN_SCHEDULING.schedules(), - queryFn: () => trainSchedulingService.listSchedules(), - }); - const detailQuery = useQuery({ - queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(detailId ?? ''), - queryFn: () => trainSchedulingService.getScheduleById(detailId!), - enabled: Boolean(detailId), - }); - - const activeRoutes = useMemo( - () => (routesQuery.data ?? []).filter((route) => route.isActive), - [routesQuery.data], - ); - const selectedRoute = activeRoutes.find((route) => route.id === routeId) ?? null; - const selectedLocomotive = (locomotivesQuery.data ?? []).find( - (locomotive) => locomotive.id === selectedLocomotiveId, - ); - - const filteredSchedules = useMemo(() => { - const query = scheduleSearch.trim().toLowerCase(); - - return (schedulesQuery.data ?? []).filter((schedule) => { - const matchesStatus = - scheduleStatusFilter === 'ALL' || schedule.status === scheduleStatusFilter; - - if (!matchesStatus) { - return false; - } - - if (!query) { - return true; - } - - const haystack = [ - schedule.id, - schedule.routeName ?? '', - schedule.origin ?? '', - schedule.destination ?? '', - schedule.locomotive?.code ?? '', - schedule.status, - ] - .join(' ') - .toLowerCase(); - - return haystack.includes(query); - }); - }, [scheduleSearch, scheduleStatusFilter, schedulesQuery.data]); - - const createMutation = useMutation({ - mutationFn: () => { - if (!routeId || !scheduleDate || !selectedLocomotiveId) { - throw new Error('Please select route, departure date, and locomotive'); - } - - return trainSchedulingService.createSchedule({ - routeId, - scheduleDate: new Date(`${scheduleDate}T08:00:00.000Z`).toISOString(), - locomotiveId: selectedLocomotiveId, - }); - }, - onSuccess: (data) => { - toast.success('Train schedule created'); - setRouteId(''); - setScheduleDate(''); - setSelectedLocomotiveId(''); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.ROOT }); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.schedules() }); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.locomotives() }); - setDetailId(data.id); - }, - onError: (error) => { - toast.error(parseError(error, 'Failed to create train schedule')); - }, - }); - - const cancelMutation = useMutation({ - mutationFn: (id: string) => trainSchedulingService.cancelSchedule(id), - onSuccess: (data) => { - toast.success('Train schedule cancelled'); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.schedules() }); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.locomotives() }); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.ROOT }); - void qc.invalidateQueries({ queryKey: QUERY_KEYS.TRAIN_SCHEDULING.scheduleById(data.id) }); - setDetailId(data.id); - }, - onError: (error) => { - toast.error(parseError(error, 'Failed to cancel train schedule')); - }, - }); - - const detail = detailQuery.data; - const isBusy = createMutation.isPending; - - return ( -
- - -
-
-
-
- -
-
-

Train Schedules

-

- Create the train schedule first, reserve the locomotive, and assign bookings and wagons later. -

-
-
- -
- -
-
-
- -

Schedule builder

-
- -
-
- - -
- -
- - setScheduleDate(event.target.value)} - /> -
- -
- - -
-
- -
-
-

Origin

-

- {selectedRoute?.originYard?.label ?? selectedRoute?.originYard?.code ?? '-'} -

-
-
-

Destination

-

- {selectedRoute?.destinationYard?.label ?? selectedRoute?.destinationYard?.code ?? '-'} -

-
-
-

Locomotive capacity

-

- {selectedLocomotive - ? `${selectedLocomotive.maxPullWeightTons}T / ${selectedLocomotive.maxTrainLengthMeters}m` - : '-'} -

-
-
-

Next step

-

Assign bookings, then allocate wagons

-
-
- -
- -
-
- -
-
-
-

Created schedules

-

- Open a schedule to inspect the reserved locomotive and prepare for later booking and wagon work. -

-
- - {filteredSchedules.length} schedules - -
- -
- setScheduleSearch(event.target.value)} - /> - -
- -
- - - - - - - - - - - - - - - - - {filteredSchedules.map((schedule) => ( - - - - - - - - - - - - - ))} - {!schedulesQuery.isLoading && filteredSchedules.length === 0 ? ( - - - - ) : null} - -
ScheduleDepartureRouteLocomotiveBookingsWagonsWeightLengthStatusActions
{schedule.id}{formatDate(schedule.scheduleDate)} - {schedule.routeName ?? `${schedule.origin ?? '-'} to ${schedule.destination ?? '-'}`} - {schedule.locomotive?.code ?? '-'}{schedule.bookingsCount}{schedule.wagonCount}{schedule.totalWeightTons} T{schedule.totalLengthMeters} m{schedule.status} -
- - {schedule.status !== 'CANCELLED' ? ( - - ) : null} -
-
- No train schedules matched the current filters. -
-
-
-
-
- - (!open ? setDetailId(null) : null)}> - - - Train schedule detail - - Inspect the selected schedule. Booking assignment and wagon allocation happen after schedule creation. - - - - {detail ? ( -
-
-
-

Schedule

-

{detail.id}

-
-
-

Departure

-

{formatDate(detail.scheduledDepartureDate)}

-
-
-

Route

-

{detail.route?.name ?? '-'}

-
-
-

Origin / destination

-

- {detail.originStation?.label ?? detail.originStation?.code ?? '-'} to{' '} - {detail.destinationStation?.label ?? detail.destinationStation?.code ?? '-'} -

-
-
-

Status

-

{detail.status}

-
-
- -
-

Locomotive

-

- {detail.trainSet?.locomotive - ? `${detail.trainSet.locomotive.code} (${detail.trainSet.locomotive.maxPullWeightTons}T pull capacity / ${detail.trainSet.locomotive.maxTrainLengthMeters ?? 0}m)` - : 'No locomotive attached'} -

-
- -
-

Wagons and allocations

- {(detail.trainSet?.wagons?.length ?? 0) === 0 ? ( -

No wagons allocated yet.

- ) : ( -
- {(detail.trainSet?.wagons ?? []).map((wagon) => ( -
-
-
-

- Wagon {wagon.sequenceNo} - {wagon.wagonType?.code ?? 'NW5'} -

-

- {wagon.assignedWeightTons}T assigned / {wagon.capacityTons}T capacity / {wagon.lengthMeters}m -

-
-
-
- ))} -
- )} -
- -
-

Bookings in schedule

- {detail.bookings.length === 0 ? ( -

No bookings assigned yet.

- ) : ( -
- - - - - - - - - - - {detail.bookings.map((booking) => ( - - - - - - - ))} - -
ReferenceCustomerWeightStatus
{booking.reference ?? booking.id}{booking.customer ?? '-'}{booking.weightTons} T{booking.status ?? '-'}
-
- )} -
-
- ) : ( -

Loading schedule detail...

- )} -
-
-
- ); -}; - -export default TrainsPage;