shoc-frontend-new/src/app/v/[token]/dispatch/_components/dispatch-snapshot-details.tsx
2026-08-10 13:16:26 -03:00

166 lines
4.6 KiB
TypeScript

import { Text } from "@/components/ui/text";
import { formatVendorPortalDateTime } from "@/domain/vendor-portal/lib/status-helpers";
import type { VendorPortalDispatchDetail } from "@/domain/vendor-portal/types/vendor-portal";
type DispatchSnapshotDetailsProps = {
data: VendorPortalDispatchDetail;
locked: boolean;
};
function resolveCanonicalService(workOrder: VendorPortalDispatchDetail["workOrder"]): string {
if (workOrder?.service) {
return workOrder.service;
}
return [workOrder?.trade, workOrder?.subTrade].filter(Boolean).join(" · ");
}
function DispatchCommercialDetails({ data }: Pick<DispatchSnapshotDetailsProps, "data">) {
const service = resolveCanonicalService(data.workOrder);
return (
<div style={{ marginTop: 12 }}>
{data.workOrder != null && Boolean(data.workOrder.internalWONumber) && (
<div className="vp-row">
<strong>WO #</strong>
<span>{data.workOrder.internalWONumber}</span>
</div>
)}
{Boolean(service) && (
<div className="vp-row">
<strong>Service</strong>
<span>{service}</span>
</div>
)}
{Boolean(data.poNumber) && (
<div className="vp-row">
<strong>PO #</strong>
<span>{data.poNumber}</span>
</div>
)}
{data.nteAmount != null && (
<div className="vp-row">
<strong>NTE</strong>
<span>${Number(data.nteAmount).toFixed(2)}</span>
</div>
)}
<div className="vp-row">
<strong>Invoice</strong>
<span>
{data.invoiceStatus || "Not Submitted"}
{data.invoiceNumber ? ` · ${data.invoiceNumber}` : ""}
</span>
</div>
<div className="vp-row">
<strong>Payment</strong>
<span>{data.paymentStatus || "Unavailable"}</span>
</div>
{Boolean(data.scheduledDate) && (
<div className="vp-row">
<strong>Scheduled</strong>
<span>{formatVendorPortalDateTime(data.scheduledDate)}</span>
</div>
)}
{Boolean(data.statusUpdatedAt) && (
<div className="vp-row">
<strong>Status updated</strong>
<span>{formatVendorPortalDateTime(data.statusUpdatedAt)}</span>
</div>
)}
</div>
);
}
function DispatchLocationDetails({ data }: Pick<DispatchSnapshotDetailsProps, "data">) {
if (data.location == null) {
return null;
}
const address = [
data.location.address,
data.location.city,
data.location.state,
data.location.zipCode,
]
.filter(Boolean)
.join(", ");
return (
<>
<Text variant="heading" sx={{ marginTop: 2 }}>
Location
</Text>
{Boolean(data.workOrder?.siteCode) && (
<div className="vp-row">
<strong>Site code</strong>
<span>{data.workOrder?.siteCode}</span>
</div>
)}
<div>{data.location.name}</div>
<div className="vp-muted">{address}</div>
</>
);
}
function DispatcherContactDetails({ data }: Pick<DispatchSnapshotDetailsProps, "data">) {
const contact = data.dispatcherContact;
if (!contact || (!contact.name && !contact.email && !contact.phone)) {
return null;
}
return (
<>
<Text variant="heading" sx={{ marginTop: 2 }}>
Dispatcher
</Text>
{Boolean(contact.name) && (
<div className="vp-row">
<strong>Name</strong>
<span>{contact.name}</span>
</div>
)}
{Boolean(contact.email) && (
<div className="vp-row">
<strong>Email</strong>
<span>
<a href={`mailto:${contact.email}`}>{contact.email}</a>
</span>
</div>
)}
{Boolean(contact.phone) && (
<div className="vp-row">
<strong>Phone</strong>
<span>
<a href={`tel:${contact.phone}`}>{contact.phone}</a>
</span>
</div>
)}
</>
);
}
export function DispatchSnapshotDetails({ data, locked }: DispatchSnapshotDetailsProps) {
const description = data.description || data.workOrder?.description;
return (
<>
<DispatchCommercialDetails data={data} />
<DispatchLocationDetails data={data} />
<DispatcherContactDetails data={data} />
{locked && (
<Text variant="feedback" className="vp-muted" sx={{ marginTop: 2 }}>
This dispatch is {data.status.toLowerCase()} and locked to further changes. Comments
remain open.
</Text>
)}
{Boolean(description) && (
<>
<Text variant="heading" sx={{ marginTop: 2 }}>
Description
</Text>
<div>{description}</div>
</>
)}
</>
);
}