mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
Customer Truck Assignment and Portal Delivary Approval
This commit is contained in:
@@ -61,6 +61,9 @@ export class DMoneyProvider implements PaymentProvider {
|
||||
): Promise<ProviderInitiationResult> {
|
||||
const fabricToken = await this.applyFabricToken();
|
||||
const requestBody = this.buildPreOrderRequest(input);
|
||||
this.logger.log(
|
||||
`D-Money preOrder send request merchOrderId=${input.merchantOrderId} body=${JSON.stringify(this.sanitize(requestBody))}`,
|
||||
);
|
||||
const response = await this.postJson<DMoneyPreOrderResponse>(
|
||||
`${this.baseUrl}/apiaccess/payment/gateway/payment/v1/merchant/preOrder`,
|
||||
requestBody,
|
||||
@@ -208,7 +211,7 @@ export class DMoneyProvider implements PaymentProvider {
|
||||
merch_order_id: input.merchantOrderId,
|
||||
trade_type: "WebCheckout" as const,
|
||||
business_type: "OnlineMerchant" as const,
|
||||
title: `${input.orderRef}`,
|
||||
title: "EDR booking payment",
|
||||
total_amount: totalAmount,
|
||||
// Charge the currency the caller already converted to; never relabel it provider-side.
|
||||
trans_currency: input.currency,
|
||||
|
||||
130
packages/types/src/freight/clearance-files.catalog.ts
Normal file
130
packages/types/src/freight/clearance-files.catalog.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/** Phased customs uploads stored by raw file `code` on contract/booking resources. */
|
||||
export type ClearanceWorkflowFileOwner = "customer" | "gl_et" | "gl_dj";
|
||||
|
||||
export type ClearanceWorkflowFileCategory =
|
||||
| "declaration"
|
||||
| "duty"
|
||||
| "transit"
|
||||
| "djibouti";
|
||||
|
||||
export interface ClearanceWorkflowFileCatalogEntry {
|
||||
code: string;
|
||||
label: string;
|
||||
uploadedBy: ClearanceWorkflowFileOwner;
|
||||
category: ClearanceWorkflowFileCategory;
|
||||
/** Omit when the file applies to both directions. */
|
||||
tradeDirection?: "IMPORT" | "EXPORT";
|
||||
}
|
||||
|
||||
export const CLEARANCE_WORKFLOW_FILE_CATALOG: ClearanceWorkflowFileCatalogEntry[] = [
|
||||
{ code: "im4", label: "IM4 Declaration", uploadedBy: "gl_et", category: "declaration", tradeDirection: "IMPORT" },
|
||||
{ code: "im5", label: "IM5 Declaration", uploadedBy: "gl_et", category: "declaration", tradeDirection: "IMPORT" },
|
||||
{ code: "ex3", label: "EX3 Declaration", uploadedBy: "gl_et", category: "declaration", tradeDirection: "EXPORT" },
|
||||
{ code: "ex8", label: "EX8 Declaration", uploadedBy: "gl_et", category: "declaration", tradeDirection: "EXPORT" },
|
||||
{ code: "duty_tax_notice", label: "Duty / Tax Notice", uploadedBy: "gl_et", category: "duty" },
|
||||
{ code: "duty_tax_receipt", label: "Duty / Tax Payment Slip", uploadedBy: "customer", category: "duty" },
|
||||
{ code: "transit_permitted", label: "Transit Permit", uploadedBy: "gl_et", category: "transit", tradeDirection: "IMPORT" },
|
||||
{
|
||||
code: "export_transport_document",
|
||||
label: "Transit Permit",
|
||||
uploadedBy: "gl_et",
|
||||
category: "transit",
|
||||
tradeDirection: "EXPORT",
|
||||
},
|
||||
{ code: "delivery_order", label: "Delivery Order", uploadedBy: "gl_dj", category: "djibouti", tradeDirection: "IMPORT" },
|
||||
{ code: "release_order", label: "Release Order", uploadedBy: "gl_dj", category: "djibouti", tradeDirection: "EXPORT" },
|
||||
];
|
||||
|
||||
/** Legacy single-type declaration codes (still shown when already uploaded). */
|
||||
export const LEGACY_DECLARATION_FILE_CODES = ["im4", "im5", "ex3", "ex8"] as const;
|
||||
|
||||
/** Multi-file declaration uploads use `declaration_0`, `declaration_1`, … */
|
||||
export const DECLARATION_FILE_PREFIX = "declaration_";
|
||||
|
||||
export function isDeclarationFileCode(code: string | null | undefined): boolean {
|
||||
if (!code) return false;
|
||||
const lower = code.toLowerCase();
|
||||
return (
|
||||
(LEGACY_DECLARATION_FILE_CODES as readonly string[]).includes(lower) ||
|
||||
lower === "declaration" ||
|
||||
lower.startsWith(DECLARATION_FILE_PREFIX)
|
||||
);
|
||||
}
|
||||
|
||||
export interface ClearanceWorkflowFile {
|
||||
code: string;
|
||||
label: string;
|
||||
uploadedBy: ClearanceWorkflowFileOwner;
|
||||
category: ClearanceWorkflowFileCategory;
|
||||
file: { id: string; name: string; url: string } | null;
|
||||
}
|
||||
|
||||
const CATALOG_BY_CODE = new Map(
|
||||
CLEARANCE_WORKFLOW_FILE_CATALOG.map((e) => [e.code, e]),
|
||||
);
|
||||
|
||||
export function clearanceWorkflowFileLabel(code: string | null | undefined): string | null {
|
||||
if (!code) return null;
|
||||
return CATALOG_BY_CODE.get(code)?.label ?? null;
|
||||
}
|
||||
|
||||
export function declarationFileLabel(code: string, index?: number): string {
|
||||
const lower = code.toLowerCase();
|
||||
const legacy = CATALOG_BY_CODE.get(lower);
|
||||
if (legacy?.category === "declaration") return legacy.label;
|
||||
if (lower.startsWith(DECLARATION_FILE_PREFIX) || lower === "declaration") {
|
||||
return index != null ? `Declaration document ${index + 1}` : "Customs declaration";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/** Legacy single import transit permit code. */
|
||||
export const LEGACY_IMPORT_TRANSIT_PERMIT_CODE = "transit_permitted";
|
||||
|
||||
/** Multi-file import transit permit uploads use `transit_permit_0`, `transit_permit_1`, … */
|
||||
export const TRANSIT_PERMIT_FILE_PREFIX = "transit_permit_";
|
||||
|
||||
export function isImportTransitPermitFileCode(code: string | null | undefined): boolean {
|
||||
if (!code) return false;
|
||||
const lower = code.toLowerCase();
|
||||
return (
|
||||
lower === LEGACY_IMPORT_TRANSIT_PERMIT_CODE || lower.startsWith(TRANSIT_PERMIT_FILE_PREFIX)
|
||||
);
|
||||
}
|
||||
|
||||
export function transitPermitFileLabel(code: string, index?: number): string {
|
||||
const lower = code.toLowerCase();
|
||||
if (lower === LEGACY_IMPORT_TRANSIT_PERMIT_CODE) return "Transit Permit";
|
||||
if (lower.startsWith(TRANSIT_PERMIT_FILE_PREFIX)) {
|
||||
return index != null ? `Transit permit ${index + 1}` : "Transit Permit";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
export const LEGACY_EXPORT_TRANSPORT_CODE = "export_transport_document";
|
||||
|
||||
export function isExportTransportFileCode(code: string | null | undefined): boolean {
|
||||
if (!code) return false;
|
||||
const lower = code.toLowerCase();
|
||||
return (
|
||||
lower === LEGACY_EXPORT_TRANSPORT_CODE ||
|
||||
lower.startsWith(`${LEGACY_EXPORT_TRANSPORT_CODE}_`)
|
||||
);
|
||||
}
|
||||
|
||||
export function exportTransportFileLabel(code: string, index?: number): string {
|
||||
const lower = code.toLowerCase();
|
||||
if (lower === LEGACY_EXPORT_TRANSPORT_CODE) return "Transit Permit";
|
||||
if (lower.startsWith(`${LEGACY_EXPORT_TRANSPORT_CODE}_`)) {
|
||||
return index != null ? `Transit permit ${index + 1}` : "Transit Permit";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
export function catalogEntriesForTradeDirection(
|
||||
tradeDirection: string,
|
||||
): ClearanceWorkflowFileCatalogEntry[] {
|
||||
return CLEARANCE_WORKFLOW_FILE_CATALOG.filter(
|
||||
(e) => !e.tradeDirection || e.tradeDirection === tradeDirection,
|
||||
);
|
||||
}
|
||||
@@ -259,6 +259,39 @@ export interface ContractClearanceView {
|
||||
documents: ContractClearanceDocument[];
|
||||
/** True once every required customer document is APPROVED. */
|
||||
allApproved: boolean;
|
||||
/** Current phased clearance step (ONE_TIME customs). */
|
||||
phase?: ContractDocPhase | null;
|
||||
/** Pre-booking milestone rows for this contract cycle. */
|
||||
milestones?: IClearanceMilestone[];
|
||||
/** What should happen next in the workflow. */
|
||||
nextAction?: ClearanceNextAction | null;
|
||||
dutyRequired?: boolean | null;
|
||||
roHold?: boolean;
|
||||
roHoldReason?: string | null;
|
||||
vesselDepartureDate?: string | null;
|
||||
roAmendmentRequestedAt?: string | null;
|
||||
bookingReady?: boolean;
|
||||
preClearanceFinalized?: boolean;
|
||||
/** Export post-booking clearance finalized after transit permit upload. */
|
||||
exportClearanceFinalized?: boolean;
|
||||
linkedBookingId?: string | null;
|
||||
dutyAdvice?: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
declarationSerial?: string | null;
|
||||
noticeFile?: { id: string; name: string; url: string } | null;
|
||||
} | null;
|
||||
/** Phased customs uploads (IM4, DO, transit permit, etc.) with friendly labels. */
|
||||
workflowFiles?: import("./clearance-files.catalog").ClearanceWorkflowFile[];
|
||||
}
|
||||
|
||||
export type ClearanceActorRole = "CUSTOMER" | "GL_ET" | "GL_DJ" | "OPERATIONS";
|
||||
|
||||
export interface ClearanceNextAction {
|
||||
actor: ClearanceActorRole;
|
||||
action: string;
|
||||
milestoneCode?: string | null;
|
||||
blockedReason?: string | null;
|
||||
}
|
||||
|
||||
/** Phased clearance document upload slots (doc §5.13). */
|
||||
@@ -334,6 +367,7 @@ export const IMPORT_MILESTONES = [
|
||||
"DECLARED",
|
||||
"DUTY_TAXES_ADVISED",
|
||||
"DUTY_TAX_PAID",
|
||||
"TRANSIT_PERMIT_UPLOADED",
|
||||
"DO_COLLECTED",
|
||||
"WAGON_REQUESTED",
|
||||
"FREIGHT_PAYMENT_SETTLED",
|
||||
@@ -366,6 +400,7 @@ export const EXPORT_MILESTONES = [
|
||||
"FREIGHT_PAYMENT_PENDING",
|
||||
"FREIGHT_PAYMENT_SETTLED",
|
||||
"WAGON_ALLOCATED",
|
||||
"EXPORT_TRANSPORT_ISSUED",
|
||||
"CARGO_ARRIVED",
|
||||
"READY_FOR_LOADING",
|
||||
"LOADED",
|
||||
@@ -411,7 +446,10 @@ export interface IContract extends BaseEntity {
|
||||
id: string;
|
||||
code: string;
|
||||
serviceName: string;
|
||||
description?: string | null;
|
||||
canBeBookedAlone: boolean;
|
||||
includesFirstMile?: boolean;
|
||||
includesLastMile?: boolean;
|
||||
includesCustoms: boolean;
|
||||
} | null;
|
||||
paymentCurrency: string;
|
||||
@@ -557,6 +595,7 @@ export interface CreateBulkLineDto {
|
||||
cargoWeightTons?: number;
|
||||
itemCount?: number;
|
||||
hazardousQuantity?: number;
|
||||
reeferQuantity?: number;
|
||||
}
|
||||
|
||||
/** Shipment booking created under a contract (Path A customer, Path B GL ET). */
|
||||
@@ -614,6 +653,39 @@ export interface IBookingRequest extends BaseEntity {
|
||||
reviewedByStaffId?: string | null;
|
||||
reviewedAt?: string | null;
|
||||
reviewNote?: string | null;
|
||||
/** Loaded contract relation (request detail response includes it). */
|
||||
contract?: BookingRequestContract | null;
|
||||
}
|
||||
|
||||
/** Customer (company) summary carried on a request's contract. */
|
||||
export interface BookingRequestCompany {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
tin?: string | null;
|
||||
email?: string | null;
|
||||
phone?: string | null;
|
||||
address?: string | null;
|
||||
contactPersonName?: string | null;
|
||||
contactPersonPhone?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The slice of the contract surfaced on the shipment-request detail page:
|
||||
* identity, service type (mile/customs flags), customer, routes and cargo scope.
|
||||
*/
|
||||
export interface BookingRequestContract {
|
||||
id: string;
|
||||
reference: string;
|
||||
contractKind: ContractKind;
|
||||
tradeDirection: ContractTradeDirection;
|
||||
freightType: ContractFreightType;
|
||||
customsClearingEnabled: boolean;
|
||||
paymentCurrency: string;
|
||||
contractValidUntil?: string | null;
|
||||
company?: BookingRequestCompany | null;
|
||||
serviceType?: IContract["serviceType"];
|
||||
routes?: IContractRoute[];
|
||||
cargoScope?: IContractCargoScope[];
|
||||
}
|
||||
|
||||
export interface CreateBookingRequestDto {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { BaseEntity } from "../common";
|
||||
import { ClearanceNextAction, ContractDocPhase, IClearanceMilestone } from "./contracts";
|
||||
|
||||
export * from "./dropdown_settings";
|
||||
export * from "./file_upload_settings";
|
||||
export * from "./overview";
|
||||
export * from "./etrade";
|
||||
export * from "./contracts";
|
||||
export * from "./clearance-files.catalog";
|
||||
|
||||
export enum TradeDirection {
|
||||
IMPORT = "IMPORT",
|
||||
@@ -141,6 +143,8 @@ export enum InvoiceStatus {
|
||||
Overdue = "OVERDUE",
|
||||
Cancelled = "CANCELLED",
|
||||
Refunded = "REFUNDED",
|
||||
/** Pay window closed before settlement; terminal, cannot be paid. */
|
||||
Expired = "EXPIRED",
|
||||
}
|
||||
|
||||
/** Originating subsystem an invoice bills for; namespaces invoice events. */
|
||||
@@ -148,14 +152,8 @@ export enum InvoiceSource {
|
||||
Booking = "booking",
|
||||
Warehouse = "warehouse",
|
||||
Demurrage = "demurrage",
|
||||
}
|
||||
|
||||
/**
|
||||
* What an invoice bills for within its source — the discriminator when one
|
||||
* entity carries several invoices (e.g. a booking's up-front vs final charge).
|
||||
*/
|
||||
export enum InvoiceType {
|
||||
Prepaid = "PREPAID",
|
||||
FirstMile = "firstmile",
|
||||
LastMile = "lastmile"
|
||||
}
|
||||
|
||||
export enum SchedulingStatus {
|
||||
@@ -443,6 +441,10 @@ export interface IBooking extends BaseEntity {
|
||||
|
||||
isHazardous: boolean;
|
||||
isRefrigerated: boolean;
|
||||
/** Bulk-only hazardous amount in the cargo's unit (tons/items); 0 otherwise. */
|
||||
bulkHazardousQuantity?: number;
|
||||
/** Bulk-only refrigerated amount in the cargo's unit (tons/items); 0 otherwise. */
|
||||
bulkReeferQuantity?: number;
|
||||
|
||||
tradeDirection: "IMPORT" | "EXPORT";
|
||||
paymentCurrency: string;
|
||||
@@ -533,6 +535,26 @@ export interface ClearanceView {
|
||||
documents: ClearanceDocument[];
|
||||
/** True once every required customer document is APPROVED (the 100% gate). */
|
||||
allApproved: boolean;
|
||||
/** Phased clearance (GENERAL + customs per-booking). */
|
||||
phase?: ContractDocPhase | null;
|
||||
milestones?: IClearanceMilestone[];
|
||||
nextAction?: ClearanceNextAction | null;
|
||||
dutyRequired?: boolean | null;
|
||||
roHold?: boolean;
|
||||
roHoldReason?: string | null;
|
||||
vesselDepartureDate?: string | null;
|
||||
roAmendmentRequestedAt?: string | null;
|
||||
/** Boundary milestone complete — customer may proceed to operations. */
|
||||
operationReady?: boolean;
|
||||
preClearanceFinalized?: boolean;
|
||||
dutyAdvice?: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
declarationSerial?: string | null;
|
||||
noticeFile?: { id: string; name: string; url: string } | null;
|
||||
} | null;
|
||||
/** Phased customs uploads (IM4, DO, transit permit, etc.) with friendly labels. */
|
||||
workflowFiles?: import("./clearance-files.catalog").ClearanceWorkflowFile[];
|
||||
}
|
||||
|
||||
/** Company an invoice is billed to (minimal projection). */
|
||||
@@ -571,6 +593,10 @@ export interface IInvoice extends BaseEntity {
|
||||
companyProfileId: string;
|
||||
companyProfile?: IInvoiceCompanyProfile;
|
||||
totalAmount: number;
|
||||
/** Cumulative amount settled so far (supports partial payment). */
|
||||
paidAmount: number;
|
||||
/** Outstanding balance = totalAmount - paidAmount (0 once fully paid). */
|
||||
balanceAmount: number;
|
||||
currency: string;
|
||||
status: InvoiceStatus;
|
||||
/** Originating subsystem: booking / warehouse / demurrage. */
|
||||
@@ -638,7 +664,6 @@ export interface BookingReferenceCargoTypeChild {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
show_free_text_box: boolean;
|
||||
/** How this cargo is measured (PER_TON / PER_ITEM); null when unset. */
|
||||
unit_of_measure?: CargoUnitOfMeasure | null;
|
||||
}
|
||||
@@ -728,6 +753,10 @@ export interface CreateBookingContainerDto {
|
||||
containerTypeId: string;
|
||||
quantity: number;
|
||||
vgmPerUnitTons: number;
|
||||
/** How many of this line's containers are hazardous (0..quantity). */
|
||||
hazardousQuantity?: number;
|
||||
/** How many of this line's containers are refrigerated (0..quantity). */
|
||||
reeferQuantity?: number;
|
||||
}
|
||||
|
||||
/** A contracted route+quantity line for a GENERAL contract. */
|
||||
@@ -779,6 +808,10 @@ export interface CreateBookingDto {
|
||||
isHazardous?: boolean | undefined;
|
||||
/** Booking-level refrigerated flag (bulk freight only; containers derive reefer from the container type). */
|
||||
isReefer?: boolean | undefined;
|
||||
/** Bulk-only: hazardous amount in the cargo's unit (tons or items), <= cargoTotalWeightVgm. */
|
||||
bulkHazardousQuantity?: number | undefined;
|
||||
/** Bulk-only: refrigerated amount in the cargo's unit (tons or items), <= cargoTotalWeightVgm. */
|
||||
bulkReeferQuantity?: number | undefined;
|
||||
paymentCurrency: string;
|
||||
pnrCode?: string | undefined;
|
||||
startDate?: string | undefined;
|
||||
|
||||
@@ -16,6 +16,8 @@ export interface OperationDatePickerProps {
|
||||
value: string;
|
||||
/** Called with the picked `yyyy-MM-dd` day. */
|
||||
onChange: (date: string) => void;
|
||||
/** Stretch to the full width of the parent container. */
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
/** `yyyy-MM-dd` for a local date. */
|
||||
@@ -41,6 +43,8 @@ const MONTH_NAMES = [
|
||||
"December",
|
||||
];
|
||||
|
||||
const WEEKDAY_LABELS = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];
|
||||
|
||||
/**
|
||||
* Presentational month calendar for picking a binding shipment day. Only the
|
||||
* `availableDays` (passed in by the caller, which owns the query) are
|
||||
@@ -53,8 +57,8 @@ export function OperationDatePicker({
|
||||
isLoading = false,
|
||||
value,
|
||||
onChange,
|
||||
fullWidth = false,
|
||||
}: OperationDatePickerProps) {
|
||||
// First-of-month for the visible month; defaults to the current month.
|
||||
const [month, setMonth] = useState(() => {
|
||||
const now = new Date();
|
||||
return new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
@@ -67,7 +71,6 @@ export function OperationDatePicker({
|
||||
|
||||
const cells = useMemo(() => {
|
||||
const first = new Date(month.getFullYear(), month.getMonth(), 1);
|
||||
// Monday-first grid: JS getDay() Sun=0..Sat=6 → shift so Mon=0.
|
||||
const lead = (first.getDay() + 6) % 7;
|
||||
const start = new Date(first);
|
||||
start.setDate(first.getDate() - lead);
|
||||
@@ -90,6 +93,11 @@ export function OperationDatePicker({
|
||||
});
|
||||
}, [month, departureDays, value]);
|
||||
|
||||
const availableInMonth = useMemo(
|
||||
() => cells.filter((c) => c.inMonth && c.hasDeparture).length,
|
||||
[cells],
|
||||
);
|
||||
|
||||
const shiftMonth = (delta: number) =>
|
||||
setMonth((m) => new Date(m.getFullYear(), m.getMonth() + delta, 1));
|
||||
|
||||
@@ -98,11 +106,25 @@ export function OperationDatePicker({
|
||||
style={{
|
||||
border: "1px solid #E6ECF2",
|
||||
borderRadius: 12,
|
||||
padding: 14,
|
||||
maxWidth: 340,
|
||||
padding: fullWidth ? "12px 16px" : 14,
|
||||
width: fullWidth ? "100%" : undefined,
|
||||
maxWidth: fullWidth ? undefined : 340,
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between" align="center" mb="sm">
|
||||
<Group
|
||||
justify="space-between"
|
||||
align="center"
|
||||
mb="sm"
|
||||
px={fullWidth ? 4 : 0}
|
||||
style={
|
||||
fullWidth
|
||||
? {
|
||||
borderBottom: "1px solid #EEF2F6",
|
||||
paddingBottom: 10,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="default"
|
||||
size="xs"
|
||||
@@ -112,9 +134,7 @@ export function OperationDatePicker({
|
||||
>
|
||||
<ChevronLeft size={15} />
|
||||
</Button>
|
||||
<Text fz="13px" fw={700} c="#10202F">
|
||||
{MONTH_NAMES[month.getMonth()]} {month.getFullYear()}
|
||||
</Text>
|
||||
<StackedMonthHeader month={month} availableCount={availableInMonth} />
|
||||
<Button
|
||||
variant="default"
|
||||
size="xs"
|
||||
@@ -143,8 +163,8 @@ export function OperationDatePicker({
|
||||
marginBottom: 6,
|
||||
}}
|
||||
>
|
||||
{["M", "T", "W", "T", "F", "S", "S"].map((d, i) => (
|
||||
<Text key={i} ta="center" fz="10px" fw={700} c="#9AA8B5">
|
||||
{WEEKDAY_LABELS.map((d) => (
|
||||
<Text key={d} ta="center" fz="10px" fw={700} c="#9AA8B5">
|
||||
{d}
|
||||
</Text>
|
||||
))}
|
||||
@@ -173,9 +193,11 @@ export function OperationDatePicker({
|
||||
cursor: clickable ? "pointer" : "default",
|
||||
border: c.selected
|
||||
? "1.5px solid #12B981"
|
||||
: clickable
|
||||
? "1px solid #CDEBDD"
|
||||
: "1px solid transparent",
|
||||
: c.today
|
||||
? "1.5px solid #94A3B8"
|
||||
: clickable
|
||||
? "1px solid #CDEBDD"
|
||||
: "1px solid transparent",
|
||||
background: c.selected
|
||||
? "#12B981"
|
||||
: clickable
|
||||
@@ -224,18 +246,30 @@ export function OperationDatePicker({
|
||||
})}
|
||||
</Box>
|
||||
{value && (
|
||||
<Text fz="12px" c="#0A6F4D" fw={600} mt="sm">
|
||||
Selected:{" "}
|
||||
{new Date(value + "T00:00:00").toLocaleDateString(undefined, {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</Text>
|
||||
<Group
|
||||
justify="center"
|
||||
mt="sm"
|
||||
py={6}
|
||||
px={10}
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
background: "#F4FBF7",
|
||||
border: "1px solid #CDEBDD",
|
||||
}}
|
||||
>
|
||||
<Text fz="12px" c="#0A6F4D" fw={600}>
|
||||
Selected:{" "}
|
||||
{new Date(value + "T00:00:00").toLocaleDateString(undefined, {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{departureDays.size === 0 && (
|
||||
<Text fz="12px" c="orange.7" mt="sm">
|
||||
<Text fz="12px" c="orange.7" mt="sm" ta="center">
|
||||
No scheduled departures found for this route yet.
|
||||
</Text>
|
||||
)}
|
||||
@@ -245,4 +279,23 @@ export function OperationDatePicker({
|
||||
);
|
||||
}
|
||||
|
||||
function StackedMonthHeader({
|
||||
month,
|
||||
availableCount,
|
||||
}: {
|
||||
month: Date;
|
||||
availableCount: number;
|
||||
}) {
|
||||
return (
|
||||
<Box style={{ textAlign: "center" }}>
|
||||
<Text fz="14px" fw={800} c="#10202F" lh={1.2}>
|
||||
{MONTH_NAMES[month.getMonth()]} {month.getFullYear()}
|
||||
</Text>
|
||||
<Text fz="11px" c="#64748B" mt={2}>
|
||||
{availableCount} available day{availableCount === 1 ? "" : "s"}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default OperationDatePicker;
|
||||
|
||||
Reference in New Issue
Block a user