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

154 lines
5.6 KiB
TypeScript

import { useMemo, useState } from "react";
import { useNavigate } from "react-router";
import { useQuery } from "@tanstack/react-query";
import { useVendorPortal } from "@/app/v/_components/vendor-portal-context";
import { Text } from "@/components/ui/text";
import { vendorPortalApi } from "@/domain/vendor-portal/api/vendor-portal-api";
import {
formatVendorPortalDateTime,
vendorPortalStatusClass,
} from "@/domain/vendor-portal/lib/status-helpers";
import {
VENDOR_PORTAL_DISPATCH_FILTERS,
applyVendorPortalDispatchFilter,
getVendorPortalDispatchFilter,
type VendorPortalDispatchFilterKey,
} from "@/domain/vendor-portal/lib/dispatch-filters";
import { queryKeys } from "@/infra/query-key/query-key";
function formatDate(value?: string) {
if (!value) return "—";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "—";
return date.toISOString().slice(0, 10);
}
function formatMoney(value?: number) {
if (value == null) return "—";
return `$${Number(value).toFixed(2)}`;
}
export default function VendorPortalPosPage() {
const { token } = useVendorPortal();
const navigate = useNavigate();
const [filter, setFilter] = useState<VendorPortalDispatchFilterKey>("open");
const [search, setSearch] = useState("");
const {
data: dispatches = [],
isLoading,
error,
} = useQuery({
queryKey: queryKeys.vendorPortal.dispatches(token),
queryFn: () => vendorPortalApi.listDispatches(token),
enabled: Boolean(token),
});
const activeFilter = getVendorPortalDispatchFilter(filter);
const visible = useMemo(() => {
const needle = search.trim().toLowerCase();
return applyVendorPortalDispatchFilter(dispatches, activeFilter).filter((dispatch) => {
if (!needle) return true;
return (
(dispatch.poNumber || "").toLowerCase().includes(needle) ||
(dispatch.dispatchNumber || "").toLowerCase().includes(needle) ||
(dispatch.internalWONumber || "").toLowerCase().includes(needle) ||
(dispatch.workOrderTitle || "").toLowerCase().includes(needle) ||
(dispatch.locationName || "").toLowerCase().includes(needle)
);
});
}, [dispatches, activeFilter, search]);
return (
<>
<div className="vp-card">
<Text variant="title">Your Purchase Orders</Text>
<div className="vp-muted" style={{ marginBottom: 12 }}>
Every PO issued to your company, with current status and dollar value.
</div>
<div
className="vp-actions"
style={{ marginTop: 0, gap: 12, alignItems: "center", flexWrap: "wrap" }}
>
{VENDOR_PORTAL_DISPATCH_FILTERS.map((item) => (
<button
key={item.key}
type="button"
className={`vp-btn ${filter === item.key ? "vp-btn-primary" : "vp-btn-ghost"}`}
onClick={() => setFilter(item.key)}
>
{item.label}{" "}
<span style={{ opacity: 0.7, fontWeight: 400 }}>
({dispatches.filter(item.match).length})
</span>
</button>
))}
<input
className="vp-input"
placeholder="Search PO #, WO, location…"
value={search}
onChange={(event) => setSearch(event.target.value)}
style={{ marginLeft: "auto", minWidth: 240 }}
/>
</div>
</div>
{isLoading && <div className="vp-card vp-muted">Loading POs…</div>}
<Text variant="error" when={Boolean(error)} className="vp-error">
{error?.message}
</Text>
{!isLoading && !error && visible.length === 0 && (
<div className="vp-card vp-empty">No POs match this view.</div>
)}
{!isLoading && !error && visible.length > 0 && (
<div className="vp-card" style={{ padding: 0 }}>
<table className="vp-po-table">
<thead>
<tr>
<th>PO #</th>
<th>Dispatch #</th>
<th>Status</th>
<th>NTE</th>
<th>Location</th>
<th>Dispatched</th>
<th>Scheduled</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{visible.map((dispatch) => (
<tr key={dispatch.id} onClick={() => navigate(`../dispatch/${dispatch.id}`)}>
<td>
<span className="vp-po-number">{dispatch.poNumber || "—"}</span>
</td>
<td className="vp-po-muted-cell">{dispatch.dispatchNumber}</td>
<td>
<span className={vendorPortalStatusClass(dispatch.status)}>
{dispatch.status}
</span>
</td>
<td>{formatMoney(dispatch.nteAmount)}</td>
<td className="vp-po-muted-cell">
{dispatch.locationName || "—"}
{dispatch.locationCity ? ` · ${dispatch.locationCity}` : ""}
{dispatch.locationState ? `, ${dispatch.locationState}` : ""}
</td>
<td className="vp-po-muted-cell">
{dispatch.dispatchedAt
? formatVendorPortalDateTime(dispatch.dispatchedAt)
: "—"}
</td>
<td className="vp-po-muted-cell">{formatDate(dispatch.scheduledDate)}</td>
<td className="vp-po-muted-cell">{formatDate(dispatch.completedDate)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
);
}