From cdfe7c1f5b4c6602569b091190b357fe6b82c796 Mon Sep 17 00:00:00 2001 From: ghost2023 Date: Tue, 23 Jun 2026 15:02:25 +0300 Subject: [PATCH] fix: type errors --- apps/edr-freight-web/backoffice/package.json | 2 +- .../baselineRatematrix/RateMatrixForm.tsx | 330 -------- .../bookings/BookingRequestsHeader.tsx | 110 +-- .../bookings/detail/BookingRequestHero.tsx | 57 +- .../src/components/fleet/FleetFormDialog.tsx | 66 +- .../components/fleet/FleetRecordActions.tsx | 56 +- .../ruleEngine/RuleEngineCardGrid.tsx | 47 +- .../ruleEngine/RuleEngineListFooter.tsx | 2 +- .../warehouses/WarehouseInventoryTable.tsx | 141 ++-- .../src/components/warehouses/badges.tsx | 69 +- .../bookings/booking-actions.config.ts | 24 +- .../backoffice/src/lib/currentCustomer.ts | 45 - .../admin/rateMatrix/RateMatrixApproval.tsx | 117 --- .../rateMatrix/RateMatrixRegistration.tsx | 25 - .../src/pages/bookings/BookingDetailPage.tsx | 1 - .../bookings/BookingRequestDetailPage.tsx | 157 ++-- .../user-management/PositionTypesPage.tsx | 327 +++++--- .../dashboard/user-management/iamConfig.ts | 3 +- .../documents/EditFileUploadSettingDialog.tsx | 31 +- .../src/pages/payments/PaymentsPage.tsx | 43 +- .../ruleEngine/RuleEngineResourcePage.tsx | 124 ++- .../BatchScheduleDetailPage.tsx | 785 ++++++++++-------- .../services/ruleEngine/ruleEngine.service.ts | 42 +- .../src/services/trainScheduling.service.ts | 79 +- .../src/types/@tria-plc__iamui.d.ts | 55 ++ .../backoffice/src/types/trainScheduling.ts | 15 +- .../backoffice/tsconfig.app.json | 3 +- 27 files changed, 1379 insertions(+), 1377 deletions(-) delete mode 100644 apps/edr-freight-web/backoffice/src/components/baselineRatematrix/RateMatrixForm.tsx delete mode 100644 apps/edr-freight-web/backoffice/src/lib/currentCustomer.ts delete mode 100644 apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixApproval.tsx delete mode 100644 apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixRegistration.tsx create mode 100644 apps/edr-freight-web/backoffice/src/types/@tria-plc__iamui.d.ts diff --git a/apps/edr-freight-web/backoffice/package.json b/apps/edr-freight-web/backoffice/package.json index e8c9d21e6..8f0e233b4 100644 --- a/apps/edr-freight-web/backoffice/package.json +++ b/apps/edr-freight-web/backoffice/package.json @@ -9,7 +9,7 @@ "preview": "vite preview --port 5183", "lint": "eslint src", "test": "vitest run", - "type-check": "tsc --noEmit" + "type-check": "tsc -b" }, "dependencies": { "@edr/types": "workspace:*", diff --git a/apps/edr-freight-web/backoffice/src/components/baselineRatematrix/RateMatrixForm.tsx b/apps/edr-freight-web/backoffice/src/components/baselineRatematrix/RateMatrixForm.tsx deleted file mode 100644 index f18389f9e..000000000 --- a/apps/edr-freight-web/backoffice/src/components/baselineRatematrix/RateMatrixForm.tsx +++ /dev/null @@ -1,330 +0,0 @@ -// components/baselineRatematrix/RateMatrixForm.tsx -import React, { useState, useCallback } from 'react'; -// import { useForm } from 'react-hook-form'; -// import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from 'sonner'; -import { z } from 'zod'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { Loader2, Save, Send, Shield, AlertTriangle } from 'lucide-react'; -import { RateTypeSection } from './RateTypeSection'; -import { ConfirmationDialog } from './ConfirmationDialog'; -import { ValidationSummary } from './ValidationSummary'; -import { LoadingScreen } from '@/ui/LoadingScreen'; -import { useRateMatrixAuth } from '@/auth/hooks/useAuth'; -import { useReferenceData } from '@/hooks/useReferenceData'; -import { queryKeys } from '@/constants/queryKeys'; -import { API_URLS } from '@/constants/apiUrls'; -import { - RATE_TYPES, - RATE_TYPE_LABELS, - REQUIRED_RATE_TYPES -} from '@/constants/rateMatrixConstants'; -import { rateMatrixRulesEngine } from '../../ruleEngine/rateMatrixRules'; -import type { RateEntry } from './types'; - -const formSchema = z.object({ - matrixName: z.string().min(1, 'Matrix name is required').max(200), - effectiveDate: z.string().min(1, 'Effective date is required'), - expiryDate: z.string().optional(), - currency: z.string().min(1, 'Currency is required'), -}); - -type FormData = z.infer; - -const createInitialSections = (): RateEntry[] => { - return REQUIRED_RATE_TYPES.map(rateType => ({ - rateType, - entries: [{ - validFrom: '', - validTo: '', - }], - })); -}; - -export function RateMatrixForm() { - const [rateSections, setRateSections] = useState(createInitialSections()); - const [showConfirmation, setShowConfirmation] = useState(false); - const [savedMatrixId, setSavedMatrixId] = useState(null); - const [validationErrors, setValidationErrors] = useState([]); - - const { isDirector } = useRateMatrixAuth(); - const { data: referenceData, isLoading: isLoadingReference } = useReferenceData(); - const queryClient = useQueryClient(); - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - matrixName: '', - effectiveDate: '', - expiryDate: '', - currency: 'USD', - }, - }); - - // Save draft mutation - const saveDraftMutation = useMutation({ - mutationFn: async (data: FormData & { rateSections: RateEntry[] }) => { - const response = await fetch(API_URLS.RATE_MATRIX.DRAFT, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }); - if (!response.ok) throw new Error('Failed to save draft'); - return response.json(); - }, - onSuccess: (data) => { - setSavedMatrixId(data.id); - queryClient.invalidateQueries({ queryKey: queryKeys.rateMatrix.all }); - toast.success('Draft saved successfully'); - }, - onError: (error) => { - toast.error('Failed to save draft'); - }, - }); - - // Submit for approval mutation - const submitMutation = useMutation({ - mutationFn: async (matrixId: string) => { - const response = await fetch(API_URLS.RATE_MATRIX.SUBMIT(matrixId), { - method: 'POST', - }); - if (!response.ok) throw new Error('Failed to submit'); - return response.json(); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: queryKeys.rateMatrix.all }); - toast.success('Rate matrix submitted for executive approval and locked!'); - setShowConfirmation(false); - }, - onError: (error) => { - toast.error('Failed to submit for approval'); - setShowConfirmation(false); - }, - }); - - const handleValidate = useCallback(() => { - const validation = rateMatrixRulesEngine.validate(rateSections); - setValidationErrors([...validation.errors, ...validation.warnings]); - - if (validation.isValid) { - toast.success('All validations passed!'); - } - }, [rateSections]); - - const handleSaveDraft = async () => { - const formData = form.getValues(); - await saveDraftMutation.mutateAsync({ - ...formData, - rateSections, - }); - }; - - const handleSubmitClick = async () => { - const isFormValid = await form.trigger(); - if (!isFormValid) return; - - const validation = rateMatrixRulesEngine.validate(rateSections); - setValidationErrors([...validation.errors, ...validation.warnings]); - - if (!validation.isValid) { - toast.error('Please fix validation errors before submitting'); - return; - } - - setShowConfirmation(true); - }; - - const handleConfirmSubmit = async () => { - const formData = form.getValues(); - - try { - let matrixId = savedMatrixId; - - if (!matrixId) { - const draftResult = await saveDraftMutation.mutateAsync({ - ...formData, - rateSections, - }); - matrixId = draftResult.id; - } - - await submitMutation.mutateAsync(matrixId!); - } catch (error) { - // Error handling done in mutations - } - }; - - if (isLoadingReference) { - return ; - } - - if (!isDirector) { - return ( -
- - - Access Denied - - Only Directors can access the rate matrix registration. - - -
- ); - } - - return ( -
- {/* Header */} -
-

- Baseline Rate Matrix Registration -

-

- Submit a comprehensive rate matrix for executive approval -

-
- - {/* Director Warning */} - - - Director Notice - - Once submitted, this matrix will be locked pending Chief Executive approval. - No edits can be made by any user until authorization is granted. - - - -
e.preventDefault()}> - {/* Matrix Metadata */} - - - Matrix Information - - -
-
- - - {form.formState.errors.matrixName && ( -

- {form.formState.errors.matrixName.message} -

- )} -
- -
- - -
- -
- - -
- -
- - -
-
-
-
- - {/* Rate Type Sections */} -
- {rateSections.map((section, index) => ( - { - const newSections = [...rateSections]; - newSections[index] = updatedSection; - setRateSections(newSections); - }} - referenceData={referenceData} - /> - ))} -
- - {/* Validation Errors */} - {validationErrors.length > 0 && ( -
- -
- )} - - {/* Form Actions */} -
- - - - - -
-
- - {/* Confirmation Dialog */} - -
- ); -} \ No newline at end of file diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/BookingRequestsHeader.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/BookingRequestsHeader.tsx index 4997f8ef8..88e522576 100644 --- a/apps/edr-freight-web/backoffice/src/components/bookings/BookingRequestsHeader.tsx +++ b/apps/edr-freight-web/backoffice/src/components/bookings/BookingRequestsHeader.tsx @@ -21,20 +21,6 @@ import type { BookingListSummaryTabs, } from "@/services/bookings.service"; -/** Lifecycle stages for the pipeline distribution bar (in flow order). */ -const PIPELINE_STAGES: Array<{ - key: keyof BookingListSummaryTabs; - label: string; - color: string; -}> = [ - { key: "intake", label: "Intake", color: "#38bdf8" }, - { key: "in_approval", label: "Approval", color: "#f59e0b" }, - { key: "approved_contract", label: "Contract", color: "#8b5cf6" }, - { key: "payment", label: "Payment", color: "#fb923c" }, - { key: "operations", label: "Operations", color: "#14b8a6" }, - { key: "completed", label: "Completed", color: "#22c55e" }, -]; - const CARD_STYLE = { background: "var(--mantine-color-gray-0)", border: "1px solid var(--mantine-color-gray-2)", @@ -62,7 +48,12 @@ export function BookingRequestsHeader({ return ( - - @@ -285,4 +323,4 @@ const FleetFormDialog = ({ ); }; -export default FleetFormDialog; \ No newline at end of file +export default FleetFormDialog; diff --git a/apps/edr-freight-web/backoffice/src/components/fleet/FleetRecordActions.tsx b/apps/edr-freight-web/backoffice/src/components/fleet/FleetRecordActions.tsx index 3ec37d092..9096f52a1 100644 --- a/apps/edr-freight-web/backoffice/src/components/fleet/FleetRecordActions.tsx +++ b/apps/edr-freight-web/backoffice/src/components/fleet/FleetRecordActions.tsx @@ -1,5 +1,5 @@ import { Edit2, Trash2, Eye, Users, MoreVertical } from "lucide-react"; -import { ActionIcon, Group, Menu, MenuItem, Tooltip } from "@mantine/core"; +import { ActionIcon, Menu, MenuItem, Tooltip } from "@mantine/core"; import { useNavigate } from "react-router-dom"; import type { FleetResourceConfig } from "@/pages/fleet/config/resources"; @@ -37,30 +37,39 @@ const FleetRecordActions = ({ - + {isVehicle && onAssignDriver ? ( - onAssignDriver(record)} leftSection={}> + onAssignDriver(record)} + leftSection={} + > Assign Driver ) : null} - onEdit(record)} leftSection={}> + onEdit(record)} + leftSection={} + > Edit {showDetail ? ( - }> + } + > View details ) : null} - onRemove(record)} leftSection={}> + onRemove(record)} + leftSection={} + > {removeLabel} @@ -72,30 +81,39 @@ const FleetRecordActions = ({ - + {isVehicle && onAssignDriver ? ( - onAssignDriver(record)} leftSection={}> + onAssignDriver(record)} + leftSection={} + > Assign Driver ) : null} - onEdit(record)} leftSection={}> + onEdit(record)} + leftSection={} + > Edit {showDetail ? ( - }> + } + > View details ) : null} - onRemove(record)} leftSection={}> + onRemove(record)} + leftSection={} + > {removeLabel} diff --git a/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineCardGrid.tsx b/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineCardGrid.tsx index 0310ca328..392bf6dfb 100644 --- a/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineCardGrid.tsx +++ b/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineCardGrid.tsx @@ -1,4 +1,4 @@ -import type { OnChangeFn, PaginationState } from "@tanstack/react-table"; +import type { OnChangeFn, PaginationState } from "@edr/ui-common"; import { Stack, Group, Text, Card, SimpleGrid, Skeleton } from "@mantine/core"; import type { RuleEngineResourceConfig } from "@/pages/ruleEngine/config/resources"; @@ -85,8 +85,15 @@ const RuleEngineCardGrid = ({ if (status === "error") { return ( - - Failed to load data + + + Failed to load data + Please refresh the page or try again later. @@ -118,8 +125,15 @@ const RuleEngineCardGrid = ({ if (status === "success" && rows.length === 0) { return ( - - {emptyMessage} + + + {emptyMessage} + Try adjusting your search or add a new record. @@ -197,7 +211,10 @@ const RuleEngineCardGrid = ({ {subtitle && ( - {presentation.subtitleKey === "stepOrder" ? "Step" : "Type"}: + {presentation.subtitleKey === "stepOrder" + ? "Step" + : "Type"} + : {subtitle} @@ -207,7 +224,12 @@ const RuleEngineCardGrid = ({ {presentation.detailColumns.map((col) => { const displayValue = getSmartValue(record, col.accessorKey); return ( - + {col.header}: @@ -220,14 +242,19 @@ const RuleEngineCardGrid = ({ )} - + {})} - onDelete={onDelete ?? (() => {})} + onEdit={onEdit ?? (() => { })} + onDelete={onDelete ?? (() => { })} onViewChain={onViewChain} onSubmitRate={onSubmitRate} onApproveRate={onApproveRate} diff --git a/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineListFooter.tsx b/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineListFooter.tsx index ea8087bd2..f53d6cb57 100644 --- a/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineListFooter.tsx +++ b/apps/edr-freight-web/backoffice/src/components/ruleEngine/RuleEngineListFooter.tsx @@ -1,4 +1,4 @@ -import type { OnChangeFn, PaginationState } from "@tanstack/react-table"; +import type { OnChangeFn, PaginationState } from "@edr/ui-common"; import { Group, Pagination, Select, Text } from "@mantine/core"; export interface RuleEngineListFooterProps { diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx index a1a00d2e9..ec6f0f7d0 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx @@ -1,12 +1,14 @@ -import { useMemo } from 'react'; -import { ActionIcon, Badge, Button, Group, Text, Tooltip } from '@mantine/core'; -import { ArrowRightLeft, ClipboardList, Coins, History } from 'lucide-react'; -import { DataTable, type ColumnDef } from '@edr/ui-common'; +import { useMemo } from "react"; +import { ActionIcon, Badge, Button, Group, Text, Tooltip } from "@mantine/core"; +import { ArrowRightLeft, ClipboardList, Coins, History } from "lucide-react"; +import { DataTable, type ColumnDef } from "@edr/ui-common"; -import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse'; -import { getNextInventoryAction } from '@/types/warehouse'; -import { InventoryStatusBadge } from './badges'; -import { formatDate, formatNumber, humanizeEnum } from './options'; +import type { + InventoryAction, + WarehouseInventoryItem, +} from "@/types/warehouse"; +import { InventoryStatusBadge } from "./badges"; +import { formatDate, formatNumber, humanizeEnum } from "./options"; interface WarehouseInventoryTableProps { items: WarehouseInventoryItem[]; @@ -27,21 +29,21 @@ interface WarehouseInventoryTableProps { } const itemKind = (item: WarehouseInventoryItem) => { - if (item.containerId) return { label: 'Container', color: 'blue' }; - if (item.cargoId) return { label: 'Cargo', color: 'grape' }; - if (item.goodsId) return { label: 'Goods', color: 'orange' }; - return { label: '—', color: 'gray' }; + if (item.containerId) return { label: "Container", color: "blue" }; + if (item.cargoId) return { label: "Cargo", color: "grape" }; + if (item.goodsId) return { label: "Goods", color: "orange" }; + return { label: "—", color: "gray" }; }; const actionColor: Record = { - store: 'blue', - reserve: 'grape', - 'ready-for-loading': 'cyan', - load: 'teal', - dispatch: 'edr-green', - 'ready-for-pickup': 'orange', - release: 'yellow', - deliver: 'green', + store: "blue", + reserve: "grape", + "ready-for-loading": "cyan", + load: "teal", + dispatch: "edr-green", + "ready-for-pickup": "orange", + release: "yellow", + deliver: "green", }; export function WarehouseInventoryTable({ @@ -62,8 +64,8 @@ export function WarehouseInventoryTable({ const columns = useMemo[]>( () => [ { - id: 'booking', - header: 'Booking', + id: "booking", + header: "Booking", cell: ({ row }) => row.original.bookingId ? ( @@ -78,16 +80,28 @@ export function WarehouseInventoryTable({ ), }, { - id: 'facility', - header: 'Facility', - cell: ({ row }) => row.original.warehouse?.facility?.name ?? '—', + id: "facility", + header: "Facility", + cell: ({ row }) => row.original.warehouse?.facility?.name ?? "—", }, - { id: 'warehouse', header: 'Warehouse', cell: ({ row }) => row.original.warehouse?.code ?? '—' }, - { id: 'yard', header: 'Yard', cell: ({ row }) => row.original.yard?.code ?? '—' }, - { id: 'zone', header: 'Zone', cell: ({ row }) => row.original.zone?.code ?? '—' }, { - id: 'item', - header: 'Item', + id: "warehouse", + header: "Warehouse", + cell: ({ row }) => row.original.warehouse?.code ?? "—", + }, + { + id: "yard", + header: "Yard", + cell: ({ row }) => row.original.yard?.code ?? "—", + }, + { + id: "zone", + header: "Zone", + cell: ({ row }) => row.original.zone?.code ?? "—", + }, + { + id: "item", + header: "Item", cell: ({ row }) => { const kind = itemKind(row.original); return ( @@ -97,27 +111,44 @@ export function WarehouseInventoryTable({ ); }, }, - { id: 'qty', header: 'Qty', cell: ({ row }) => formatNumber(row.original.quantity) }, - { id: 'weight', header: 'Weight', cell: ({ row }) => formatNumber(row.original.weight) }, { - id: 'status', - header: 'Status', - cell: ({ row }) => , + id: "qty", + header: "Qty", + cell: ({ row }) => formatNumber(row.original.quantity), }, { - id: 'arrived', - header: 'Arrived', - cell: ({ row }) => {formatDate(row.original.arrivedAt)}, + id: "weight", + header: "Weight", + cell: ({ row }) => formatNumber(row.original.weight), }, { - id: 'actions', - header: '', + id: "status", + header: "Status", + cell: ({ row }) => ( + + ), + }, + { + id: "arrived", + header: "Arrived", + cell: ({ row }) => ( + {formatDate(row.original.arrivedAt)} + ), + }, + { + id: "actions", + header: "", cell: ({ row }) => { const item = row.original; const busy = busyId === item.id; const nextAction = INVENTORY_NEXT_ACTION[item.status]; return ( - e.stopPropagation()}> + e.stopPropagation()} + > {nextAction && ( )} - {item.status !== 'DISPATCHED' && ( + {item.status !== "DISPATCHED" && ( - onMove(item)}> + onMove(item)} + > )} {onInspect && ( - onInspect(item)}> + onInspect(item)} + > )} {onFeePreview && ( - onFeePreview(item)}> + onFeePreview(item)} + > )} - onHistory(item)}> + onHistory(item)} + > diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/badges.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/badges.tsx index 1808a78bb..aebbd4de4 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/badges.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/badges.tsx @@ -1,53 +1,80 @@ -import { Badge } from '@mantine/core'; +import { Badge } from "@mantine/core"; -import type { InventoryStatus, WarehouseStatus, WarehouseType } from '@/types/warehouse'; +import type { + InventoryStatus, + WarehouseStatus, + WarehouseType, +} from "@/types/warehouse"; const humanize = (value: string) => value .toLowerCase() - .split('_') + .split("_") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) - .join(' '); + .join(" "); const badgeStyle = { - fontSize: '0.7rem', - letterSpacing: '0.04em', - whiteSpace: 'nowrap' as const, + fontSize: "0.7rem", + letterSpacing: "0.04em", + whiteSpace: "nowrap" as const, }; export function WarehouseTypeBadge({ type }: { type: WarehouseType }) { - const color = type === 'CLOSED_WAREHOUSE' ? 'indigo' : 'teal'; + const color = type === "CLOSED_WAREHOUSE" ? "indigo" : "teal"; return ( - + {humanize(type)} ); } export function WarehouseStatusBadge({ status }: { status: WarehouseStatus }) { - const color = status === 'ACTIVE' ? 'edr-green' : 'gray'; + const color = status === "ACTIVE" ? "edr-green" : "gray"; return ( - + {status} ); } const inventoryStatusColor: Record = { - UNLOADED: 'indigo', - RECEIVED: 'yellow', - STORED: 'blue', - RESERVED: 'grape', - READY_FOR_LOADING: 'cyan', - LOADED: 'teal', - DISPATCHED: 'edr-green', - DELIVERED: 'edr-green', + UNLOADED: "indigo", + RECEIVED: "yellow", + STORED: "blue", + RESERVED: "grape", + READY_FOR_LOADING: "cyan", + LOADED: "teal", + READY_FOR_PICKUP: "teal", + DISPATCHED: "edr-green", + DELIVERED: "edr-green", }; export function InventoryStatusBadge({ status }: { status: InventoryStatus }) { - const color = inventoryStatusColor[status] ?? 'gray'; + const color = inventoryStatusColor[status] ?? "gray"; return ( - + {humanize(status)} ); diff --git a/apps/edr-freight-web/backoffice/src/features/bookings/booking-actions.config.ts b/apps/edr-freight-web/backoffice/src/features/bookings/booking-actions.config.ts index cdb0d3d83..b71ad779c 100644 --- a/apps/edr-freight-web/backoffice/src/features/bookings/booking-actions.config.ts +++ b/apps/edr-freight-web/backoffice/src/features/bookings/booking-actions.config.ts @@ -55,7 +55,11 @@ export interface BookingActionDef { export type BookingActionContext = Pick< BookingDetail, - "status" | "paymentCurrency" | "approvalSteps" | "reference" | "schedulingStatus" + | "status" + | "paymentCurrency" + | "approvalSteps" + | "reference" + | "schedulingStatus" >; const ALLOCATABLE_SCHEDULING_STATUSES = new Set([ @@ -67,7 +71,9 @@ const ALLOCATABLE_SCHEDULING_STATUSES = new Set([ "", ]); -export function canAllocateBooking(booking: Pick) { +export function canAllocateBooking( + booking: Pick, +) { return ( booking.status === "PAID" && ALLOCATABLE_SCHEDULING_STATUSES.has(booking.schedulingStatus ?? undefined) @@ -286,10 +292,18 @@ export function getBookingActions( actions = [SIGN_CONTRACT_STAFF_ACTION, VIEW_CONTRACT_ACTION]; break; case "FULLY_EXECUTED": - actions = [{ ...VIEW_CONTRACT_ACTION, label: "View executed contract", primary: true }]; + actions = [ + { + ...VIEW_CONTRACT_ACTION, + label: "View executed contract", + primary: true, + }, + ]; break; case "PAID": - if (canAllocateBooking({ status, schedulingStatus: ctx.schedulingStatus })) { + if ( + canAllocateBooking({ status, schedulingStatus: ctx.schedulingStatus }) + ) { actions = [ { id: "allocateBooking", @@ -384,7 +398,7 @@ export function listRowHasActions( paymentCurrency: row.paymentCurrency, reference: "", approvalSteps: row.approvalSteps ?? undefined, - schedulingStatus: row.schedulingStatus, + schedulingStatus: row.status, }, user, ); diff --git a/apps/edr-freight-web/backoffice/src/lib/currentCustomer.ts b/apps/edr-freight-web/backoffice/src/lib/currentCustomer.ts deleted file mode 100644 index 0a68cd4f5..000000000 --- a/apps/edr-freight-web/backoffice/src/lib/currentCustomer.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { customers, type Customer } from "@/pages/customers/customers.mock"; -import { bookings, type Booking } from "@/pages/bookings/bookings.mock"; -import { - consignments, - type Consignment, -} from "@/pages/consignments/consignments.mock"; -import { - shipments, - type Shipment, -} from "@/pages/tracking/shipments.mock"; -import { invoices, type Invoice } from "@/pages/billing/invoices.mock"; - -/** - * Mock "logged-in customer". When auth integrates, replace this with the value - * pulled from `@edr/iamui-common` / the JWT context. - */ -const CURRENT_CUSTOMER_ID = 1; - -export function getCurrentCustomer(): Customer { - return ( - customers.find((c) => c.id === CURRENT_CUSTOMER_ID) ?? - (customers[0] as Customer) - ); -} - -export function getMyBookings(): Booking[] { - const me = getCurrentCustomer(); - return bookings.filter((b) => b.customerId === me.id); -} - -export function getMyConsignments(): Consignment[] { - const me = getCurrentCustomer(); - const myBookingIds = new Set(getMyBookings().map((b) => b.id)); - return consignments.filter((c) => myBookingIds.has(c.bookingId)); -} - -export function getMyShipments(): Shipment[] { - const myBookingIds = new Set(getMyBookings().map((b) => b.id)); - return shipments.filter((s) => myBookingIds.has(s.bookingId)); -} - -export function getMyInvoices(): Invoice[] { - const me = getCurrentCustomer(); - return invoices.filter((inv) => inv.customerId === me.id); -} diff --git a/apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixApproval.tsx b/apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixApproval.tsx deleted file mode 100644 index 723821936..000000000 --- a/apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixApproval.tsx +++ /dev/null @@ -1,117 +0,0 @@ -// pages/admin/rateMatrix/RateMatrixApproval.tsx -import React from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { LoadingScreen } from '@/ui/LoadingScreen'; -import { useRateMatrixAuth } from '@/auth/hooks/useAuth'; -import { queryKeys } from '../../../constants/QUERY_KEYS'; -import { API_URLS } from '@/constants/URL_CONSTANTS'; -//import { MATRIX_STATUS } from '@/constants/rateMatrixConstants'; -import { toast } from 'sonner'; -import { Navigate } from 'react-router-dom'; - -export default function RateMatrixApprovalPage() { - const { isChiefExecutive } = useRateMatrixAuth(); - const queryClient = useQueryClient(); - const pendingMatricesQueryKey = [...queryKeys.rateMatrix.all, 'pending-approval']; - - const { data: pendingMatrices, isLoading } = useQuery({ - queryKey: pendingMatricesQueryKey, - queryFn: async () => { - const response = await fetch(`${API_URLS.RATE_MATRIX.LIST}?status=pending_approval`); - return response.json(); - }, - }); - - const authorizeMutation = useMutation({ - mutationFn: async ({ matrixId, signature }: { matrixId: string; signature: string }) => { - const response = await fetch(API_URLS.RATE_MATRIX.AUTHORIZE(matrixId), { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ digitalSignature: signature }), - }); - if (!response.ok) throw new Error('Authorization failed'); - return response.json(); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: pendingMatricesQueryKey }); - toast.success('Rate matrix authorized successfully!'); - }, - onError: () => { - toast.error('Failed to authorize rate matrix'); - }, - }); - - if (!isChiefExecutive) { - return ; - } - - if (isLoading) return ; - - return ( -
-

Pending Rate Matrix Approvals

- -
- {pendingMatrices?.map((matrix: any) => ( - - - - {matrix.matrixName} - {matrix.status} - - - -
-
-
-

Effective Date

-

{matrix.effectiveDate}

-
-
-

Submitted By

-

{matrix.createdBy}

-
-
- -
-

Rate Types Included:

-
- {matrix.rateEntries?.map((entry: any) => ( - - {entry.rateType} - - ))} -
-
- -
- - -
-
-
-
- ))} -
-
- ); -} \ No newline at end of file diff --git a/apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixRegistration.tsx b/apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixRegistration.tsx deleted file mode 100644 index 6f151f992..000000000 --- a/apps/edr-freight-web/backoffice/src/pages/admin/rateMatrix/RateMatrixRegistration.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// pages/admin/rateMatrix/RateMatrixRegistration.tsx -import React from 'react'; -import { RateMatrixForm } from '@/components/baselineRatematrix/RateMatrixForm'; -import { useRateMatrixAuth } from '@/auth/useAuth'; -import { Navigate } from 'react-router-dom'; -// Local lightweight fallback for LoadingScreen to avoid import errors -const LoadingScreen: React.FC<{ message?: string }> = ({ message = 'Loading...' }) => ( -
-
{message}
-
-); - -export default function RateMatrixRegistrationPage() { - const { isDirector, isLoading } = useRateMatrixAuth(); - - if (isLoading) { - return ; - } - - if (!isDirector) { - return ; - } - - return ; -} \ No newline at end of file diff --git a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx index 481eeb407..0497f0ce8 100644 --- a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx @@ -104,7 +104,6 @@ const BookingDetailPage = () => { const approvedCount = approvalSteps.filter( (s) => s.status === "APPROVED", ).length; - const totalSteps = approvalSteps.length; return (
diff --git a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestDetailPage.tsx index 2e5b9ee0f..901226436 100644 --- a/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/bookings/BookingRequestDetailPage.tsx @@ -34,7 +34,10 @@ import { WarehouseInfoCard } from "@/components/warehouses"; import { getStatusMeta } from "@/features/bookings/booking-status.config"; import { toBookingListRow } from "@/features/bookings/mapBookingListRow"; import { downloadBookingFile } from "@/services/files.service"; -import { useBookingDetail, useBookingMutations } from "@/hooks/bookings/useBookings"; +import { + useBookingDetail, + useBookingMutations, +} from "@/hooks/bookings/useBookings"; import toast from "react-hot-toast"; // Signature / generated-contract files are surfaced on the contract page, not @@ -49,7 +52,13 @@ const SIGNATURE_FILE_CODES = new Set([ export default function BookingRequestDetailPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - const { data: booking, isLoading, isError, refetch, isFetching } = useBookingDetail(id); + const { + data: booking, + isLoading, + isError, + refetch, + isFetching, + } = useBookingDetail(id); const mutations = useBookingMutations(id ?? ""); const handleDownloadFile = async (file: BookingFileView) => { @@ -79,7 +88,13 @@ export default function BookingRequestDetailPage() { return ( - +
- navigate("/dashboard/booking-requests")} - onRefresh={() => refetch()} - isFetching={isFetching} - /> + navigate("/dashboard/booking-requests")} + onRefresh={() => refetch()} + isFetching={isFetching} + /> - + - {booking.status === "PENDING_CONSOLIDATION" && ( - - )} + {booking.status === "PENDING_CONSOLIDATION" && ( + + )} - - {/* LEFT — primary content */} - - - - - - {booking.contractSummary && ( - + + {/* LEFT — primary content */} + + + + + + {booking.contractSummary && ( + + )} + !SIGNATURE_FILE_CODES.has(f.code ?? ""), )} - !SIGNATURE_FILE_CODES.has(f.code ?? ""), - )} - onDownload={handleDownloadFile} - /> - - + onDownload={handleDownloadFile} + /> + + - {/* RIGHT — sticky action / summary rail */} - - - - - - - - {showContractButton && ( - - )} - {showApprovalCard && ( - - )} - - - - + {/* RIGHT — sticky action / summary rail */} + + + + + + + + {showContractButton && ( + + )} + {showApprovalCard && ( + + )} + + + + ); diff --git a/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/PositionTypesPage.tsx b/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/PositionTypesPage.tsx index 9895ae36a..55a4df00e 100644 --- a/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/PositionTypesPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/PositionTypesPage.tsx @@ -140,30 +140,38 @@ const PositionTypesPage = () => { const [loadingOrganizations, setLoadingOrganizations] = useState(true); const [loadingUnits, setLoadingUnits] = useState(false); const [loadingPositionTypes, setLoadingPositionTypes] = useState(false); - const [loadingPermissionsCatalog, setLoadingPermissionsCatalog] = useState(false); + const [loadingPermissionsCatalog, setLoadingPermissionsCatalog] = + useState(false); const [submitting, setSubmitting] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const [selectedPositionType, setSelectedPositionType] = useState(null); - const [positionTypePermissions, setPositionTypePermissions] = useState([]); + const [selectedPositionType, setSelectedPositionType] = + useState(null); const [allPermissions, setAllPermissions] = useState([]); const [permissionsLoading, setPermissionsLoading] = useState(false); const [permissionsError, setPermissionsError] = useState(null); const [permissionSearch, setPermissionSearch] = useState(""); - const [selectedPermissionIds, setSelectedPermissionIds] = useState([]); + const [selectedPermissionIds, setSelectedPermissionIds] = useState( + [], + ); const [isCreateOpen, setIsCreateOpen] = useState(false); const [createForm, setCreateForm] = useState(emptyCreateForm); const [createPermissionSearch, setCreatePermissionSearch] = useState(""); const [createPermissionIds, setCreatePermissionIds] = useState([]); const [createError, setCreateError] = useState(null); - const [editForm, setEditForm] = useState(emptyEditForm); + const [editForm, setEditForm] = + useState(emptyEditForm); - const isSuperAdmin = Boolean(user?.roles?.some((role) => role.key === "super_admin")); + const isSuperAdmin = Boolean( + user?.roles?.some((role) => role.key === "super_admin"), + ); const allowedOrgIds = useMemo( () => new Set( (user?.employee ?? []) .map((employee) => employee.organizationId) - .filter((organizationId): organizationId is string => Boolean(organizationId)), + .filter((organizationId): organizationId is string => + Boolean(organizationId), + ), ), [user?.employee], ); @@ -173,14 +181,21 @@ const PositionTypesPage = () => { return organizations; } - return organizations.filter((organization) => allowedOrgIds.has(organization.id)); + return organizations.filter((organization) => + allowedOrgIds.has(organization.id), + ); }, [allowedOrgIds, isSuperAdmin, organizations]); const selectedOrganization = - visibleOrganizations.find((organization) => organization.id === selectedOrgId) ?? null; + visibleOrganizations.find( + (organization) => organization.id === selectedOrgId, + ) ?? null; const selectedUnit = units.find((unit) => unit.id === selectedUnitId) ?? null; const availableCopySources = useMemo( - () => positionTypes.filter((positionType) => positionType.id !== selectedPositionType?.id), + () => + positionTypes.filter( + (positionType) => positionType.id !== selectedPositionType?.id, + ), [positionTypes, selectedPositionType?.id], ); const filteredPermissions = useMemo(() => { @@ -191,8 +206,13 @@ const PositionTypesPage = () => { return true; } - const label = getLocaleLabel(permission.name, permission.key).toLowerCase(); - return label.includes(query) || permission.key.toLowerCase().includes(query); + const label = getLocaleLabel( + permission.name, + permission.key, + ).toLowerCase(); + return ( + label.includes(query) || permission.key.toLowerCase().includes(query) + ); }); }, [allPermissions, permissionSearch]); const filteredCreatePermissions = useMemo(() => { @@ -203,18 +223,29 @@ const PositionTypesPage = () => { return true; } - const label = getLocaleLabel(permission.name, permission.key).toLowerCase(); - return label.includes(query) || permission.key.toLowerCase().includes(query); + const label = getLocaleLabel( + permission.name, + permission.key, + ).toLowerCase(); + return ( + label.includes(query) || permission.key.toLowerCase().includes(query) + ); }); }, [allPermissions, createPermissionSearch]); - const allFilteredPermissionIds = filteredPermissions.map((permission) => permission.id); - const allFilteredCreatePermissionIds = filteredCreatePermissions.map((permission) => permission.id); + const allFilteredPermissionIds = filteredPermissions.map( + (permission) => permission.id, + ); + const allFilteredCreatePermissionIds = filteredCreatePermissions.map( + (permission) => permission.id, + ); const areAllFilteredPermissionsSelected = allFilteredPermissionIds.length > 0 && allFilteredPermissionIds.every((id) => selectedPermissionIds.includes(id)); const areAllFilteredCreatePermissionsSelected = allFilteredCreatePermissionIds.length > 0 && - allFilteredCreatePermissionIds.every((id) => createPermissionIds.includes(id)); + allFilteredCreatePermissionIds.every((id) => + createPermissionIds.includes(id), + ); const loadPositionTypes = async (unitId: string) => { const response = await api.get>( @@ -247,7 +278,8 @@ const PositionTypesPage = () => { setErrorMessage(null); try { - const response = await api.get>("/organizations"); + const response = + await api.get>("/organizations"); if (!isMounted) { return; @@ -261,7 +293,7 @@ const PositionTypesPage = () => { setErrorMessage( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to load organizations." + ? (error.response?.data?.message ?? "Unable to load organizations.") : "Unable to load organizations.", ); } finally { @@ -285,12 +317,15 @@ const PositionTypesPage = () => { setLoadingPermissionsCatalog(true); try { - const response = await api.get>("/permissions", { - params: { - skip: 0, - take: 2000, + const response = await api.get>( + "/permissions", + { + params: { + skip: 0, + take: 2000, + }, }, - }); + ); if (!isMounted) { return; @@ -326,7 +361,12 @@ const PositionTypesPage = () => { return; } - if (selectedOrgId && visibleOrganizations.some((organization) => organization.id === selectedOrgId)) { + if ( + selectedOrgId && + visibleOrganizations.some( + (organization) => organization.id === selectedOrgId, + ) + ) { return; } @@ -350,7 +390,9 @@ const PositionTypesPage = () => { setPositionTypes([]); try { - const response = await api.get>(`/units/list/${selectedOrgId}`); + const response = await api.get>( + `/units/list/${selectedOrgId}`, + ); const items = getItems(response.data); if (!isMounted) { @@ -367,7 +409,7 @@ const PositionTypesPage = () => { setUnits([]); setErrorMessage( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to load units." + ? (error.response?.data?.message ?? "Unable to load units.") : "Unable to load units.", ); } finally { @@ -412,7 +454,8 @@ const PositionTypesPage = () => { setPositionTypes([]); setErrorMessage( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to load position types." + ? (error.response?.data?.message ?? + "Unable to load position types.") : "Unable to load position types.", ); } finally { @@ -431,7 +474,6 @@ const PositionTypesPage = () => { useEffect(() => { if (!selectedPositionType) { - setPositionTypePermissions([]); setSelectedPermissionIds([]); setEditForm(emptyEditForm); setPermissionsError(null); @@ -453,23 +495,24 @@ const PositionTypesPage = () => { setPermissionsError(null); try { - const items = await loadPermissionsForPositionType(selectedPositionType.id); + const items = await loadPermissionsForPositionType( + selectedPositionType.id, + ); if (!isMounted) { return; } - setPositionTypePermissions(items); setSelectedPermissionIds(items.map((permission) => permission.id)); } catch (error) { if (!isMounted) { return; } - setPositionTypePermissions([]); setPermissionsError( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to load position type permissions." + ? (error.response?.data?.message ?? + "Unable to load position type permissions.") : "Unable to load position type permissions.", ); } finally { @@ -499,13 +542,16 @@ const PositionTypesPage = () => { setPositionTypes(items); if (selectedPositionType) { - const nextSelected = items.find((item) => item.id === selectedPositionType.id) ?? selectedPositionType; + const nextSelected = + items.find((item) => item.id === selectedPositionType.id) ?? + selectedPositionType; setSelectedPositionType(nextSelected); } } catch (error) { setErrorMessage( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to refresh position types." + ? (error.response?.data?.message ?? + "Unable to refresh position types.") : "Unable to refresh position types.", ); } finally { @@ -522,7 +568,10 @@ const PositionTypesPage = () => { }; const handleSelectCopySource = async (positionTypeId: string) => { - setCreateForm((current) => ({ ...current, copyPermissionFromId: positionTypeId })); + setCreateForm((current) => ({ + ...current, + copyPermissionFromId: positionTypeId, + })); if (!positionTypeId) { setCreatePermissionIds([]); @@ -530,18 +579,23 @@ const PositionTypesPage = () => { } try { - const copiedPermissions = await loadPermissionsForPositionType(positionTypeId); - setCreatePermissionIds(copiedPermissions.map((permission) => permission.id)); + const copiedPermissions = + await loadPermissionsForPositionType(positionTypeId); + setCreatePermissionIds( + copiedPermissions.map((permission) => permission.id), + ); } catch (error) { setCreateError( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to copy permissions." + ? (error.response?.data?.message ?? "Unable to copy permissions.") : "Unable to copy permissions.", ); } }; - const handleCreatePositionType = async (event: React.FormEvent) => { + const handleCreatePositionType = async ( + event: React.FormEvent, + ) => { event.preventDefault(); if (!selectedUnitId) { @@ -576,7 +630,7 @@ const PositionTypesPage = () => { } catch (error) { setCreateError( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to create position type." + ? (error.response?.data?.message ?? "Unable to create position type.") : "Unable to create position type.", ); } finally { @@ -611,21 +665,26 @@ const PositionTypesPage = () => { const [refreshedPermissions, refreshedPositionTypes] = await Promise.all([ loadPermissionsForPositionType(selectedPositionType.id), - selectedUnitId ? loadPositionTypes(selectedUnitId) : Promise.resolve(positionTypes), + selectedUnitId + ? loadPositionTypes(selectedUnitId) + : Promise.resolve(positionTypes), ]); - setPositionTypePermissions(refreshedPermissions); - setSelectedPermissionIds(refreshedPermissions.map((permission) => permission.id)); + setSelectedPermissionIds( + refreshedPermissions.map((permission) => permission.id), + ); setPositionTypes(refreshedPositionTypes); - const refreshedSelected = refreshedPositionTypes.find((item) => item.id === selectedPositionType.id); + const refreshedSelected = refreshedPositionTypes.find( + (item) => item.id === selectedPositionType.id, + ); if (refreshedSelected) { setSelectedPositionType(refreshedSelected); } } catch (error) { setPermissionsError( isAxiosError(error) - ? error.response?.data?.message ?? "Unable to update position type." + ? (error.response?.data?.message ?? "Unable to update position type.") : "Unable to update position type.", ); } finally { @@ -633,34 +692,6 @@ const PositionTypesPage = () => { } }; - const handleSavePermissions = async () => { - if (!selectedPositionType) { - return; - } - - setSubmitting(true); - setPermissionsError(null); - - try { - await api.post("/position-type-permissions/assign-seconds-for-first", { - firstId: selectedPositionType.id, - secondIds: selectedPermissionIds, - }); - - const items = await loadPermissionsForPositionType(selectedPositionType.id); - setPositionTypePermissions(items); - setSelectedPermissionIds(items.map((permission) => permission.id)); - } catch (error) { - setPermissionsError( - isAxiosError(error) - ? error.response?.data?.message ?? "Unable to update position type permissions." - : "Unable to update position type permissions.", - ); - } finally { - setSubmitting(false); - } - }; - return (
@@ -670,9 +701,12 @@ const PositionTypesPage = () => {

-

Position Type

+

+ Position Type +

- Browse position types for a selected organization unit, add new ones, and manage their permissions. + Browse position types for a selected organization unit, add new + ones, and manage their permissions.

@@ -712,7 +746,13 @@ const PositionTypesPage = () => { disabled={loadingOrganizations || !visibleOrganizations.length} > - + {visibleOrganizations.map((organization) => ( @@ -734,7 +774,11 @@ const PositionTypesPage = () => { disabled={!selectedOrgId || loadingUnits || !units.length} > - + {units.map((unit) => ( @@ -800,7 +844,9 @@ const PositionTypesPage = () => { {getLocaleLabel(positionType.name, positionType.key)} - {positionType.key} + + {positionType.key} + {positionType.isSystem ? "System" : "Unit"} @@ -833,7 +879,10 @@ const PositionTypesPage = () => { {selectedPositionType - ? getLocaleLabel(selectedPositionType.name, selectedPositionType.key) + ? getLocaleLabel( + selectedPositionType.name, + selectedPositionType.key, + ) : "Position type details"} @@ -851,7 +900,10 @@ const PositionTypesPage = () => { Position type

- {getLocaleLabel(selectedPositionType.name, selectedPositionType.key)} + {getLocaleLabel( + selectedPositionType.name, + selectedPositionType.key, + )}

@@ -883,23 +935,33 @@ const PositionTypesPage = () => {
{selectedPositionType.isSystem ? (

- System position types keep their name and key, but you can still manage permissions here. + System position types keep their name and key, but you can + still manage permissions here.

) : null}
-

Permissions

+

+ Permissions +

{selectedPermissionIds.length} permissions selected
@@ -942,7 +1010,9 @@ const PositionTypesPage = () => { setPermissionSearch(event.target.value)} + onChange={(event) => + setPermissionSearch(event.target.value) + } placeholder="Search permissions by name or key" /> @@ -960,7 +1030,9 @@ const PositionTypesPage = () => { ); }} /> - Select all + + Select all + {loadingPermissionsCatalog ? ( @@ -976,20 +1048,29 @@ const PositionTypesPage = () => { > { setSelectedPermissionIds((current) => event.target.checked ? [...current, permission.id] - : current.filter((item) => item !== permission.id), + : current.filter( + (item) => item !== permission.id, + ), ); }} />
- {getLocaleLabel(permission.name, permission.key)} + {getLocaleLabel( + permission.name, + permission.key, + )} +
+
+ {permission.key}
-
{permission.key}
))} @@ -1030,30 +1111,44 @@ const PositionTypesPage = () => { Create position type - Add a new position type for the selected unit and optionally copy permissions from an existing one. + Add a new position type for the selected unit and optionally copy + permissions from an existing one. -
void handleCreatePositionType(event)}> + void handleCreatePositionType(event)} + >
@@ -1064,13 +1159,18 @@ const PositionTypesPage = () => { className={inputClassName} value={createForm.key} onChange={(event) => - setCreateForm((current) => ({ ...current, key: event.target.value })) + setCreateForm((current) => ({ + ...current, + key: event.target.value, + })) } /> ))} @@ -1163,7 +1271,8 @@ const PositionTypesPage = () => {
- The new position type will inherit permissions from the selected source. + The new position type will inherit permissions from the + selected source.
) : null} diff --git a/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/iamConfig.ts b/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/iamConfig.ts index 923efad0e..ec27b5c76 100644 --- a/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/iamConfig.ts +++ b/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/iamConfig.ts @@ -2,7 +2,6 @@ import type { DesignConfig } from "@tria-plc/iamui"; import { FREIGHT_BRAND, - FREIGHT_BRAND_DARK, FREIGHT_BRAND_LIGHT, freightBrand, } from "@/theme/freight-brand"; @@ -48,7 +47,7 @@ export const iamConfig: DesignConfig = { }, layout: { userManagementView: "classic", - showTopBar: true, + showTopBar: true as any, sidebarWidth: "280px", sidebarCollapsedWidth: "80px", headerHeight: "80px", diff --git a/apps/edr-freight-web/backoffice/src/pages/documents/EditFileUploadSettingDialog.tsx b/apps/edr-freight-web/backoffice/src/pages/documents/EditFileUploadSettingDialog.tsx index 4370adac1..af69d4991 100644 --- a/apps/edr-freight-web/backoffice/src/pages/documents/EditFileUploadSettingDialog.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/documents/EditFileUploadSettingDialog.tsx @@ -17,15 +17,7 @@ import { Textarea } from "@/components/ui/textarea"; import { FileUploadEntity } from "@edr/types/freight"; import { useMutation } from "@tanstack/react-query"; import { api } from "@/services/api"; - -// import type { -// FileUploadEntity, -// FileUploadSetting, -// } from "@/types/fileUploadSettings"; -// import { -// useCreateFileUploadSetting, -// useUpdateFileUploadSetting, -// } from "@/hooks/useFileUploadSettings"; +import { FileUploadSetting } from "@/types/fileUploadSettings"; export interface EditFileUploadSettingDialogProps { mode?: "create" | "edit"; @@ -33,19 +25,6 @@ export interface EditFileUploadSettingDialogProps { children: ReactNode; } -const selectClass = - "flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700 shadow-xs outline-none transition hover:border-slate-300 focus:border-[#10B981]/50 focus:ring-2 focus:ring-[#10B981]/20"; - -// const ENTITIES: FileUploadEntity[] = [ -// "customer", -// "booking", -// "consignment", -// "shipment", -// "invoice", -// "train", -// "other", -// ]; - export default function EditFileUploadSettingDialog({ mode = "create", setting, @@ -62,8 +41,12 @@ export default function EditFileUploadSettingDialog({ const [description, setDescription] = useState(setting?.description ?? ""); const [error, setError] = useState(null); - const createMutation = useMutation(api.fileUploadSettings.create.mutationOptions()); - const updateMutation = useMutation(api.fileUploadSettings.update.mutationOptions()); + const createMutation = useMutation( + api.fileUploadSettings.create.mutationOptions(), + ); + const updateMutation = useMutation( + api.fileUploadSettings.update.mutationOptions(), + ); const pending = createMutation.isPending || updateMutation.isPending; const reset = () => { diff --git a/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx index f68ac32f4..d3e4387b0 100644 --- a/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx @@ -3,6 +3,7 @@ import { Box, Card, Group, + Badge, Badge as MantineBadge, Select, Stack, @@ -28,7 +29,6 @@ import { KpiStrip, PageContainer, PageHeader } from "@/components/page"; import { api } from "@/services/api"; import type { PaymentMethod, PaymentRow } from "@/services/payments.service"; import { - Badge, DataTable, DataTableFooter, usePagination, @@ -36,7 +36,12 @@ import { } from "@edr/ui-common"; const STATUS_TABS = [ - { key: "all", label: "All", statuses: undefined as string | undefined, icon: LayoutGrid }, + { + key: "all", + label: "All", + statuses: undefined as string | undefined, + icon: LayoutGrid, + }, { key: "success", label: "Success", statuses: "success", icon: CheckCircle2 }, { key: "processing", @@ -44,7 +49,12 @@ const STATUS_TABS = [ statuses: "processing,action-required", icon: Loader2, }, - { key: "failed", label: "Failed", statuses: "failed,canceled", icon: XCircle }, + { + key: "failed", + label: "Failed", + statuses: "failed,canceled", + icon: XCircle, + }, { key: "refunded", label: "Refunded", statuses: "refunded", icon: RotateCcw }, ] as const; @@ -81,13 +91,14 @@ function formatDate(iso: string | null): string { return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString(undefined, { - year: "numeric", - month: "short", - day: "numeric", - }); + year: "numeric", + month: "short", + day: "numeric", + }); } -const tableHeader = "text-xs font-semibold uppercase tracking-wide text-muted-foreground"; +const tableHeader = + "text-xs font-semibold uppercase tracking-wide text-muted-foreground"; export default function PaymentsPage() { const { pagination, setPagination } = usePagination({ pageSize: 10 }); @@ -124,9 +135,9 @@ export default function PaymentsPage() { summary === undefined ? undefined : (summary.success ?? 0) + - (summary.processing ?? 0) + - (summary.failed ?? 0) + - (summary.refunded ?? 0), + (summary.processing ?? 0) + + (summary.failed ?? 0) + + (summary.refunded ?? 0), success: summary?.success, processing: summary?.processing, failed: summary?.failed, @@ -285,7 +296,10 @@ export default function PaymentsPage() { value={query} onChange={(e) => { setQuery(e.target.value); - setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); + setPagination({ + pageIndex: 0, + pageSize: pagination.pageSize, + }); }} rightSection={ query && ( @@ -309,7 +323,10 @@ export default function PaymentsPage() { value={method} onChange={(value) => { setMethod(value); - setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); + setPagination({ + pageIndex: 0, + pageSize: pagination.pageSize, + }); }} style={{ minWidth: 180 }} /> diff --git a/apps/edr-freight-web/backoffice/src/pages/ruleEngine/RuleEngineResourcePage.tsx b/apps/edr-freight-web/backoffice/src/pages/ruleEngine/RuleEngineResourcePage.tsx index 783c0ffd4..ffb752a56 100644 --- a/apps/edr-freight-web/backoffice/src/pages/ruleEngine/RuleEngineResourcePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/ruleEngine/RuleEngineResourcePage.tsx @@ -2,8 +2,18 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Navigate, useLocation, useParams } from "react-router-dom"; import { useAuth } from "@/auth/useAuth"; import { canAccessRuleEngineResource } from "@/lib/permissions"; -import type { ColumnDef } from "@tanstack/react-table"; -import { Box, Card, Button, Modal, Stack, Group, Text, List, Loader } from "@mantine/core"; +import type { ColumnDef } from "@edr/ui-common"; +import { + Box, + Card, + Button, + Modal, + Stack, + Group, + Text, + List, + Loader, +} from "@mantine/core"; import { Plus } from "lucide-react"; import { PageContainer, PageHeader } from "@/components/page"; @@ -60,16 +70,17 @@ const RuleEngineResourcePage = () => { const config = resourceSlug ? getRuleEngineResource(resourceSlug) : undefined; const defaultPath = category - ? `${RULE_ENGINE_CATEGORY_BASE_PATH[category]}/${ - category === "rules" ? DEFAULT_RULES_SLUG : DEFAULT_CONFIGURATION_SLUG - }` + ? `${RULE_ENGINE_CATEGORY_BASE_PATH[category]}/${category === "rules" ? DEFAULT_RULES_SLUG : DEFAULT_CONFIGURATION_SLUG + }` : `${RULE_ENGINE_CATEGORY_BASE_PATH.configuration}/${DEFAULT_CONFIGURATION_SLUG}`; const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [search, setSearch] = useState(""); const [formOpen, setFormOpen] = useState(false); const [editing, setEditing] = useState(null); - const [deleteTarget, setDeleteTarget] = useState(null); + const [deleteTarget, setDeleteTarget] = useState( + null, + ); const [chainOpen, setChainOpen] = useState(false); const [orderDialogOpen, setOrderDialogOpen] = useState(false); const { viewMode, setViewMode } = useRuleEngineViewMode( @@ -90,9 +101,9 @@ const RuleEngineResourcePage = () => { pageSize: pagination.pageSize, ...(config?.orderConfig ? { - sortBy: config.orderConfig.field, - sortOrder: "ASC" as const, - } + sortBy: config.orderConfig.field, + sortOrder: "ASC" as const, + } : {}), }), [ @@ -120,11 +131,12 @@ const RuleEngineResourcePage = () => { const { reorder, moveOrder } = useRuleEngineOrderMutations( config?.slug ?? DEFAULT_CONFIGURATION_SLUG, ); - const { data: orderListData, isLoading: orderListLoading } = useRuleEngineOrderList( - config?.slug ?? DEFAULT_CONFIGURATION_SLUG, - Boolean(orderDialogOpen && config?.orderConfig), - config?.orderConfig?.field, - ); + const { data: orderListData, isLoading: orderListLoading } = + useRuleEngineOrderList( + config?.slug ?? DEFAULT_CONFIGURATION_SLUG, + Boolean(orderDialogOpen && config?.orderConfig), + config?.orderConfig?.field, + ); const { submit, approve } = useRateWorkflow(); const { data: chainData, isLoading: chainLoading } = useApprovalChain( chainOpen && config?.slug === "approval-rules", @@ -141,10 +153,7 @@ const RuleEngineResourcePage = () => { const { data: cargoParentOptions, isLoading: cargoParentOptionsLoading } = useCargoTypeParentOptions(editingId, config?.slug === "cargo-types"); const { data: containerTypeOptions, isLoading: containerTypeOptionsLoading } = - useContainerTypeOptions( - config?.slug === "rates", - usesContainerTypeField, - ); + useContainerTypeOptions(config?.slug === "rates", usesContainerTypeField); const { data: liveRateOptions, isLoading: liveRateOptionsLoading } = useLiveRateOptions(usesLiveRateField); @@ -154,10 +163,9 @@ const RuleEngineResourcePage = () => { if (config.slug === "cargo-types" && field.name === "parentGroupId") { return { ...field, - options: - cargoParentOptions ?? [ - { label: "None", value: RULE_ENGINE_SELECT_NONE }, - ], + options: cargoParentOptions ?? [ + { label: "None", value: RULE_ENGINE_SELECT_NONE }, + ], }; } if (field.name === "containerTypeId") { @@ -183,14 +191,16 @@ const RuleEngineResourcePage = () => { const pageCount = meta?.totalPages ?? 1; const totalCount = meta?.total ?? rows.length; - const { data: createPositionList, isLoading: createPositionLoading } = useRuleEngineOrderList( - config?.slug ?? DEFAULT_CONFIGURATION_SLUG, - Boolean(formOpen && !editing && config?.orderConfig), - config?.orderConfig?.field, - ); + const { data: createPositionList, isLoading: createPositionLoading } = + useRuleEngineOrderList( + config?.slug ?? DEFAULT_CONFIGURATION_SLUG, + Boolean(formOpen && !editing && config?.orderConfig), + config?.orderConfig?.field, + ); const createPositionOptions = useMemo(() => { - if (!config?.orderConfig || !createPositionList?.data?.length) return undefined; + if (!config?.orderConfig || !createPositionList?.data?.length) + return undefined; return createPositionList.data .filter((row) => row.id) .map((row) => ({ @@ -199,7 +209,6 @@ const RuleEngineResourcePage = () => { })); }, [config?.orderConfig, config?.slug, createPositionList?.data]); - const handleApproveRate = useCallback( (record: RuleEngineRecord) => { approve.mutate(String(record.id)); @@ -259,7 +268,9 @@ const RuleEngineResourcePage = () => { }} onDelete={setDeleteTarget} onViewChain={ - config.slug === "approval-rules" ? () => setChainOpen(true) : undefined + config.slug === "approval-rules" + ? () => setChainOpen(true) + : undefined } onSubmitRate={canManage ? (id) => submit.mutate(id) : undefined} onApproveRate={canManage ? handleApproveRate : undefined} @@ -270,7 +281,15 @@ const RuleEngineResourcePage = () => { }); return base; - }, [canManage, config, submit, handleApproveRate, handleMoveOrder, moveOrder.isPending, totalCount]); + }, [ + canManage, + config, + submit, + handleApproveRate, + handleMoveOrder, + moveOrder.isPending, + totalCount, + ]); const tableStatus = isLoading ? "loading" : isError ? "error" : "success"; @@ -350,15 +369,20 @@ const RuleEngineResourcePage = () => { onSearchChange={ config.supportsSearch ? (v) => { - setSearch(v); - setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); - } + setSearch(v); + setPagination({ + pageIndex: 0, + pageSize: pagination.pageSize, + }); + } : undefined } showSearch={Boolean(config.supportsSearch)} searchPlaceholder={config.searchPlaceholder} onManageOrder={ - canManage && config.orderConfig ? () => setOrderDialogOpen(true) : undefined + canManage && config.orderConfig + ? () => setOrderDialogOpen(true) + : undefined } viewMode={viewMode} onViewModeChange={setViewMode} @@ -373,10 +397,12 @@ const RuleEngineResourcePage = () => { error={ isError ? { - message: "Failed to load data", - description: - error instanceof Error ? error.message : "Unknown error", - } + message: "Failed to load data", + description: + error instanceof Error + ? error.message + : "Unknown error", + } : undefined } emptyMessage={`No ${itemLabel} found.`} @@ -422,7 +448,9 @@ const RuleEngineResourcePage = () => { onEdit={canManage ? openEdit : undefined} onDelete={canManage ? setDeleteTarget : undefined} onViewChain={ - config.slug === "approval-rules" ? () => setChainOpen(true) : undefined + config.slug === "approval-rules" + ? () => setChainOpen(true) + : undefined } onSubmitRate={canManage ? (id) => submit.mutate(id) : undefined} onApproveRate={canManage ? handleApproveRate : undefined} @@ -434,7 +462,11 @@ const RuleEngineResourcePage = () => { { > - This will soft-delete the selected {config.label.toLowerCase()} record. + This will soft-delete the selected {config.label.toLowerCase()}{" "} + record. - - {data.trainNumber ?? data.routeName ?? "Schedule"} - - - {data.status} - - - - }> - {data.scheduleDate - ? new Intl.DateTimeFormat("en-GB", { + + + + + + {data.trainNumber ?? data.routeName ?? "Schedule"} + + + {data.status} + + + + }> + {data.scheduleDate + ? new Intl.DateTimeFormat("en-GB", { weekday: "short", day: "2-digit", month: "short", @@ -653,317 +693,360 @@ export default function BatchScheduleDetailPage() { hour12: false, timeZone: "Africa/Addis_Ababa", }).format(new Date(data.scheduleDate)) + " EAT" - : "No date"} - - {data.locomotive ? ( - }> - Loco {data.locomotive.code} · {fmtTons(data.locomotive.maxPullWeightTons)} ·{" "} - {data.locomotive.maxTrainLengthMeters} m + : "No date"} - ) : null} - - - - - - - - - - - {!data.locomotive ? ( - }> - No locomotive assigned — wagon allocation cannot run. - - ) : null} - - - - {/* Booking pipeline */} - - - - - - - Booking pipeline - - - {totalBookings} booking{totalBookings === 1 ? "" : "s"} - - - - - - {data.allocationViolations.length ? ( - } - title="Allocation constraints" - > - - {data.allocationViolations.map((v) => ( - - {v} - - ))} - - - ) : null} - - {/* Batch windows */} - - - - - - - Batch windows (EAT) - - 3-hour windows for every day from when the booking window opened through the - departure date. Bookings appear under the date their contract was signed — open a - day to see its windows. - - - - - {dayGroups.length && selectedDay ? ( - <> - {/* Date stepper — page back/forward through each day in the range */} - - setSelectedDate(dayGroups[selectedIndex - 1]?.date ?? null)} - > - - - - - - - - {selectedDay.dateLabel} - - {selectedDay.date === todayEat ? ( - - Today - + {data.locomotive ? ( + }> + Loco {data.locomotive.code} ·{" "} + {fmtTons(data.locomotive.maxPullWeightTons)} ·{" "} + {data.locomotive.maxTrainLengthMeters} m + ) : null} - - {selectedDay.totalBookings - ? `${selectedDay.totalBookings} booking${selectedDay.totalBookings === 1 ? "" : "s"} · ${selectedDay.windows.length} windows` - : `${selectedDay.windows.length} windows · no bookings`} - - + - = dayGroups.length - 1} - onClick={() => setSelectedDate(dayGroups[selectedIndex + 1]?.date ?? null)} - > - - - - - - - Day {selectedIndex + 1} of {dayGroups.length} - - - {selectedDay.hasIssues ? ( - } - > - Issues - - ) : null} - + + + + - - {selectedDay.windows.map((window) => ( - - ))} - - - ) : ( - - No batch windows for this schedule. - - )} + {!data.locomotive ? ( + }> + No locomotive assigned — wagon allocation cannot run. + + ) : null} - {data.pendingContract.bookings.length ? ( - - - - - - + + {/* Booking pipeline */} + + + + + + + Booking pipeline + + + {totalBookings} booking{totalBookings === 1 ? "" : "s"} + + + + + + {data.allocationViolations.length ? ( + } + title="Allocation constraints" + > + + {data.allocationViolations.map((v) => ( + + {v} + + ))} + + + ) : null} + + {/* Batch windows */} + + + + + + + Batch windows (EAT) + + 3-hour windows for every day from when the booking window + opened through the departure date. Bookings appear under the + date their contract was signed — open a day to see its + windows. + + + + + {dayGroups.length && selectedDay ? ( + <> + {/* Date stepper — page back/forward through each day in the range */} + + + setSelectedDate( + dayGroups[selectedIndex - 1]?.date ?? null, + ) + } + > + + + + - - - - - Pending contract + + + + {selectedDay.dateLabel} + + {selectedDay.date === todayEat ? ( + + Today + + ) : null} + + + {selectedDay.totalBookings + ? `${selectedDay.totalBookings} booking${selectedDay.totalBookings === 1 ? "" : "s"} · ${selectedDay.windows.length} windows` + : `${selectedDay.windows.length} windows · no bookings`} - - Contract not signed yet — not in any window - - - - - {data.pendingContract.bookings.length} booking - {data.pendingContract.bookings.length === 1 ? "" : "s"} - - - - - - - - - ) : null} - + - {/* Train composition diagram */} - {hasAssignedWagons && scheduleDetailQuery.data ? ( - - - - ) : null} + = dayGroups.length - 1} + onClick={() => + setSelectedDate( + dayGroups[selectedIndex + 1]?.date ?? null, + ) + } + > + + + + + + + Day {selectedIndex + 1} of {dayGroups.length} + + + {selectedDay.hasIssues ? ( + } + > + Issues + + ) : null} + + + + + + {selectedDay.windows.map((window) => ( + + ))} + + + ) : ( + + No batch windows for this schedule. + + )} + + {data.pendingContract.bookings.length ? ( + + + + + + + + + + + Pending contract + + + Contract not signed yet — not in any window + + + + + {data.pendingContract.bookings.length} booking + {data.pendingContract.bookings.length === 1 + ? "" + : "s"} + + + + + + + + + ) : null} + + + {/* Train composition diagram */} + {hasAssignedWagons && scheduleDetailQuery.data ? ( + + + + ) : null} diff --git a/apps/edr-freight-web/backoffice/src/services/ruleEngine/ruleEngine.service.ts b/apps/edr-freight-web/backoffice/src/services/ruleEngine/ruleEngine.service.ts index 9f092f46d..7cab23f64 100644 --- a/apps/edr-freight-web/backoffice/src/services/ruleEngine/ruleEngine.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/ruleEngine/ruleEngine.service.ts @@ -67,7 +67,11 @@ const byIdPath = (resource: RuleEngineResourceSlug, id: string): string => { } }; -const defaultMeta = (dataLength: number, page = 1, pageSize = 10): RuleEngineListMeta => ({ +const defaultMeta = ( + dataLength: number, + page = 1, + pageSize = 10, +): RuleEngineListMeta => ({ total: dataLength, page, pageSize, @@ -79,7 +83,7 @@ const isPaginatedListResult = ( ): value is RuleEngineListResult => Boolean(value) && typeof value === "object" && - "data" in value && + "data" in (value ?? {}) && Array.isArray((value as RuleEngineListResult).data); const normalizeList = ( @@ -104,7 +108,10 @@ const normalizeList = ( } if (Array.isArray(body)) { - return { data: body as T[], meta: defaultMeta(body.length, page, pageSize) }; + return { + data: body as T[], + meta: defaultMeta(body.length, page, pageSize), + }; } return { data: [], meta: defaultMeta(0, page, pageSize) }; @@ -161,7 +168,10 @@ export const ruleEngineService = { return normalizeEntity(response.data); }, - remove: async (resource: RuleEngineResourceSlug, id: string): Promise => { + remove: async ( + resource: RuleEngineResourceSlug, + id: string, + ): Promise => { await client.delete(byIdPath(resource, id)); }, @@ -181,24 +191,36 @@ export const ruleEngineService = { }, submitRate: async (id: string): Promise => { - const response = await client.post(URL_CONSTANTS.RULE_ENGINE.RATE_SUBMIT(id)); + const response = await client.post( + URL_CONSTANTS.RULE_ENGINE.RATE_SUBMIT(id), + ); return normalizeEntity(response.data); }, approveRate: async (id: string): Promise => { - const response = await client.post(URL_CONSTANTS.RULE_ENGINE.RATE_APPROVE(id)); + const response = await client.post( + URL_CONSTANTS.RULE_ENGINE.RATE_APPROVE(id), + ); return normalizeEntity(response.data); }, getApprovalChain: async ( requiresDirectorApproval = true, ): Promise => { - const response = await client.get(URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES_CHAIN, { - params: { requiresDirectorApproval }, - }); + const response = await client.get( + URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES_CHAIN, + { + params: { requiresDirectorApproval }, + }, + ); const body = unwrap(response.data) as unknown; if (Array.isArray(body)) return body as RuleEngineRecord[]; - if (body && typeof body === "object" && "data" in body && Array.isArray((body as { data: unknown }).data)) { + if ( + body && + typeof body === "object" && + "data" in body && + Array.isArray((body as { data: unknown }).data) + ) { return (body as { data: RuleEngineRecord[] }).data; } return []; diff --git a/apps/edr-freight-web/backoffice/src/services/trainScheduling.service.ts b/apps/edr-freight-web/backoffice/src/services/trainScheduling.service.ts index 63929fd2e..2c8669137 100644 --- a/apps/edr-freight-web/backoffice/src/services/trainScheduling.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/trainScheduling.service.ts @@ -1,13 +1,12 @@ -import { api as client } from '../auth/http'; -import { unwrap } from '@/utils/endpoint'; -import { URL_CONSTANTS } from '@/constants/URLS'; +import { api as client } from "../auth/http"; +import { unwrap } from "@/utils/endpoint"; +import { URL_CONSTANTS } from "@/constants/URLS"; import type { BatchBoardSchedule, BatchBoardScheduleDetail, BookableSchedule, AssignBookingsPayload, CompositionRemovalEntry, - CompositionUnassignedBooking, UnassignedBookingsResponse, CreateTrainSchedulePayload, EligibleContainerBookingsResponse, @@ -24,7 +23,7 @@ import type { TrainTrackResponse, WagonAllocationAttemptResult, YardOption, -} from '@/types/trainScheduling'; +} from "@/types/trainScheduling"; interface BookingReferenceDataResponse { yard?: Array; @@ -58,7 +57,9 @@ export const trainSchedulingService = { ): Promise => { const useUnified = !freightType || freightType === "MIXED"; const response = await client.post( - useUnified ? URL_CONSTANTS.TRAIN_SCHEDULING.PREVIEW : pathsFor(freightType).PREVIEW, + useUnified + ? URL_CONSTANTS.TRAIN_SCHEDULING.PREVIEW + : pathsFor(freightType).PREVIEW, payload, ); return unwrap(response.data); @@ -91,7 +92,9 @@ export const trainSchedulingService = { return unwrap(response.data); }, - getBatchBoardDetail: async (scheduleId: string): Promise => { + getBatchBoardDetail: async ( + scheduleId: string, + ): Promise => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD_DETAIL(scheduleId), ); @@ -132,7 +135,9 @@ export const trainSchedulingService = { return unwrap(response.data); }, - runAllocation: async (scheduleId: string): Promise => { + runAllocation: async ( + scheduleId: string, + ): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.RUN_ALLOCATION(scheduleId), {}, @@ -152,20 +157,29 @@ export const trainSchedulingService = { }, markBookingPaid: async (bookingId: string): Promise => { - await client.post(URL_CONSTANTS.TRAIN_SCHEDULING.MARK_BOOKING_PAID(bookingId), {}); + await client.post( + URL_CONSTANTS.TRAIN_SCHEDULING.MARK_BOOKING_PAID(bookingId), + {}, + ); }, expireBooking: async (bookingId: string): Promise => { - await client.post(URL_CONSTANTS.TRAIN_SCHEDULING.EXPIRE_BOOKING(bookingId), {}); + await client.post( + URL_CONSTANTS.TRAIN_SCHEDULING.EXPIRE_BOOKING(bookingId), + {}, + ); }, moveBookingSchedule: async ( bookingId: string, trainScheduleId: string, ): Promise => { - await client.post(URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_BOOKING_SCHEDULE(bookingId), { - trainScheduleId, - }); + await client.post( + URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_BOOKING_SCHEDULE(bookingId), + { + trainScheduleId, + }, + ); }, getScheduleById: async ( @@ -173,7 +187,9 @@ export const trainSchedulingService = { freightType?: FreightType, ): Promise => { const response = await client.get( - pathsFor(freightType === "MIXED" ? undefined : freightType).SCHEDULE_BY_ID(id), + pathsFor( + freightType === "MIXED" ? undefined : freightType, + ).SCHEDULE_BY_ID(id), ); return unwrap(response.data); }, @@ -225,7 +241,9 @@ export const trainSchedulingService = { return unwrap(response.data); }, - finalizeSchedule: async (scheduleId: string): Promise => { + finalizeSchedule: async ( + scheduleId: string, + ): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.FINALIZE(scheduleId), {}, @@ -233,7 +251,9 @@ export const trainSchedulingService = { return unwrap(response.data); }, - dispatchSchedule: async (scheduleId: string): Promise => { + dispatchSchedule: async ( + scheduleId: string, + ): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.DISPATCH(scheduleId), {}, @@ -272,13 +292,17 @@ export const trainSchedulingService = { freightType: FreightType = "CONTAINER", ): Promise => { const response = await client.post( - pathsFor(freightType === "MIXED" ? undefined : freightType).CANCEL_SCHEDULE(id), + pathsFor( + freightType === "MIXED" ? undefined : freightType, + ).CANCEL_SCHEDULE(id), {}, ); return unwrap(response.data); }, - getAvailableLocomotives: async (routeId?: string): Promise => { + getAvailableLocomotives: async ( + routeId?: string, + ): Promise => { if (routeId) { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_LOCOMOTIVES, @@ -286,9 +310,12 @@ export const trainSchedulingService = { ); return unwrap(response.data); } - const response = await client.get(URL_CONSTANTS.LOCOMOTIVES.BASE, { - params: { status: 'AVAILABLE' }, - }); + const response = await client.get( + URL_CONSTANTS.LOCOMOTIVES.BASE, + { + params: { status: "AVAILABLE" }, + }, + ); return unwrap(response.data); }, @@ -380,7 +407,10 @@ export const trainSchedulingService = { })); }, - removeWagonSlot: async (scheduleId: string, wagonId: string): Promise => { + removeWagonSlot: async ( + scheduleId: string, + wagonId: string, + ): Promise => { const response = await client.delete( URL_CONSTANTS.TRAIN_SCHEDULING.REMOVE_WAGON_SLOT(scheduleId, wagonId), ); @@ -392,7 +422,10 @@ export const trainSchedulingService = { itemId: string, payload: { containerNumber: string | null }, ): Promise<{ id: string; containerNumber: string | null }> => { - const response = await client.patch<{ id: string; containerNumber: string | null }>( + const response = await client.patch<{ + id: string; + containerNumber: string | null; + }>( URL_CONSTANTS.TRAIN_SCHEDULING.UPDATE_CONTAINER_ITEM(scheduleId, itemId), payload, ); diff --git a/apps/edr-freight-web/backoffice/src/types/@tria-plc__iamui.d.ts b/apps/edr-freight-web/backoffice/src/types/@tria-plc__iamui.d.ts new file mode 100644 index 000000000..d0bdfe618 --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/types/@tria-plc__iamui.d.ts @@ -0,0 +1,55 @@ +declare module "@tria-plc/iamui" { + import type { ComponentType } from "react"; + + export interface DesignConfig { + brand: { appName: string; logoUrl: string }; + colors: Record; + typography: { + fontFamily: string; + headingFontFamily: string; + baseFontSize: string; + fontWeight: string; + }; + shape: { radius: string }; + shadows: Record; + components: { + buttonDefaultVariant?: string; + inputDefaultSize?: string; + inputRadius?: string; + modalRadius?: string; + tableHighlightOnHover?: boolean; + }; + layout: Record>; + appearance: { + colorScheme: string; + slots: Record }>; + customCss: string; + }; + } + + export interface UserManagementRuntimeOptions { + basename: string; + apiBaseUrl: string; + apiUrl: string; + recordApiUrl: string; + chronicleUrl: string; + auditApiUrl: string; + } + + export interface UserManagementSessionSeed { + token: string; + refreshToken?: string; + rememberMe: boolean; + } + + export interface UserManagementAppProps { + config: DesignConfig; + runtime: UserManagementRuntimeOptions; + session: { + initialSession: UserManagementSessionSeed | null; + enableEmbeddedAuthBridge: boolean; + }; + } + + export const UserManagementApp: ComponentType; +} diff --git a/apps/edr-freight-web/backoffice/src/types/trainScheduling.ts b/apps/edr-freight-web/backoffice/src/types/trainScheduling.ts index 3195281b2..805e48132 100644 --- a/apps/edr-freight-web/backoffice/src/types/trainScheduling.ts +++ b/apps/edr-freight-web/backoffice/src/types/trainScheduling.ts @@ -146,14 +146,12 @@ export interface TrainScheduleListItem { origin: string | null; destination: string | null; freightType?: FreightType | null; - locomotive: - | { - id: string; - code: string; - name?: string | null; - currentYardId?: string | null; - } - | null; + locomotive: { + id: string; + code: string; + name?: string | null; + currentYardId?: string | null; + } | null; wagonCount: number; totalWeightTons: number; totalLengthMeters: number; @@ -316,7 +314,6 @@ export interface TrainScheduleWagonAllocation { export interface TrainScheduleDetail { id: string; status: TrainScheduleStatus | string; - warnings?: string[]; deferredBookings?: DeferredBookingRow[]; freightType?: FreightType | null; trainNumber?: string | null; diff --git a/apps/edr-freight-web/backoffice/tsconfig.app.json b/apps/edr-freight-web/backoffice/tsconfig.app.json index 9c7064567..079051c3d 100644 --- a/apps/edr-freight-web/backoffice/tsconfig.app.json +++ b/apps/edr-freight-web/backoffice/tsconfig.app.json @@ -5,7 +5,8 @@ "useDefineForClassFields": true, "skipLibCheck": true, "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@tria-plc/iamui": ["./src/types/@tria-plc__iamui.d.ts"] } }, "include": ["src"]