mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
feat: add invoice pages and api to the backoffice
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
PackageCheck,
|
||||
PackageOpen,
|
||||
Paperclip,
|
||||
Receipt,
|
||||
Send,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
@@ -32,7 +33,11 @@ import {
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
|
||||
import { FreightDashboardLayout, type SidebarItem, type SidebarSection } from "@/components/layout";
|
||||
import {
|
||||
FreightDashboardLayout,
|
||||
type SidebarItem,
|
||||
type SidebarSection,
|
||||
} from "@/components/layout";
|
||||
import { useAuth } from "./auth/useAuth";
|
||||
import LoadingScreen from "./components/LoadingScreen";
|
||||
import LoginPage from "./pages/auth/LoginPage";
|
||||
@@ -53,6 +58,8 @@ import GlCreateBookingForm from "./components/contracts/GlCreateBookingForm";
|
||||
import DocumentClearanceDetailPage from "./pages/bookings/DocumentClearanceDetailPage";
|
||||
import CustomerDetailPage from "./pages/customers/CustomerDetailPage";
|
||||
import CustomersPage from "./pages/customers/CustomersPage";
|
||||
import InvoiceDetailPage from "./pages/invoices/InvoiceDetailPage";
|
||||
import InvoicesPage from "./pages/invoices/InvoicesPage";
|
||||
import DemoUser1Page from "./pages/dashboard/demo/DemoUser1Page";
|
||||
import DemoUser2Page from "./pages/dashboard/demo/DemoUser2Page";
|
||||
import MyProfilePage from "./pages/dashboard/MyProfilePage";
|
||||
@@ -61,7 +68,10 @@ import UserManagementHostPage from "./pages/dashboard/user-management/UserManage
|
||||
import PaymentsPage from "./pages/payments/PaymentsPage";
|
||||
//import EmployeesPage from "./pages/dashboard/user-management/EmployeesPage";
|
||||
import { RequirePermission } from "./components/auth/RequirePermission";
|
||||
import { FREIGHT_PERMS, hasPermission as hasFreightPermission } from "./lib/permissions";
|
||||
import {
|
||||
FREIGHT_PERMS,
|
||||
hasPermission as hasFreightPermission,
|
||||
} from "./lib/permissions";
|
||||
import PermissionsPage from "./pages/dashboard/user-management/PermissionsPage";
|
||||
import PositionTypesPage from "./pages/dashboard/user-management/PositionTypesPage";
|
||||
import RolesPage from "./pages/dashboard/user-management/RolesPage";
|
||||
@@ -144,6 +154,12 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
|
||||
icon: <Wallet />,
|
||||
permission: FREIGHT_PERMS.bookings.view,
|
||||
},
|
||||
{
|
||||
label: "Invoices",
|
||||
href: "/dashboard/invoices",
|
||||
icon: <Receipt />,
|
||||
permission: FREIGHT_PERMS.bookings.view,
|
||||
},
|
||||
...demoItems,
|
||||
],
|
||||
},
|
||||
@@ -506,7 +522,10 @@ const App = () => {
|
||||
<Route path="/um/*" element={<UserManagementHostPage />} />
|
||||
<Route path="/health" element={<HealthCheck />} />
|
||||
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
|
||||
<Route path="/dashboard" element={<Navigate to="/dashboard/overview" replace />} />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={<Navigate to="/dashboard/overview" replace />}
|
||||
/>
|
||||
<Route path="/dashboard" element={<DashboardShell />}>
|
||||
<Route path="overview" element={<OverviewPage />} />
|
||||
<Route path="profile" element={<MyProfilePage />} />
|
||||
@@ -522,8 +541,27 @@ const App = () => {
|
||||
/>
|
||||
<Route path="customers" element={<CustomersPage />} />
|
||||
<Route path="customers/:id" element={<CustomerDetailPage />} />
|
||||
<Route
|
||||
path="invoices"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
|
||||
<InvoicesPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="invoices/:id"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
|
||||
<InvoiceDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route path="booking-requests/new" element={<NewBookingPage />} />
|
||||
<Route path="booking-requests/:id" element={<BookingRequestDetailPage />} />
|
||||
<Route
|
||||
path="booking-requests/:id"
|
||||
element={<BookingRequestDetailPage />}
|
||||
/>
|
||||
<Route
|
||||
path="booking-requests/:id/contract"
|
||||
element={<BookingContractPage />}
|
||||
@@ -536,7 +574,9 @@ const App = () => {
|
||||
<Route
|
||||
path="clearance/:id"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.bookings.clearanceView}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.bookings.clearanceView}
|
||||
>
|
||||
<DocumentClearanceDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -570,7 +610,9 @@ const App = () => {
|
||||
<Route
|
||||
path="bookings/:bookingId/clearance"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.bookings.clearanceView}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.bookings.clearanceView}
|
||||
>
|
||||
<DocumentClearanceDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -578,7 +620,9 @@ const App = () => {
|
||||
<Route
|
||||
path="shipment-requests"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.contracts.createBooking}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.contracts.createBooking}
|
||||
>
|
||||
<ShipmentRequestsPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -586,7 +630,9 @@ const App = () => {
|
||||
<Route
|
||||
path="shipment-requests/:id"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.contracts.createBooking}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.contracts.createBooking}
|
||||
>
|
||||
<ShipmentRequestDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -618,12 +664,20 @@ const App = () => {
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route path="gl-ethiopia/clearance" element={<LegacyGlEthiopiaClearanceRedirect />} />
|
||||
<Route path="gl-ethiopia/clearance/:id" element={<LegacyGlEthiopiaClearanceRedirect />} />
|
||||
<Route
|
||||
path="gl-ethiopia/clearance"
|
||||
element={<LegacyGlEthiopiaClearanceRedirect />}
|
||||
/>
|
||||
<Route
|
||||
path="gl-ethiopia/clearance/:id"
|
||||
element={<LegacyGlEthiopiaClearanceRedirect />}
|
||||
/>
|
||||
<Route
|
||||
path="gl-djibouti/clearance"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.contracts.clearanceDjActions}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.contracts.clearanceDjActions}
|
||||
>
|
||||
<GlDjiboutiClearanceListPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -631,7 +685,9 @@ const App = () => {
|
||||
<Route
|
||||
path="gl-djibouti/clearance/:id"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.contracts.clearanceDjActions}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.contracts.clearanceDjActions}
|
||||
>
|
||||
<GlClearanceDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -644,7 +700,9 @@ const App = () => {
|
||||
<Route
|
||||
path="contracts/:id/create-booking"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.contracts.createBooking}>
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.contracts.createBooking}
|
||||
>
|
||||
<GlCreateBookingForm />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -655,155 +713,174 @@ const App = () => {
|
||||
/>
|
||||
<Route path="warehouses" element={<WarehouseListPage />} />
|
||||
<Route path="warehouses/:id" element={<WarehouseDetailPage />} />
|
||||
<Route path="warehouse-inventory" element={<WarehouseInventoryPage />} />
|
||||
<Route
|
||||
path="warehouse-inventory"
|
||||
element={<WarehouseInventoryPage />}
|
||||
/>
|
||||
<Route path="import-warehouse" element={<ImportWarehouseFlowPage />} />
|
||||
<Route path="export-warehouse" element={<ExportWarehouseFlowPage />} />
|
||||
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
|
||||
<Route path="loading-queue" element={<LoadingQueuePage />} />
|
||||
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
|
||||
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
|
||||
<Route path="export-djibouti-unloading" element={<ExportDjiboutiUnloadingQueuePage />} />
|
||||
<Route path="interchange-documents" element={<InterchangeDocumentsPage />} />
|
||||
<Route
|
||||
path="export-djibouti-unloading"
|
||||
element={<ExportDjiboutiUnloadingQueuePage />}
|
||||
/>
|
||||
<Route
|
||||
path="interchange-documents"
|
||||
element={<InterchangeDocumentsPage />}
|
||||
/>
|
||||
<Route path="inventory-inquiry" element={<InventoryInquiryPage />} />
|
||||
<Route path="warehouse-rules" element={<WarehouseRulesPage />} />
|
||||
<Route path="warehouse-fee-invoices" element={<WarehouseInvoicesPage />} />
|
||||
<Route path="warehouse-dashboard" element={<WarehouseDashboardPage />} />
|
||||
<Route
|
||||
path="warehouse-fee-invoices"
|
||||
element={<WarehouseInvoicesPage />}
|
||||
/>
|
||||
<Route
|
||||
path="warehouse-dashboard"
|
||||
element={<WarehouseDashboardPage />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="operations/train-scheduling"
|
||||
element={<Navigate to="/dashboard/operations/train-scheduling-v2" replace />}
|
||||
/>
|
||||
<Route
|
||||
path="operations/batch-board"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<BatchBoardPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/first-mile"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<FirstMilePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/last-mile"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<LastMilePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/batch-board/:scheduleId"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<BatchScheduleDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling-v2"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<TrainScheduleV2ListPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling-v2/:scheduleId"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<TrainScheduleV2DetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling-v2/:scheduleId/track"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<TrainScheduleTrackPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="routes"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<RoutesPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="locomotives"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="trains"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="trains/:id"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<TrainDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="wagons"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="containers"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="cargoes"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="vehicles"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="drivers"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling"
|
||||
element={<Navigate to="/dashboard/operations/train-scheduling-v2" replace />}
|
||||
element={
|
||||
<Navigate to="/dashboard/operations/train-scheduling-v2" replace />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/batch-board"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<BatchBoardPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/first-mile"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<FirstMilePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/last-mile"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<LastMilePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/batch-board/:scheduleId"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<BatchScheduleDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling-v2"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<TrainScheduleV2ListPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling-v2/:scheduleId"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<TrainScheduleV2DetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling-v2/:scheduleId/track"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
||||
<TrainScheduleTrackPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="routes"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<RoutesPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="locomotives"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="trains"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="trains/:id"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<TrainDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="wagons"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="containers"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="cargoes"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="vehicles"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="drivers"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
||||
<FleetResourcePage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/train-scheduling"
|
||||
element={
|
||||
<Navigate to="/dashboard/operations/train-scheduling-v2" replace />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="operations/batch-board"
|
||||
@@ -953,9 +1030,15 @@ const App = () => {
|
||||
{/* Legacy embedded user management routes */}
|
||||
<Route path="user-management" element={<UserManagementPage />} />
|
||||
<Route path="user-management/users" element={<UsersPage />} />
|
||||
<Route path="user-management/position-types" element={<PositionTypesPage />} />
|
||||
<Route
|
||||
path="user-management/position-types"
|
||||
element={<PositionTypesPage />}
|
||||
/>
|
||||
{/* <Route path="user-management/employees" element={<EmployeesPage />} /> */}
|
||||
<Route path="user-management/permissions" element={<PermissionsPage />} />
|
||||
<Route
|
||||
path="user-management/permissions"
|
||||
element={<PermissionsPage />}
|
||||
/>
|
||||
<Route path="user-management/roles" element={<RolesPage />} />
|
||||
|
||||
<Route
|
||||
@@ -977,7 +1060,9 @@ const App = () => {
|
||||
|
||||
<Route
|
||||
path="configuration"
|
||||
element={<Navigate to="/dashboard/configuration/cargo-types" replace />}
|
||||
element={
|
||||
<Navigate to="/dashboard/configuration/cargo-types" replace />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="configuration/train-scheduling-rules"
|
||||
@@ -996,8 +1081,14 @@ const App = () => {
|
||||
}
|
||||
/>
|
||||
<Route path="configuration/cargo-types" element={<CargoTypesPage />} />
|
||||
<Route path="configuration/cargo-types/:id" element={<CargoTypesPage />} />
|
||||
<Route path="configuration/:resource" element={<RuleEngineResourcePage />} />
|
||||
<Route
|
||||
path="configuration/cargo-types/:id"
|
||||
element={<CargoTypesPage />}
|
||||
/>
|
||||
<Route
|
||||
path="configuration/:resource"
|
||||
element={<RuleEngineResourcePage />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="rules"
|
||||
@@ -1007,9 +1098,14 @@ const App = () => {
|
||||
|
||||
<Route
|
||||
path="rule-engine"
|
||||
element={<Navigate to="/dashboard/configuration/cargo-types" replace />}
|
||||
element={
|
||||
<Navigate to="/dashboard/configuration/cargo-types" replace />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="rule-engine/:resource"
|
||||
element={<RuleEngineLegacyRedirect />}
|
||||
/>
|
||||
<Route path="rule-engine/:resource" element={<RuleEngineLegacyRedirect />} />
|
||||
|
||||
<Route path="user1" element={<DemoUser1Page />} />
|
||||
<Route path="user2" element={<DemoUser2Page />} />
|
||||
@@ -1026,9 +1122,7 @@ const App = () => {
|
||||
/** Redirect removed milestones page to document clearance. */
|
||||
function BookingMilestonesRedirect() {
|
||||
const { id } = useParams();
|
||||
return (
|
||||
<Navigate to={`/dashboard/bookings/${id}/clearance`} replace />
|
||||
);
|
||||
return <Navigate to={`/dashboard/bookings/${id}/clearance`} replace />;
|
||||
}
|
||||
|
||||
/** Redirect legacy GL Ethiopia clearance URLs to the unified document clearance hub. */
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { BookingListFilter } from "@/services/bookings.service";
|
||||
import type { ContractListFilter } from "@/services/contracts.service";
|
||||
import type { RuleEngineListParams } from "@/services/ruleEngine/ruleEngine.service";
|
||||
import type { CompanyListFilter } from "@/types/customer";
|
||||
import type { InvoiceListFilter } from "@/types/invoice";
|
||||
import type { RuleEngineResourceSlug } from "@/types/rule-engine";
|
||||
import type { TrainScheduleFilters } from "@/types/trainScheduling";
|
||||
|
||||
@@ -16,7 +17,8 @@ export const QUERY_KEYS = {
|
||||
ROOT: ["file-upload-settings"] as const,
|
||||
list: () => ["file-upload-settings", "list"] as const,
|
||||
byId: (id: string) => ["file-upload-settings", "detail", id] as const,
|
||||
byCode: (code: string) => ["file-upload-settings", "by-code", code] as const,
|
||||
byCode: (code: string) =>
|
||||
["file-upload-settings", "by-code", code] as const,
|
||||
},
|
||||
|
||||
DROPDOWN_SETTINGS: {
|
||||
@@ -33,10 +35,18 @@ export const QUERY_KEYS = {
|
||||
["customers", "list", filter ?? {}] as const,
|
||||
byId: (id: string) => ["customers", "detail", id] as const,
|
||||
bookings: (id: string) => ["customers", "detail", id, "bookings"] as const,
|
||||
documents: (id: string) => ["customers", "detail", id, "documents"] as const,
|
||||
documents: (id: string) =>
|
||||
["customers", "detail", id, "documents"] as const,
|
||||
payments: (id: string) => ["customers", "detail", id, "payments"] as const,
|
||||
},
|
||||
|
||||
INVOICES: {
|
||||
ROOT: ["invoices"] as const,
|
||||
list: (filter?: InvoiceListFilter) =>
|
||||
["invoices", "list", filter ?? {}] as const,
|
||||
byId: (id: string) => ["invoices", "detail", id] as const,
|
||||
},
|
||||
|
||||
BOOKINGS: {
|
||||
ROOT: ["bookings"] as const,
|
||||
list: (filter?: BookingListFilter) =>
|
||||
@@ -79,7 +89,12 @@ export const QUERY_KEYS = {
|
||||
TRAIN_SCHEDULING: {
|
||||
ROOT: ["train-scheduling"] as const,
|
||||
eligible: (freightType?: string, filters?: TrainScheduleFilters) =>
|
||||
["train-scheduling", "eligible-bookings", freightType ?? "CONTAINER", filters ?? {}] as const,
|
||||
[
|
||||
"train-scheduling",
|
||||
"eligible-bookings",
|
||||
freightType ?? "CONTAINER",
|
||||
filters ?? {},
|
||||
] as const,
|
||||
locomotives: (routeId?: string) =>
|
||||
["train-scheduling", "locomotives", routeId ?? "all"] as const,
|
||||
stations: () => ["train-scheduling", "stations"] as const,
|
||||
@@ -97,31 +112,37 @@ export const QUERY_KEYS = {
|
||||
|
||||
FLEET: {
|
||||
ROOT: ["fleet"] as const,
|
||||
list: (resource: FleetResourceSlug | string) => ["fleet", "list", resource] as const,
|
||||
list: (resource: FleetResourceSlug | string) =>
|
||||
["fleet", "list", resource] as const,
|
||||
},
|
||||
|
||||
VEHICLES: {
|
||||
ROOT: ["vehicles"] as const,
|
||||
list: (filter?: Record<string, unknown>) => ["vehicles", "list", filter ?? {}] as const,
|
||||
list: (filter?: Record<string, unknown>) =>
|
||||
["vehicles", "list", filter ?? {}] as const,
|
||||
byId: (id: string) => ["vehicles", "detail", id] as const,
|
||||
},
|
||||
|
||||
FIRST_MILE: {
|
||||
ROOT: ["first-mile"] as const,
|
||||
list: (filter?: Record<string, unknown>) => ["first-mile", "list", filter ?? {}] as const,
|
||||
list: (filter?: Record<string, unknown>) =>
|
||||
["first-mile", "list", filter ?? {}] as const,
|
||||
byId: (id: string) => ["first-mile", "detail", id] as const,
|
||||
},
|
||||
|
||||
LAST_MILE: {
|
||||
ROOT: ["last-mile"] as const,
|
||||
list: (filter?: Record<string, unknown>) => ["last-mile", "list", filter ?? {}] as const,
|
||||
list: (filter?: Record<string, unknown>) =>
|
||||
["last-mile", "list", filter ?? {}] as const,
|
||||
byId: (id: string) => ["last-mile", "detail", id] as const,
|
||||
},
|
||||
|
||||
RULE_ENGINE: {
|
||||
ROOT: ["rule-engine"] as const,
|
||||
list: (resource: RuleEngineResourceSlug | string, params?: RuleEngineListParams) =>
|
||||
["rule-engine", "list", resource, params ?? {}] as const,
|
||||
list: (
|
||||
resource: RuleEngineResourceSlug | string,
|
||||
params?: RuleEngineListParams,
|
||||
) => ["rule-engine", "list", resource, params ?? {}] as const,
|
||||
detail: (resource: RuleEngineResourceSlug | string, id: string) =>
|
||||
["rule-engine", "detail", resource, id] as const,
|
||||
chain: ["rule-engine", "approval-rules", "chain"] as const,
|
||||
@@ -135,27 +156,39 @@ export const QUERY_KEYS = {
|
||||
|
||||
OVERVIEW: {
|
||||
ROOT: ["overview"] as const,
|
||||
dashboard: (range?: string) => ["overview", "dashboard", range ?? "30d"] as const,
|
||||
bookingsTab: (range?: string) => ["overview", "bookings", range ?? "30d"] as const,
|
||||
contractsTab: (range?: string) => ["overview", "contracts", range ?? "30d"] as const,
|
||||
billingTab: (range?: string) => ["overview", "billing", range ?? "30d"] as const,
|
||||
dashboard: (range?: string) =>
|
||||
["overview", "dashboard", range ?? "30d"] as const,
|
||||
bookingsTab: (range?: string) =>
|
||||
["overview", "bookings", range ?? "30d"] as const,
|
||||
contractsTab: (range?: string) =>
|
||||
["overview", "contracts", range ?? "30d"] as const,
|
||||
billingTab: (range?: string) =>
|
||||
["overview", "billing", range ?? "30d"] as const,
|
||||
operationsTab: () => ["overview", "operations"] as const,
|
||||
customersTab: (range?: string) => ["overview", "customers", range ?? "30d"] as const,
|
||||
staffTab: (range?: string) => ["overview", "staff", range ?? "30d"] as const,
|
||||
customersTab: (range?: string) =>
|
||||
["overview", "customers", range ?? "30d"] as const,
|
||||
staffTab: (range?: string) =>
|
||||
["overview", "staff", range ?? "30d"] as const,
|
||||
},
|
||||
|
||||
FUEL: {
|
||||
ROOT: ["fuel"] as const,
|
||||
purchases: (vehicleId?: string) => ["fuel", "purchases", vehicleId ?? "all"] as const,
|
||||
stats: (vehicleId?: string) => ["fuel", "stats", vehicleId ?? "all"] as const,
|
||||
purchases: (vehicleId?: string) =>
|
||||
["fuel", "purchases", vehicleId ?? "all"] as const,
|
||||
stats: (vehicleId?: string) =>
|
||||
["fuel", "stats", vehicleId ?? "all"] as const,
|
||||
},
|
||||
|
||||
MAINTENANCE: {
|
||||
ROOT: ["maintenance"] as const,
|
||||
schedules: (vehicleId?: string) => ["maintenance", "schedules", vehicleId ?? "all"] as const,
|
||||
upcoming: (vehicleId?: string) => ["maintenance", "upcoming", vehicleId ?? "all"] as const,
|
||||
history: (vehicleId?: string) => ["maintenance", "history", vehicleId ?? "all"] as const,
|
||||
stats: (vehicleId?: string) => ["maintenance", "stats", vehicleId ?? "all"] as const,
|
||||
schedules: (vehicleId?: string) =>
|
||||
["maintenance", "schedules", vehicleId ?? "all"] as const,
|
||||
upcoming: (vehicleId?: string) =>
|
||||
["maintenance", "upcoming", vehicleId ?? "all"] as const,
|
||||
history: (vehicleId?: string) =>
|
||||
["maintenance", "history", vehicleId ?? "all"] as const,
|
||||
stats: (vehicleId?: string) =>
|
||||
["maintenance", "stats", vehicleId ?? "all"] as const,
|
||||
},
|
||||
|
||||
FINANCIAL_REPORTS: {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const URL_CONSTANTS = {
|
||||
BASE: "/users",
|
||||
BY_ID: (id: string | number) => `/users/${id}`,
|
||||
SET_PASSWORD: "/api/auth/set-password",
|
||||
ME: "/api/auth/me"
|
||||
ME: "/api/auth/me",
|
||||
},
|
||||
|
||||
ROLES: {
|
||||
@@ -74,9 +74,18 @@ export const URL_CONSTANTS = {
|
||||
STATS: "/companies/stats",
|
||||
BY_ID: (id: string | number) => `/companies/${id}`,
|
||||
DOCUMENTS: (id: string) => `/companies/${id}/documents`,
|
||||
PROFILE_STATUS: (profileId: string) => `/companies/company-profiles/${profileId}/status`,
|
||||
BOOKINGS_CUSTOMER_VIEW: (id: string) => `/bookings/by-company/${id}/customer-view`,
|
||||
PAYMENTS_CUSTOMER_VIEW: (id: string) => `/payments/by-company/${id}/customer-view`,
|
||||
PROFILE_STATUS: (profileId: string) =>
|
||||
`/companies/company-profiles/${profileId}/status`,
|
||||
BOOKINGS_CUSTOMER_VIEW: (id: string) =>
|
||||
`/bookings/by-company/${id}/customer-view`,
|
||||
PAYMENTS_CUSTOMER_VIEW: (id: string) =>
|
||||
`/payments/by-company/${id}/customer-view`,
|
||||
},
|
||||
|
||||
BILLING: {
|
||||
INVOICES: "/billing/invoices",
|
||||
INVOICE_BY_ID: (id: string) => `/billing/invoices/${id}`,
|
||||
INVOICE_DOCUMENT: (id: string) => `/billing/invoices/${id}/document`,
|
||||
},
|
||||
|
||||
CUSTOMERS_API: {
|
||||
@@ -124,15 +133,21 @@ export const URL_CONSTANTS = {
|
||||
CANCEL: (id: string) => `/bookings/${id}/cancel`,
|
||||
CONSOLIDATION: (id: string) => `/bookings/${id}/consolidation`,
|
||||
CLEARANCE: (id: string) => `/bookings/${id}/clearance`,
|
||||
CLEARANCE_DECLARATION: (id: string) => `/bookings/${id}/clearance/declaration`,
|
||||
CLEARANCE_DECLARATION: (id: string) =>
|
||||
`/bookings/${id}/clearance/declaration`,
|
||||
CLEARANCE_DUTY: (id: string) => `/bookings/${id}/clearance/duty`,
|
||||
CLEARANCE_FINALIZE_PRE: (id: string) =>
|
||||
`/bookings/${id}/clearance/finalize-pre-clearance`,
|
||||
CLEARANCE_TRANSIT_PERMIT: (id: string) => `/bookings/${id}/clearance/transit-permit`,
|
||||
CLEARANCE_DELIVERY_ORDER: (id: string) => `/bookings/${id}/clearance/delivery-order`,
|
||||
CLEARANCE_RELEASE_ORDER: (id: string) => `/bookings/${id}/clearance/release-order`,
|
||||
CLEARANCE_RO_AMENDMENT: (id: string) => `/bookings/${id}/clearance/ro-amendment`,
|
||||
CLEARANCE_EXPORT_RELEASE: (id: string) => `/bookings/${id}/clearance/export-release`,
|
||||
CLEARANCE_TRANSIT_PERMIT: (id: string) =>
|
||||
`/bookings/${id}/clearance/transit-permit`,
|
||||
CLEARANCE_DELIVERY_ORDER: (id: string) =>
|
||||
`/bookings/${id}/clearance/delivery-order`,
|
||||
CLEARANCE_RELEASE_ORDER: (id: string) =>
|
||||
`/bookings/${id}/clearance/release-order`,
|
||||
CLEARANCE_RO_AMENDMENT: (id: string) =>
|
||||
`/bookings/${id}/clearance/ro-amendment`,
|
||||
CLEARANCE_EXPORT_RELEASE: (id: string) =>
|
||||
`/bookings/${id}/clearance/export-release`,
|
||||
CLEARANCE_ET_QUEUE: "/bookings/clearance/et-queue",
|
||||
CLEARANCE_DJ_QUEUE: "/bookings/clearance/dj-queue",
|
||||
},
|
||||
@@ -157,7 +172,8 @@ export const URL_CONSTANTS = {
|
||||
CLEARANCE_OUTPUT_DOCUMENTS: (id: string) =>
|
||||
`/contracts/${id}/clearance/output-documents`,
|
||||
CLEARANCE_FINALIZE: (id: string) => `/contracts/${id}/clearance/finalize`,
|
||||
CLEARANCE_DECLARATION: (id: string) => `/contracts/${id}/clearance/declaration`,
|
||||
CLEARANCE_DECLARATION: (id: string) =>
|
||||
`/contracts/${id}/clearance/declaration`,
|
||||
CLEARANCE_DUTY: (id: string) => `/contracts/${id}/clearance/duty`,
|
||||
CLEARANCE_DUTY_SLIP: (id: string) => `/contracts/${id}/clearance/duty-slip`,
|
||||
CLEARANCE_FINALIZE_PRE: (id: string) =>
|
||||
@@ -232,7 +248,7 @@ export const URL_CONSTANTS = {
|
||||
},
|
||||
|
||||
ROUTES: {
|
||||
BASE: '/routes',
|
||||
BASE: "/routes",
|
||||
BY_ID: (id: string) => `/routes/${id}`,
|
||||
},
|
||||
|
||||
@@ -246,10 +262,12 @@ export const URL_CONSTANTS = {
|
||||
BATCH_BOARD_DETAIL: (scheduleId: string) =>
|
||||
`/train-scheduling/batch-board/${scheduleId}`,
|
||||
RUN_BATCH: (id: string) => `/train-scheduling/schedules/${id}/run-batch`,
|
||||
RUN_ALLOCATION: (id: string) => `/train-scheduling/schedules/${id}/run-allocation`,
|
||||
RUN_ALLOCATION: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/run-allocation`,
|
||||
ASSIGN_UNASSIGNED_BOOKING: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/assign-unassigned-booking`,
|
||||
BOOKING_WINDOW: (id: string) => `/train-scheduling/schedules/${id}/booking-window`,
|
||||
BOOKING_WINDOW: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/booking-window`,
|
||||
MARK_BOOKING_PAID: (bookingId: string) =>
|
||||
`/train-scheduling/bookings/${bookingId}/mark-paid`,
|
||||
EXPIRE_BOOKING: (bookingId: string) =>
|
||||
@@ -258,12 +276,14 @@ export const URL_CONSTANTS = {
|
||||
`/train-scheduling/bookings/${bookingId}/move-schedule`,
|
||||
GLOBAL_RULES: "/train-scheduling/global-rules",
|
||||
PREVIEW: "/train-scheduling/preview",
|
||||
ASSIGN_BOOKINGS: (id: string) => `/train-scheduling/schedules/${id}/assign-bookings`,
|
||||
ASSIGN_BOOKINGS: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/assign-bookings`,
|
||||
CONTAINER: {
|
||||
ELIGIBLE_BOOKINGS: "/train-scheduling/container/eligible-bookings",
|
||||
PREVIEW: "/train-scheduling/container/preview",
|
||||
SCHEDULES: "/train-scheduling/container/schedules",
|
||||
SCHEDULE_BY_ID: (id: string) => `/train-scheduling/container/schedules/${id}`,
|
||||
SCHEDULE_BY_ID: (id: string) =>
|
||||
`/train-scheduling/container/schedules/${id}`,
|
||||
ASSIGN_BOOKINGS: (id: string) =>
|
||||
`/train-scheduling/container/schedules/${id}/assign-bookings`,
|
||||
CANCEL_SCHEDULE: (id: string) =>
|
||||
@@ -302,15 +322,18 @@ export const URL_CONSTANTS = {
|
||||
`/train-scheduling/schedules/${id}/import-djibouti/load-list/document`,
|
||||
EXPORT_LOAD_LIST_DOCUMENT: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/export/load-list/document`,
|
||||
CHECKPOINTS: (id: string) => `/train-scheduling/schedules/${id}/checkpoints`,
|
||||
CHECKPOINTS: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/checkpoints`,
|
||||
ARRIVE: (id: string) => `/train-scheduling/schedules/${id}/arrive`,
|
||||
RESCHEDULE_PREVIEW: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/reschedule/preview`,
|
||||
RESCHEDULE_EXECUTE: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/reschedule/execute`,
|
||||
MAINTENANCE: (id: string) => `/train-scheduling/schedules/${id}/maintenance`,
|
||||
MAINTENANCE: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/maintenance`,
|
||||
SCHEDULES: "/train-scheduling/container/schedules",
|
||||
SCHEDULE_BY_ID: (id: string) => `/train-scheduling/container/schedules/${id}`,
|
||||
SCHEDULE_BY_ID: (id: string) =>
|
||||
`/train-scheduling/container/schedules/${id}`,
|
||||
CANCEL_SCHEDULE: (id: string) =>
|
||||
`/train-scheduling/container/schedules/${id}/cancel`,
|
||||
REMOVE_WAGON_SLOT: (scheduleId: string, wagonId: string) =>
|
||||
@@ -358,175 +381,193 @@ export const URL_CONSTANTS = {
|
||||
APPROVAL_RULE_BY_ID: (id: string) => `/approval-rules/${id}`,
|
||||
APPROVAL_RULES_CHAIN: "/approval-rules/chain",
|
||||
},
|
||||
RATE_MATRIX: {
|
||||
BASE: '/api/rate-matrices',
|
||||
DRAFT: '/api/rate-matrices/draft',
|
||||
RATE_MATRIX: {
|
||||
BASE: "/api/rate-matrices",
|
||||
DRAFT: "/api/rate-matrices/draft",
|
||||
SUBMIT: (id: string) => `/api/rate-matrices/${id}/submit`,
|
||||
AUTHORIZE: (id: string) => `/api/rate-matrices/${id}/authorize`,
|
||||
LIST: '/api/rate-matrices',
|
||||
LIST: "/api/rate-matrices",
|
||||
DETAIL: (id: string) => `/api/rate-matrices/${id}`,
|
||||
},
|
||||
REFERENCE: {
|
||||
PORTS: '/api/reference/ports',
|
||||
CITIES: '/api/reference/cities',
|
||||
CONTAINER_TYPES: '/api/reference/container-types',
|
||||
CURRENCIES: '/api/reference/currencies',
|
||||
PORTS: "/api/reference/ports",
|
||||
CITIES: "/api/reference/cities",
|
||||
CONTAINER_TYPES: "/api/reference/container-types",
|
||||
CURRENCIES: "/api/reference/currencies",
|
||||
},
|
||||
|
||||
FACILITIES: {
|
||||
BASE: '/facilities',
|
||||
BASE: "/facilities",
|
||||
BY_ID: (id: string) => `/facilities/${id}`,
|
||||
},
|
||||
|
||||
WAREHOUSES: {
|
||||
BASE: '/warehouses',
|
||||
DASHBOARD: '/warehouses/dashboard',
|
||||
BASE: "/warehouses",
|
||||
DASHBOARD: "/warehouses/dashboard",
|
||||
BY_ID: (id: string) => `/warehouses/${id}`,
|
||||
YARDS: (warehouseId: string) => `/warehouses/${warehouseId}/yards`,
|
||||
},
|
||||
|
||||
WAREHOUSE_YARDS: {
|
||||
BASE: '/warehouse-yards',
|
||||
BASE: "/warehouse-yards",
|
||||
BY_ID: (id: string) => `/warehouse-yards/${id}`,
|
||||
ZONES: (yardId: string) => `/warehouse-yards/${yardId}/zones`,
|
||||
},
|
||||
|
||||
WAREHOUSE_ZONES: {
|
||||
BASE: '/warehouse-zones',
|
||||
BASE: "/warehouse-zones",
|
||||
BY_ID: (id: string) => `/warehouse-zones/${id}`,
|
||||
},
|
||||
|
||||
WAREHOUSE_INVENTORY: {
|
||||
BASE: '/warehouse-inventory',
|
||||
RECEIVE: '/warehouse-inventory/receive',
|
||||
DASHBOARD_SUMMARY: '/warehouse-inventory/dashboard/summary',
|
||||
READY_FOR_LOADING: '/warehouse-inventory/ready-for-loading',
|
||||
INQUIRY: '/warehouse-inventory/inquiry',
|
||||
BASE: "/warehouse-inventory",
|
||||
RECEIVE: "/warehouse-inventory/receive",
|
||||
DASHBOARD_SUMMARY: "/warehouse-inventory/dashboard/summary",
|
||||
READY_FOR_LOADING: "/warehouse-inventory/ready-for-loading",
|
||||
INQUIRY: "/warehouse-inventory/inquiry",
|
||||
STORE: (id: string) => `/warehouse-inventory/${id}/store`,
|
||||
INSPECT: (id: string) => `/warehouse-inventory/${id}/inspect`,
|
||||
MARK_READY: (id: string) => `/warehouse-inventory/${id}/ready-for-loading`,
|
||||
LOAD: (id: string) => `/warehouse-inventory/${id}/load`,
|
||||
DISPATCH: (id: string) => `/warehouse-inventory/${id}/dispatch`,
|
||||
MOVE: (id: string) => `/warehouse-inventory/${id}/move`,
|
||||
RESERVE: '/warehouse-inventory/reserve',
|
||||
ARRIVAL_QUEUE: '/warehouse-inventory/arrival-queue',
|
||||
AUTO_UNLOAD_ARRIVED: '/warehouse-inventory/auto-unload-arrived',
|
||||
AUTO_LOAD_READY: '/warehouse-inventory/auto-load-ready',
|
||||
UNLOAD_BOOKING: (bookingId: string) => `/warehouse-inventory/bookings/${bookingId}/unload`,
|
||||
INSPECTION_REPORTS: (inventoryId: string) => `/warehouse-inventory/${inventoryId}/inspection-reports`,
|
||||
LOADABLE_WAGONS: '/warehouse-inventory/loadable-wagons',
|
||||
BOOKING_SCHEDULE: (bookingId: string) => `/warehouse-inventory/booking/${bookingId}/schedule`,
|
||||
RESERVE: "/warehouse-inventory/reserve",
|
||||
ARRIVAL_QUEUE: "/warehouse-inventory/arrival-queue",
|
||||
AUTO_UNLOAD_ARRIVED: "/warehouse-inventory/auto-unload-arrived",
|
||||
AUTO_LOAD_READY: "/warehouse-inventory/auto-load-ready",
|
||||
UNLOAD_BOOKING: (bookingId: string) =>
|
||||
`/warehouse-inventory/bookings/${bookingId}/unload`,
|
||||
INSPECTION_REPORTS: (inventoryId: string) =>
|
||||
`/warehouse-inventory/${inventoryId}/inspection-reports`,
|
||||
LOADABLE_WAGONS: "/warehouse-inventory/loadable-wagons",
|
||||
BOOKING_SCHEDULE: (bookingId: string) =>
|
||||
`/warehouse-inventory/booking/${bookingId}/schedule`,
|
||||
MOVEMENTS: (id: string) => `/warehouse-inventory/${id}/movements`,
|
||||
ACTIVITY: (id: string) => `/warehouse-inventory/${id}/activity`,
|
||||
LOADINGS: (id: string) => `/warehouse-inventory/${id}/loadings`,
|
||||
// Import branch
|
||||
MARK_READY_PICKUP: (id: string) => `/warehouse-inventory/${id}/ready-for-pickup`,
|
||||
MARK_READY_PICKUP: (id: string) =>
|
||||
`/warehouse-inventory/${id}/ready-for-pickup`,
|
||||
RELEASE: (id: string) => `/warehouse-inventory/${id}/release`,
|
||||
RELEASE_DOCUMENT: (id: string) => `/warehouse-inventory/${id}/release-document`,
|
||||
RELEASE_DOCUMENT: (id: string) =>
|
||||
`/warehouse-inventory/${id}/release-document`,
|
||||
GRN_DOCUMENT: (id: string) => `/warehouse-inventory/${id}/grn-document`,
|
||||
HANDOVER_DOCUMENT: (id: string) => `/warehouse-inventory/${id}/handover-document`,
|
||||
HANDOVER_DOCUMENT: (id: string) =>
|
||||
`/warehouse-inventory/${id}/handover-document`,
|
||||
DELIVER: (id: string) => `/warehouse-inventory/${id}/deliver`,
|
||||
// Receive (Import/Export bulk)
|
||||
ELIGIBLE_BOOKINGS: (direction?: string) =>
|
||||
direction
|
||||
? `/warehouse-inventory/eligible-bookings?direction=${direction}`
|
||||
: `/warehouse-inventory/eligible-bookings`,
|
||||
RECEIVE_BULK: '/warehouse-inventory/receive-bulk',
|
||||
LOAD_PASSED_EXPORT: '/warehouse-inventory/load-passed-export',
|
||||
BULK_MARK_INSPECTED: '/warehouse-inventory/bulk-mark-inspected',
|
||||
RECEIVED_EXPORT: '/warehouse-inventory/received-export',
|
||||
READY_TO_LOAD_EXPORT: '/warehouse-inventory/ready-to-load-export',
|
||||
LOADED_EXPORT: '/warehouse-inventory/loaded-export',
|
||||
BULK_DISPATCH_EXPORT: '/warehouse-inventory/bulk-dispatch-export',
|
||||
IMPORT_ARRIVE_QUEUE: '/warehouse-inventory/import/arrive-queue',
|
||||
RECEIVE_BULK: "/warehouse-inventory/receive-bulk",
|
||||
LOAD_PASSED_EXPORT: "/warehouse-inventory/load-passed-export",
|
||||
BULK_MARK_INSPECTED: "/warehouse-inventory/bulk-mark-inspected",
|
||||
RECEIVED_EXPORT: "/warehouse-inventory/received-export",
|
||||
READY_TO_LOAD_EXPORT: "/warehouse-inventory/ready-to-load-export",
|
||||
LOADED_EXPORT: "/warehouse-inventory/loaded-export",
|
||||
BULK_DISPATCH_EXPORT: "/warehouse-inventory/bulk-dispatch-export",
|
||||
IMPORT_ARRIVE_QUEUE: "/warehouse-inventory/import/arrive-queue",
|
||||
IMPORT_TRAIN_ITEMS: (scheduleId: string) =>
|
||||
`/warehouse-inventory/import/trains/${scheduleId}/items`,
|
||||
IMPORT_AUTO_UNLOAD_ARRIVED: '/warehouse-inventory/import/auto-unload-arrived-bookings',
|
||||
IMPORT_UNLOADED_QUEUE: '/warehouse-inventory/import/unloaded-queue',
|
||||
IMPORT_PICKUP_READY_QUEUE: '/warehouse-inventory/import/pickup-ready-queue',
|
||||
EXPORT_DJIBOUTI_ARRIVAL_QUEUE: '/warehouse-inventory/export/djibouti-arrival-queue',
|
||||
IMPORT_AUTO_UNLOAD_ARRIVED:
|
||||
"/warehouse-inventory/import/auto-unload-arrived-bookings",
|
||||
IMPORT_UNLOADED_QUEUE: "/warehouse-inventory/import/unloaded-queue",
|
||||
IMPORT_PICKUP_READY_QUEUE: "/warehouse-inventory/import/pickup-ready-queue",
|
||||
EXPORT_DJIBOUTI_ARRIVAL_QUEUE:
|
||||
"/warehouse-inventory/export/djibouti-arrival-queue",
|
||||
EXPORT_DJIBOUTI_TRAIN_ITEMS: (scheduleId: string) =>
|
||||
`/warehouse-inventory/export/djibouti-trains/${scheduleId}/items`,
|
||||
EXPORT_AUTO_UNLOAD_AT_DJIBOUTI: '/warehouse-inventory/export/auto-unload-at-djibouti',
|
||||
EXPORT_AUTO_UNLOAD_AT_DJIBOUTI:
|
||||
"/warehouse-inventory/export/auto-unload-at-djibouti",
|
||||
},
|
||||
|
||||
WAREHOUSE_LOADINGS: {
|
||||
BASE: '/warehouse-loadings',
|
||||
BASE: "/warehouse-loadings",
|
||||
},
|
||||
|
||||
WAREHOUSE_INSPECTION: {
|
||||
BY_ID: (id: string) => `/warehouse-inspection-reports/${id}`,
|
||||
ATTACHMENTS: (id: string) => `/warehouse-inspection-reports/${id}/attachments`,
|
||||
ATTACHMENTS: (id: string) =>
|
||||
`/warehouse-inspection-reports/${id}/attachments`,
|
||||
},
|
||||
|
||||
WAREHOUSE_RULES: {
|
||||
ALLOCATION: '/warehouse-allocation-rules',
|
||||
ALLOCATION: "/warehouse-allocation-rules",
|
||||
ALLOCATION_BY_ID: (id: string) => `/warehouse-allocation-rules/${id}`,
|
||||
ALLOCATION_PREVIEW: '/warehouse-allocation/preview',
|
||||
FEES: '/warehouse-fee-rules',
|
||||
ALLOCATION_PREVIEW: "/warehouse-allocation/preview",
|
||||
FEES: "/warehouse-fee-rules",
|
||||
FEES_BY_ID: (id: string) => `/warehouse-fee-rules/${id}`,
|
||||
FEE_PREVIEW: (inventoryId: string) => `/warehouse-inventory/${inventoryId}/fee-preview`,
|
||||
FEE_PREVIEW: (inventoryId: string) =>
|
||||
`/warehouse-inventory/${inventoryId}/fee-preview`,
|
||||
},
|
||||
|
||||
WAREHOUSE_INVOICES: {
|
||||
BASE: '/warehouse-fee-invoices',
|
||||
BASE: "/warehouse-fee-invoices",
|
||||
BY_ID: (id: string) => `/warehouse-fee-invoices/${id}`,
|
||||
DOCUMENT: (id: string) => `/warehouse-fee-invoices/${id}/document`,
|
||||
RECEIPT: (id: string) => `/warehouse-fee-invoices/${id}/receipt`,
|
||||
CANCEL: (id: string) => `/warehouse-fee-invoices/${id}/cancel`,
|
||||
PAY: (id: string) => `/warehouse-fee-invoices/${id}/pay`,
|
||||
PAY_ONLINE: (id: string) => `/warehouse-fee-invoices/${id}/pay-online`,
|
||||
GENERATE: (inventoryId: string) => `/warehouse-inventory/${inventoryId}/generate-fee-invoice`,
|
||||
FOR_INVENTORY: (inventoryId: string) => `/warehouse-inventory/${inventoryId}/fee-invoices`,
|
||||
FOR_BOOKING: (bookingId: string) => `/bookings/${bookingId}/warehouse-fee-invoices`,
|
||||
GATE_CLEARANCE: (inventoryId: string) => `/warehouse-inventory/${inventoryId}/gate-clearance`,
|
||||
GENERATE: (inventoryId: string) =>
|
||||
`/warehouse-inventory/${inventoryId}/generate-fee-invoice`,
|
||||
FOR_INVENTORY: (inventoryId: string) =>
|
||||
`/warehouse-inventory/${inventoryId}/fee-invoices`,
|
||||
FOR_BOOKING: (bookingId: string) =>
|
||||
`/bookings/${bookingId}/warehouse-fee-invoices`,
|
||||
GATE_CLEARANCE: (inventoryId: string) =>
|
||||
`/warehouse-inventory/${inventoryId}/gate-clearance`,
|
||||
},
|
||||
|
||||
INTERCHANGE_DOCUMENTS: {
|
||||
BASE: '/interchange-documents',
|
||||
BASE: "/interchange-documents",
|
||||
BY_ID: (id: string) => `/interchange-documents/${id}`,
|
||||
GENERATE_FROM_SCHEDULE: '/interchange-documents/generate-from-schedule',
|
||||
GENERATE_FROM_SCHEDULE: "/interchange-documents/generate-from-schedule",
|
||||
ACKNOWLEDGE: (id: string) => `/interchange-documents/${id}/acknowledge`,
|
||||
DISPUTE: (id: string) => `/interchange-documents/${id}/dispute`,
|
||||
CANCEL: (id: string) => `/interchange-documents/${id}/cancel`,
|
||||
},
|
||||
|
||||
IMPORT_OPERATIONS: {
|
||||
DJIBOUTI_INCIDENTS: '/import-operations/djibouti-incidents',
|
||||
DJIBOUTI_INCIDENTS: "/import-operations/djibouti-incidents",
|
||||
CUSTOMS: (bookingId: string) => `/import-operations/customs/${bookingId}`,
|
||||
CUSTOMS_DOCUMENTS: (bookingId: string) => `/import-operations/customs/${bookingId}/documents`,
|
||||
CUSTOMS_DECLARATION: (bookingId: string) => `/import-operations/customs/${bookingId}/declaration`,
|
||||
CUSTOMS_DOCUMENTS: (bookingId: string) =>
|
||||
`/import-operations/customs/${bookingId}/documents`,
|
||||
CUSTOMS_DECLARATION: (bookingId: string) =>
|
||||
`/import-operations/customs/${bookingId}/declaration`,
|
||||
CUSTOMS_NOTIFY_DUTIES_TAXES: (bookingId: string) =>
|
||||
`/import-operations/customs/${bookingId}/notify-duties-taxes`,
|
||||
CUSTOMS_DUTIES_TAXES_PAID: (bookingId: string) =>
|
||||
`/import-operations/customs/${bookingId}/duties-taxes-paid`,
|
||||
CUSTOMS_RISK: (bookingId: string) => `/import-operations/customs/${bookingId}/risk`,
|
||||
CUSTOMS_RISK: (bookingId: string) =>
|
||||
`/import-operations/customs/${bookingId}/risk`,
|
||||
CUSTOMS_RELEASE_PERMITTED: (bookingId: string) =>
|
||||
`/import-operations/customs/${bookingId}/release-permitted`,
|
||||
EMPTY_CONTAINER_RETURNS: '/import-operations/empty-container-returns',
|
||||
EMPTY_CONTAINER_RETURNS: "/import-operations/empty-container-returns",
|
||||
EMPTY_CONTAINER_RETURN_STATUS: (id: string) =>
|
||||
`/import-operations/empty-container-returns/${id}/status`,
|
||||
},
|
||||
|
||||
VEHICLES: {
|
||||
BASE: '/vehicles',
|
||||
BASE: "/vehicles",
|
||||
BY_ID: (id: string) => `/vehicles/${id}`,
|
||||
},
|
||||
|
||||
FIRST_MILE: {
|
||||
BASE: '/first-mile',
|
||||
BASE: "/first-mile",
|
||||
BY_ID: (id: string) => `/first-mile/${id}`,
|
||||
ACCEPT: (reference: string) => `/first-mile/accept/${reference}`,
|
||||
},
|
||||
|
||||
LAST_MILE: {
|
||||
BASE: '/last-mile',
|
||||
BASE: "/last-mile",
|
||||
BY_ID: (id: string) => `/last-mile/${id}`,
|
||||
ACCEPT: (reference: string) => `/last-mile/accept/${reference}`,
|
||||
},
|
||||
|
||||
DRIVERS: {
|
||||
BASE: '/drivers',
|
||||
BASE: "/drivers",
|
||||
BY_ID: (id: string) => `/drivers/${id}`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
Container,
|
||||
Group,
|
||||
Loader,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ArrowLeft, Download } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import {
|
||||
InvoiceStatusBadge,
|
||||
formatDate,
|
||||
formatMoney,
|
||||
humanize,
|
||||
} from "@/components/customers";
|
||||
import { PageContainer, PageHeader } from "@/components/page";
|
||||
import { api } from "@/services/api";
|
||||
import { invoicesService } from "@/services/invoices.service";
|
||||
|
||||
function openPdfBlob(blob: Blob, filename: string) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const opened = window.open(url, "_blank");
|
||||
if (!opened) {
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
}
|
||||
|
||||
function InfoField({ label, value }: { label: string; value?: string | null }) {
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<Text
|
||||
size="xs"
|
||||
fw={600}
|
||||
c="edr-muted"
|
||||
tt="uppercase"
|
||||
style={{ letterSpacing: "0.04em" }}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="sm" c="edr-text">
|
||||
{value && value.trim() ? value : "—"}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default function InvoiceDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
|
||||
const { data: invoice, isLoading } = useQuery(
|
||||
api.invoices.getById.queryOptions({
|
||||
input: { id: id ?? "" },
|
||||
enabled: Boolean(id),
|
||||
}),
|
||||
);
|
||||
|
||||
const downloadDocument = async () => {
|
||||
if (!id) return;
|
||||
setDownloading(true);
|
||||
try {
|
||||
const { data } = await invoicesService.downloadDocument(id);
|
||||
openPdfBlob(data, `${invoice?.invoiceNumber ?? "invoice"}.pdf`);
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Center mih="60vh">
|
||||
<Loader />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!invoice) {
|
||||
return (
|
||||
<Container size="sm" py="xl">
|
||||
<Stack align="center" gap="md">
|
||||
<Text fw={700}>Invoice not found</Text>
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<ArrowLeft size={16} />}
|
||||
onClick={() => navigate("/dashboard/invoices")}
|
||||
>
|
||||
Back to invoices
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
breadcrumbs={[
|
||||
{ label: "Invoices", href: "/dashboard/invoices" },
|
||||
{ label: invoice.invoiceNumber },
|
||||
]}
|
||||
backTo="/dashboard/invoices"
|
||||
title={invoice.invoiceNumber}
|
||||
subtitle={`${humanize(invoice.source)} · ${invoice.sourceId}`}
|
||||
meta={<InvoiceStatusBadge status={invoice.status} />}
|
||||
action={
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="lg"
|
||||
radius="md"
|
||||
aria-label="Download invoice"
|
||||
loading={downloading}
|
||||
onClick={() => void downloadDocument()}
|
||||
>
|
||||
<Download size={16} />
|
||||
</ActionIcon>
|
||||
}
|
||||
/>
|
||||
|
||||
<Stack gap="lg">
|
||||
<Card>
|
||||
<Stack gap="lg">
|
||||
<Text fw={600} c="edr-text">
|
||||
Summary
|
||||
</Text>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
|
||||
<InfoField label="Billed to" value={invoice.company?.name} />
|
||||
<InfoField
|
||||
label="Profile"
|
||||
value={invoice.companyProfile?.reference}
|
||||
/>
|
||||
<InfoField label="Type" value={humanize(invoice.type)} />
|
||||
<InfoField label="Currency" value={invoice.currency} />
|
||||
<InfoField label="Issued" value={formatDate(invoice.issuedAt)} />
|
||||
<InfoField label="Due" value={formatDate(invoice.dueAt)} />
|
||||
<InfoField
|
||||
label="Total"
|
||||
value={formatMoney(invoice.totalAmount, invoice.currency)}
|
||||
/>
|
||||
<InfoField
|
||||
label="Balance"
|
||||
value={formatMoney(invoice.balanceAmount, invoice.currency)}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Stack gap="md">
|
||||
<Text fw={600} c="edr-text">
|
||||
Line items
|
||||
</Text>
|
||||
<Table striped withRowBorders={false} verticalSpacing="xs">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Description</Table.Th>
|
||||
<Table.Th>Charge type</Table.Th>
|
||||
<Table.Th ta="right">Quantity</Table.Th>
|
||||
<Table.Th ta="right">Unit rate</Table.Th>
|
||||
<Table.Th ta="right">Amount</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{(invoice.lines ?? []).map((line) => (
|
||||
<Table.Tr key={line.id}>
|
||||
<Table.Td>{line.description ?? line.chargeType}</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" c="dimmed">
|
||||
{humanize(line.chargeType)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">{line.quantity}</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
{formatMoney(line.unitRate, line.currency)}
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
{formatMoney(line.amount, line.currency)}
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
{(invoice.lines ?? []).length === 0 && (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={5}>
|
||||
<Text size="sm" c="dimmed" ta="center" py="md">
|
||||
No line items.
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
|
||||
<Group
|
||||
justify="flex-end"
|
||||
gap="xl"
|
||||
pt="sm"
|
||||
style={{
|
||||
borderTop: "1px solid var(--mantine-color-edr-border-0)",
|
||||
}}
|
||||
>
|
||||
<Stack gap={2} align="flex-end">
|
||||
<Text size="xs" c="edr-muted">
|
||||
Subtotal
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{formatMoney(invoice.subtotalAmount, invoice.currency)}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={2} align="flex-end">
|
||||
<Text size="xs" c="edr-muted">
|
||||
Tax
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{formatMoney(invoice.taxAmount, invoice.currency)}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={2} align="flex-end">
|
||||
<Text size="xs" c="edr-muted">
|
||||
Paid
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{formatMoney(invoice.paidAmount, invoice.currency)}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={2} align="flex-end">
|
||||
<Text size="xs" fw={700}>
|
||||
Total
|
||||
</Text>
|
||||
<Text size="sm" fw={700}>
|
||||
{formatMoney(invoice.totalAmount, invoice.currency)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import type { Freight } from "@edr/types";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Card,
|
||||
Group,
|
||||
SegmentedControl,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { RefreshCw, Search, X } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import {
|
||||
InvoiceStatusBadge,
|
||||
formatDate,
|
||||
formatMoney,
|
||||
humanize,
|
||||
} from "@/components/customers";
|
||||
import { PageContainer, PageHeader } from "@/components/page";
|
||||
import { api } from "@/services/api";
|
||||
import type { Invoice } from "@/types/invoice";
|
||||
import {
|
||||
DataTable,
|
||||
DataTableFooter,
|
||||
usePagination,
|
||||
type ColumnDef,
|
||||
} from "@edr/ui-common";
|
||||
|
||||
export default function InvoicesPage() {
|
||||
const navigate = useNavigate();
|
||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
||||
const [query, setQuery] = useState("");
|
||||
const [debouncedQuery] = useDebouncedValue(query, 300);
|
||||
const [statusFilter, setStatusFilter] = useState<"" | Freight.InvoiceStatus>(
|
||||
"",
|
||||
);
|
||||
|
||||
const filter = useMemo(
|
||||
() => ({
|
||||
page: pagination.pageIndex + 1,
|
||||
pageSize: pagination.pageSize,
|
||||
search: debouncedQuery,
|
||||
status: statusFilter || undefined,
|
||||
}),
|
||||
[pagination.pageIndex, pagination.pageSize, debouncedQuery, statusFilter],
|
||||
);
|
||||
|
||||
const { data, isLoading, isError, refetch, isFetching } = useQuery(
|
||||
api.invoices.list.queryOptions({ input: { filter } }),
|
||||
);
|
||||
|
||||
const rows = data?.items ?? [];
|
||||
const total = data?.total ?? 0;
|
||||
const pageCount = Math.max(1, Math.ceil(total / pagination.pageSize));
|
||||
|
||||
const columns: ColumnDef<Invoice>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: "invoiceNumber",
|
||||
header: "Invoice",
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" fw={600} c="edr-text">
|
||||
{row.original.invoiceNumber}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "billedTo",
|
||||
header: "Billed to",
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" c="edr-text">
|
||||
{row.original.company?.name ?? "—"}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "source",
|
||||
header: "Source",
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" c="dimmed">
|
||||
{humanize(row.original.source)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => <InvoiceStatusBadge status={row.original.status} />,
|
||||
},
|
||||
{
|
||||
id: "amount",
|
||||
header: "Amount",
|
||||
meta: { headerClassName: "text-right", cellClassName: "text-right" },
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" fw={600} c="edr-text">
|
||||
{formatMoney(row.original.totalAmount, row.original.currency)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "balance",
|
||||
header: "Balance",
|
||||
meta: { headerClassName: "text-right", cellClassName: "text-right" },
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" c="dimmed">
|
||||
{formatMoney(row.original.balanceAmount, row.original.currency)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "dueAt",
|
||||
header: "Due",
|
||||
meta: { headerClassName: "text-right", cellClassName: "text-right" },
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" c="dimmed">
|
||||
{formatDate(row.original.dueAt)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Invoices"
|
||||
subtitle="Every invoice issued across bookings, warehouse fees and clearance charges."
|
||||
action={
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="lg"
|
||||
radius="md"
|
||||
aria-label="Refresh"
|
||||
loading={isFetching}
|
||||
onClick={() => void refetch()}
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
</ActionIcon>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card p={0}>
|
||||
<Stack gap={0}>
|
||||
<Box px="md" pt="md" pb="sm" w="100%">
|
||||
<Group justify="space-between" gap="md" wrap="wrap">
|
||||
<TextInput
|
||||
placeholder="Search by invoice number…"
|
||||
leftSection={<Search size={18} />}
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
rightSection={
|
||||
query ? (
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
color="gray"
|
||||
radius="md"
|
||||
variant="transparent"
|
||||
onClick={() => setQuery("")}
|
||||
>
|
||||
<X size={16} />
|
||||
</ActionIcon>
|
||||
) : null
|
||||
}
|
||||
style={{ flex: 1, minWidth: "240px" }}
|
||||
radius="lg"
|
||||
/>
|
||||
<SegmentedControl
|
||||
size="sm"
|
||||
radius="md"
|
||||
value={statusFilter || "all"}
|
||||
onChange={(v) => {
|
||||
setStatusFilter(
|
||||
v === "all" ? "" : (v as Freight.InvoiceStatus),
|
||||
);
|
||||
setPagination((prev) => ({ ...prev, pageIndex: 0 }));
|
||||
}}
|
||||
data={[
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Pending", value: "PENDING" },
|
||||
{ label: "Paid", value: "PAID" },
|
||||
{ label: "Overdue", value: "OVERDUE" },
|
||||
]}
|
||||
/>
|
||||
<Text size="sm" c="dimmed">
|
||||
{total} record{total !== 1 ? "s" : ""}
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Box style={{ overflowX: "auto" }} w="100%">
|
||||
<Box miw={920}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={rows}
|
||||
status={isLoading ? "loading" : isError ? "error" : "success"}
|
||||
onRowClick={(row) => navigate(`/dashboard/invoices/${row.id}`)}
|
||||
emptyMessage={
|
||||
debouncedQuery
|
||||
? "No invoices match your search."
|
||||
: "No invoices yet."
|
||||
}
|
||||
error={
|
||||
isError
|
||||
? {
|
||||
message: "Failed to load invoices.",
|
||||
onRetry: () => void refetch(),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
pagination={{
|
||||
pageIndex: pagination.pageIndex,
|
||||
pageSize: pagination.pageSize,
|
||||
pageCount,
|
||||
totalCount: total,
|
||||
}}
|
||||
tableOptions={{
|
||||
state: { pagination },
|
||||
onPaginationChange: setPagination,
|
||||
manualPagination: true,
|
||||
pageCount,
|
||||
}}
|
||||
containerClassName="border-0 shadow-none bg-transparent"
|
||||
footer={DataTableFooter}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -28,6 +28,11 @@ import type {
|
||||
UpdateFileUploadFieldDto,
|
||||
UpdateFileUploadSettingDto,
|
||||
} from "@/types/fileUploadSettings";
|
||||
import type {
|
||||
Invoice,
|
||||
InvoiceListFilter,
|
||||
PaginatedInvoices,
|
||||
} from "@/types/invoice";
|
||||
import type { IOverviewDashboard, OverviewRange } from "@/types/overview";
|
||||
import {
|
||||
RuleEngineListResult,
|
||||
@@ -128,6 +133,7 @@ import {
|
||||
import { containerTypesService } from "./container-types.service";
|
||||
import { containerService, type Container } from "./containerService";
|
||||
import { customersService } from "./customers.service";
|
||||
import { invoicesService } from "./invoices.service";
|
||||
import { dropdownSettingsService } from "./dropdownSettings.service";
|
||||
import { fileUploadSettingsService } from "./fileUploadSettings.service";
|
||||
import {
|
||||
@@ -197,7 +203,10 @@ const TRAIN_SCHEDULING_INVALIDATIONS: ReadonlyArray<readonly unknown[]> = [
|
||||
export const api = {
|
||||
trainScheduling: {
|
||||
// ── Queries ────────────────────────────────────────────────────────────
|
||||
scheduleList: endpoint<{ freightType?: FreightType }, TrainScheduleListItem[]>(
|
||||
scheduleList: endpoint<
|
||||
{ freightType?: FreightType },
|
||||
TrainScheduleListItem[]
|
||||
>(
|
||||
"train-scheduling",
|
||||
"schedules",
|
||||
({ freightType }) => trainSchedulingService.listSchedules(freightType),
|
||||
@@ -211,11 +220,16 @@ export const api = {
|
||||
() => QUERY_KEYS.TRAIN_SCHEDULING.batchBoard(),
|
||||
),
|
||||
|
||||
batchBoardDetail: endpoint<{ scheduleId: string }, BatchBoardScheduleDetail>(
|
||||
batchBoardDetail: endpoint<
|
||||
{ scheduleId: string },
|
||||
BatchBoardScheduleDetail
|
||||
>(
|
||||
"train-scheduling",
|
||||
"batch-board-detail",
|
||||
({ scheduleId }) => trainSchedulingService.getBatchBoardDetail(scheduleId),
|
||||
({ scheduleId }) => QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId),
|
||||
({ scheduleId }) =>
|
||||
trainSchedulingService.getBatchBoardDetail(scheduleId),
|
||||
({ scheduleId }) =>
|
||||
QUERY_KEYS.TRAIN_SCHEDULING.batchBoardDetail(scheduleId),
|
||||
),
|
||||
|
||||
scheduleDetail: endpoint<
|
||||
@@ -344,7 +358,10 @@ export const api = {
|
||||
),
|
||||
|
||||
// ── Mutations ──────────────────────────────────────────────────────────
|
||||
runAllocation: endpoint<{ scheduleId: string }, WagonAllocationAttemptResult>(
|
||||
runAllocation: endpoint<
|
||||
{ scheduleId: string },
|
||||
WagonAllocationAttemptResult
|
||||
>(
|
||||
"train-scheduling",
|
||||
"run-allocation",
|
||||
({ scheduleId }) => trainSchedulingService.runAllocation(scheduleId),
|
||||
@@ -454,7 +471,10 @@ export const api = {
|
||||
() => TRAIN_SCHEDULING_INVALIDATIONS,
|
||||
),
|
||||
|
||||
pinWagons: endpoint<{ id: string; payload: PinWagonsPayload }, TrainScheduleDetail>(
|
||||
pinWagons: endpoint<
|
||||
{ id: string; payload: PinWagonsPayload },
|
||||
TrainScheduleDetail
|
||||
>(
|
||||
"train-scheduling",
|
||||
"pin-wagons",
|
||||
({ id, payload }) => trainSchedulingService.pinWagons(id, payload),
|
||||
@@ -550,8 +570,10 @@ export const api = {
|
||||
({ id }) => warehouseService.getById(id).then((r) => r.data),
|
||||
),
|
||||
|
||||
dashboard: endpoint<void, WarehouseDashboard>("warehouses", "dashboard", () =>
|
||||
warehouseService.dashboard().then((r) => r.data),
|
||||
dashboard: endpoint<void, WarehouseDashboard>(
|
||||
"warehouses",
|
||||
"dashboard",
|
||||
() => warehouseService.dashboard().then((r) => r.data),
|
||||
),
|
||||
|
||||
create: endpoint<SaveWarehousePayload, Warehouse>(
|
||||
@@ -642,10 +664,8 @@ export const api = {
|
||||
listInventory: endpoint<
|
||||
{ filter?: InventoryFilter },
|
||||
WarehouseInventoryItem[]
|
||||
>(
|
||||
"warehouse-inventory",
|
||||
"list",
|
||||
({ filter }) => warehouseService.listInventory(filter).then((r) => r.data),
|
||||
>("warehouse-inventory", "list", ({ filter }) =>
|
||||
warehouseService.listInventory(filter).then((r) => r.data),
|
||||
),
|
||||
|
||||
inquiry: endpoint<
|
||||
@@ -658,11 +678,19 @@ export const api = {
|
||||
({ filter }) => ["warehouse-inventory", "inquiry", filter],
|
||||
),
|
||||
|
||||
eligibleBookings: endpoint<{ direction?: 'IMPORT' | 'EXPORT' } | void, EligibleBooking[]>(
|
||||
eligibleBookings: endpoint<
|
||||
{ direction?: "IMPORT" | "EXPORT" } | void,
|
||||
EligibleBooking[]
|
||||
>(
|
||||
"warehouse-inventory",
|
||||
"eligible-bookings",
|
||||
(input) => warehouseService.eligibleBookings(input?.direction).then((r) => r.data),
|
||||
(input) => ["warehouse-inventory", "eligible-bookings", input?.direction ?? "ALL"],
|
||||
(input) =>
|
||||
warehouseService.eligibleBookings(input?.direction).then((r) => r.data),
|
||||
(input) => [
|
||||
"warehouse-inventory",
|
||||
"eligible-bookings",
|
||||
input?.direction ?? "ALL",
|
||||
],
|
||||
),
|
||||
|
||||
readyToLoadExport: endpoint<void, ReadyToLoadRow[]>(
|
||||
@@ -698,7 +726,11 @@ export const api = {
|
||||
"import-train-items",
|
||||
({ scheduleId }) =>
|
||||
warehouseService.importTrainItems(scheduleId).then((r) => r.data),
|
||||
({ scheduleId }) => ["warehouse-inventory", "import-train-items", scheduleId],
|
||||
({ scheduleId }) => [
|
||||
"warehouse-inventory",
|
||||
"import-train-items",
|
||||
scheduleId,
|
||||
],
|
||||
),
|
||||
|
||||
importUnloadedQueue: endpoint<void, ImportUnloadedItem[]>(
|
||||
@@ -766,8 +798,11 @@ export const api = {
|
||||
"inspection-reports",
|
||||
({ inventoryId }) =>
|
||||
warehouseService.listInspectionReports(inventoryId).then((r) => r.data),
|
||||
({ inventoryId }) =>
|
||||
["warehouse-inventory", inventoryId, "inspection-reports"],
|
||||
({ inventoryId }) => [
|
||||
"warehouse-inventory",
|
||||
inventoryId,
|
||||
"inspection-reports",
|
||||
],
|
||||
),
|
||||
|
||||
allocationRules: endpoint<void, AllocationRule[]>(
|
||||
@@ -784,15 +819,28 @@ export const api = {
|
||||
() => ["warehouse-fee-rules"],
|
||||
),
|
||||
|
||||
feePreview: endpoint<{ inventoryId: string; billingCurrency?: 'ETB' | 'USD' }, FeePreview[]>(
|
||||
feePreview: endpoint<
|
||||
{ inventoryId: string; billingCurrency?: "ETB" | "USD" },
|
||||
FeePreview[]
|
||||
>(
|
||||
"warehouse-inventory",
|
||||
"fee-preview",
|
||||
({ inventoryId, billingCurrency }) =>
|
||||
warehouseService.feePreview(inventoryId, billingCurrency).then((r) => r.data),
|
||||
({ inventoryId, billingCurrency }) => ["warehouse-inventory", inventoryId, "fee-preview", billingCurrency ?? 'USD'],
|
||||
warehouseService
|
||||
.feePreview(inventoryId, billingCurrency)
|
||||
.then((r) => r.data),
|
||||
({ inventoryId, billingCurrency }) => [
|
||||
"warehouse-inventory",
|
||||
inventoryId,
|
||||
"fee-preview",
|
||||
billingCurrency ?? "USD",
|
||||
],
|
||||
),
|
||||
|
||||
invoices: endpoint<{ filter?: WarehouseInvoiceFilter }, WarehouseFeeInvoice[]>(
|
||||
invoices: endpoint<
|
||||
{ filter?: WarehouseInvoiceFilter },
|
||||
WarehouseFeeInvoice[]
|
||||
>(
|
||||
"warehouse-fee-invoices",
|
||||
"list",
|
||||
({ filter }) => warehouseService.listInvoices(filter).then((r) => r.data),
|
||||
@@ -821,7 +869,8 @@ export const api = {
|
||||
receiveInventory: endpoint<ReceiveInventoryPayload, WarehouseInventoryItem>(
|
||||
"warehouse-inventory",
|
||||
"receive",
|
||||
(payload) => warehouseService.receiveInventory(payload).then((r) => r.data),
|
||||
(payload) =>
|
||||
warehouseService.receiveInventory(payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => [["warehouse-inventory"], ["warehouses"]],
|
||||
),
|
||||
@@ -856,7 +905,8 @@ export const api = {
|
||||
>(
|
||||
"warehouse-inventory",
|
||||
"load",
|
||||
({ id, payload }) => warehouseService.load(id, payload).then((r) => r.data),
|
||||
({ id, payload }) =>
|
||||
warehouseService.load(id, payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
@@ -875,7 +925,8 @@ export const api = {
|
||||
>(
|
||||
"warehouse-inventory",
|
||||
"move",
|
||||
({ id, payload }) => warehouseService.move(id, payload).then((r) => r.data),
|
||||
({ id, payload }) =>
|
||||
warehouseService.move(id, payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
@@ -931,7 +982,8 @@ export const api = {
|
||||
bulkMarkInspected: endpoint<BulkInspectPayload, BulkInspectResult>(
|
||||
"warehouse-inventory",
|
||||
"bulk-mark-inspected",
|
||||
(payload) => warehouseService.bulkMarkInspected(payload).then((r) => r.data),
|
||||
(payload) =>
|
||||
warehouseService.bulkMarkInspected(payload).then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
@@ -949,7 +1001,12 @@ export const api = {
|
||||
{
|
||||
scheduleId: string;
|
||||
warehouseId?: string;
|
||||
assignments?: { bookingId: string; warehouseId: string; yardId: string; zoneId: string }[];
|
||||
assignments?: {
|
||||
bookingId: string;
|
||||
warehouseId: string;
|
||||
yardId: string;
|
||||
zoneId: string;
|
||||
}[];
|
||||
},
|
||||
AutoUnloadArrivedResult
|
||||
>(
|
||||
@@ -1021,11 +1078,11 @@ export const api = {
|
||||
),
|
||||
|
||||
// ── Allocation + fee rules ─────────────────────────────────────────────
|
||||
previewAllocation: endpoint<AllocationCriteria, AllocationPreviewResult | null>(
|
||||
"warehouse-allocation-rules",
|
||||
"preview",
|
||||
(criteria) =>
|
||||
warehouseService.previewAllocation(criteria).then((r) => r.data),
|
||||
previewAllocation: endpoint<
|
||||
AllocationCriteria,
|
||||
AllocationPreviewResult | null
|
||||
>("warehouse-allocation-rules", "preview", (criteria) =>
|
||||
warehouseService.previewAllocation(criteria).then((r) => r.data),
|
||||
),
|
||||
|
||||
createAllocationRule: endpoint<SaveAllocationRulePayload, AllocationRule>(
|
||||
@@ -1087,7 +1144,11 @@ export const api = {
|
||||
|
||||
// ── Invoices ───────────────────────────────────────────────────────────
|
||||
generateInvoice: endpoint<
|
||||
{ inventoryId: string; confirmZero?: boolean; billingCurrency?: 'ETB' | 'USD' },
|
||||
{
|
||||
inventoryId: string;
|
||||
confirmZero?: boolean;
|
||||
billingCurrency?: "ETB" | "USD";
|
||||
},
|
||||
WarehouseFeeInvoice
|
||||
>(
|
||||
"warehouse-fee-invoices",
|
||||
@@ -1143,7 +1204,10 @@ export const api = {
|
||||
},
|
||||
|
||||
routes: {
|
||||
list: endpoint<{ status?: import("./routes.service").RouteStatus } | void, RouteRecord[]>(
|
||||
list: endpoint<
|
||||
{ status?: import("./routes.service").RouteStatus } | void,
|
||||
RouteRecord[]
|
||||
>(
|
||||
"routes",
|
||||
"list",
|
||||
(input) =>
|
||||
@@ -1168,7 +1232,10 @@ export const api = {
|
||||
() => [["routes"]],
|
||||
),
|
||||
|
||||
update: endpoint<{ id: string; data: Partial<SaveRoutePayload> }, RouteRecord>(
|
||||
update: endpoint<
|
||||
{ id: string; data: Partial<SaveRoutePayload> },
|
||||
RouteRecord
|
||||
>(
|
||||
"routes",
|
||||
"update",
|
||||
({ id, data }) => routesService.update(id, data).then((r) => r.data),
|
||||
@@ -1281,10 +1348,8 @@ export const api = {
|
||||
({ trainId }) => ["wagons", "train", trainId],
|
||||
),
|
||||
|
||||
getById: endpoint<{ id: string }, Wagon>(
|
||||
"wagons",
|
||||
"getById",
|
||||
({ id }) => wagonService.getById(id).then((r) => r.data),
|
||||
getById: endpoint<{ id: string }, Wagon>("wagons", "getById", ({ id }) =>
|
||||
wagonService.getById(id).then((r) => r.data),
|
||||
),
|
||||
|
||||
assignToTrain: endpoint<
|
||||
@@ -1663,7 +1728,8 @@ export const api = {
|
||||
>(
|
||||
"file-upload-settings",
|
||||
"addField",
|
||||
({ settingId, dto }) => fileUploadSettingsService.addField(settingId, dto),
|
||||
({ settingId, dto }) =>
|
||||
fileUploadSettingsService.addField(settingId, dto),
|
||||
undefined,
|
||||
() => [["file-upload-settings"]],
|
||||
),
|
||||
@@ -1762,7 +1828,8 @@ export const api = {
|
||||
>(
|
||||
"dropdown-settings",
|
||||
"updateOption",
|
||||
({ optionId, dto }) => dropdownSettingsService.updateOption(optionId, dto),
|
||||
({ optionId, dto }) =>
|
||||
dropdownSettingsService.updateOption(optionId, dto),
|
||||
undefined,
|
||||
() => [["dropdown-settings"]],
|
||||
),
|
||||
@@ -1815,11 +1882,10 @@ export const api = {
|
||||
ruleEngineService.update(resource, id, payload),
|
||||
),
|
||||
|
||||
remove: endpoint<
|
||||
{ resource: RuleEngineResourceSlug; id: string },
|
||||
void
|
||||
>("rule-engine", "remove", ({ resource, id }) =>
|
||||
ruleEngineService.remove(resource, id),
|
||||
remove: endpoint<{ resource: RuleEngineResourceSlug; id: string }, void>(
|
||||
"rule-engine",
|
||||
"remove",
|
||||
({ resource, id }) => ruleEngineService.remove(resource, id),
|
||||
),
|
||||
|
||||
submitRate: endpoint<{ id: string }, RuleEngineRecord>(
|
||||
@@ -1842,14 +1908,21 @@ export const api = {
|
||||
),
|
||||
|
||||
reorder: endpoint<
|
||||
{ resource: RuleEngineResourceSlug; payload: { ids: string[]; requiresDirectorApproval?: boolean } },
|
||||
{
|
||||
resource: RuleEngineResourceSlug;
|
||||
payload: { ids: string[]; requiresDirectorApproval?: boolean };
|
||||
},
|
||||
void
|
||||
>("rule-engine", "reorder", ({ resource, payload }) =>
|
||||
ruleEngineService.reorder(resource, payload),
|
||||
),
|
||||
|
||||
moveOrder: endpoint<
|
||||
{ resource: RuleEngineResourceSlug; id: string; direction: "up" | "down" },
|
||||
{
|
||||
resource: RuleEngineResourceSlug;
|
||||
id: string;
|
||||
direction: "up" | "down";
|
||||
},
|
||||
void
|
||||
>("rule-engine", "moveOrder", ({ resource, id, direction }) =>
|
||||
ruleEngineService.moveOrder(resource, id, direction),
|
||||
@@ -1871,10 +1944,8 @@ export const api = {
|
||||
({ id }) => QUERY_KEYS.BOOKINGS.byId(id),
|
||||
),
|
||||
|
||||
remove: endpoint<{ id: string }, void>(
|
||||
"bookings",
|
||||
"remove",
|
||||
({ id }) => bookingsService.remove(id),
|
||||
remove: endpoint<{ id: string }, void>("bookings", "remove", ({ id }) =>
|
||||
bookingsService.remove(id),
|
||||
),
|
||||
|
||||
staffAccept: endpoint<{ id: string; validityDays: number }, BookingDetail>(
|
||||
@@ -1924,10 +1995,11 @@ export const api = {
|
||||
({ id }) => bookingsService.generateContract(id),
|
||||
),
|
||||
|
||||
getContractView: endpoint<{ id: string }, import("./bookings.service").ContractView>(
|
||||
"bookings",
|
||||
"getContractView",
|
||||
({ id }) => bookingsService.getContractView(id),
|
||||
getContractView: endpoint<
|
||||
{ id: string },
|
||||
import("./bookings.service").ContractView
|
||||
>("bookings", "getContractView", ({ id }) =>
|
||||
bookingsService.getContractView(id),
|
||||
),
|
||||
|
||||
signContract: endpoint<
|
||||
@@ -2011,7 +2083,8 @@ export const api = {
|
||||
>(
|
||||
"customers",
|
||||
"setProfileStatus",
|
||||
({ profileId, status }) => customersService.setProfileStatus(profileId, status),
|
||||
({ profileId, status }) =>
|
||||
customersService.setProfileStatus(profileId, status),
|
||||
undefined,
|
||||
(_input, data) => [
|
||||
QUERY_KEYS.CUSTOMERS.byId(data.companyId),
|
||||
@@ -2032,6 +2105,22 @@ export const api = {
|
||||
),
|
||||
},
|
||||
|
||||
invoices: {
|
||||
list: endpoint<{ filter: InvoiceListFilter }, PaginatedInvoices>(
|
||||
"invoices",
|
||||
"list",
|
||||
({ filter }) => invoicesService.list(filter),
|
||||
({ filter }) => QUERY_KEYS.INVOICES.list(filter),
|
||||
),
|
||||
|
||||
getById: endpoint<{ id: string }, Invoice>(
|
||||
"invoices",
|
||||
"getById",
|
||||
({ id }) => invoicesService.getById(id),
|
||||
({ id }) => QUERY_KEYS.INVOICES.byId(id),
|
||||
),
|
||||
},
|
||||
|
||||
overview: {
|
||||
get: endpoint<{ range?: OverviewRange }, IOverviewDashboard>(
|
||||
"overview",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { api as apiClient } from "@/auth/http";
|
||||
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||
import type {
|
||||
Invoice,
|
||||
InvoiceListFilter,
|
||||
PaginatedInvoices,
|
||||
} from "@/types/invoice";
|
||||
|
||||
const cleanParams = (params: object) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(params).filter(
|
||||
([, value]) => value !== undefined && value !== "" && value !== null,
|
||||
),
|
||||
);
|
||||
|
||||
export const invoicesService = {
|
||||
list(filter: InvoiceListFilter): Promise<PaginatedInvoices> {
|
||||
return apiClient
|
||||
.get<PaginatedInvoices>(URL_CONSTANTS.BILLING.INVOICES, {
|
||||
params: cleanParams(filter),
|
||||
})
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
getById(id: string): Promise<Invoice> {
|
||||
return apiClient
|
||||
.get<Invoice>(URL_CONSTANTS.BILLING.INVOICE_BY_ID(id))
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
downloadDocument(id: string) {
|
||||
return apiClient.get<Blob>(URL_CONSTANTS.BILLING.INVOICE_DOCUMENT(id), {
|
||||
responseType: "blob",
|
||||
});
|
||||
},
|
||||
};
|
||||
22
apps/edr-freight-web/backoffice/src/types/invoice.ts
Normal file
22
apps/edr-freight-web/backoffice/src/types/invoice.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
/** Mirrors backend `Invoice` (the shared `Freight.IInvoice` omits a couple of raw entity columns). */
|
||||
export interface Invoice extends Freight.IInvoice {
|
||||
subtotalAmount: number;
|
||||
taxAmount: number;
|
||||
}
|
||||
|
||||
/** Query parameters for the invoice list. */
|
||||
export interface InvoiceListFilter {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
companyId?: string;
|
||||
status?: Freight.InvoiceStatus;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/** Standard paginated list envelope (matches the customers/bookings service shape). */
|
||||
export interface PaginatedInvoices {
|
||||
items: Invoice[];
|
||||
total: number;
|
||||
}
|
||||
Reference in New Issue
Block a user