126 lines
5.1 KiB
TypeScript
126 lines
5.1 KiB
TypeScript
// ============================================================
|
|
// ویجت ۶ — شبکهی وضعیت سفارشهای در راه
|
|
// جدول کمی سفارشهای باز: متریال و تأمینکننده | سفارششده | رسیده
|
|
// | وضعیت (در راه / تحویل جزئی / تحویل کامل) + نوار پیشرفت.
|
|
// داده از /pm/on-the-way (با برچسب برند و مشخصات و فیلتر سراسری).
|
|
// ============================================================
|
|
|
|
import { Truck } from "lucide-react";
|
|
import { useInTransit } from "../../hooks/useDashboardData";
|
|
import type { InTransitRow } from "../../lib/types";
|
|
import { faNum } from "../../lib/formatters";
|
|
import { cn } from "../../lib/utils";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "../ui/table";
|
|
import { Badge } from "../ui/badge";
|
|
import { ChartCard, ChartEmpty, ChartLoading } from "./chartShared";
|
|
|
|
const STATE_META: Record<
|
|
InTransitRow["state"],
|
|
{ label: string; cls: string; bar: string }
|
|
> = {
|
|
on_the_way: { label: "در راه", cls: "bg-amber-bg text-amber", bar: "bg-amber" },
|
|
partially_received: { label: "تحویل جزئی", cls: "bg-blue-bg text-[#1d4ed8]", bar: "bg-[#2563eb]" },
|
|
arrived: { label: "تحویل کامل", cls: "bg-green-bg text-green", bar: "bg-green" },
|
|
};
|
|
|
|
function specLabel(specs: Record<string, string>): string {
|
|
const parts = Object.entries(specs || {});
|
|
return parts.length ? parts.map(([k, v]) => `${k}: ${v}`).join("، ") : "";
|
|
}
|
|
|
|
function num(v: string): number {
|
|
const n = Number(v);
|
|
return Number.isFinite(n) ? n : 0;
|
|
}
|
|
|
|
export function InTransitGrid() {
|
|
const query = useInTransit();
|
|
const rows = query.data ?? [];
|
|
|
|
return (
|
|
<ChartCard
|
|
title={
|
|
<span className="inline-flex items-center gap-1.5">
|
|
<Truck className="h-4 w-4 text-muted" />
|
|
سفارشهای در راه — وضعیت تحویل
|
|
</span>
|
|
}
|
|
sub="اقلام پذیرفتهشدهی خرید تا رسیدن کامل به انبار"
|
|
>
|
|
{query.isLoading ? (
|
|
<ChartLoading />
|
|
) : rows.length === 0 ? (
|
|
<ChartEmpty message="سفارش بازی در جریان نیست" />
|
|
) : (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow className="hover:bg-transparent">
|
|
<TableHead>متریال / تأمینکننده</TableHead>
|
|
<TableHead className="text-end">سفارششده</TableHead>
|
|
<TableHead className="text-end">رسیده تاکنون</TableHead>
|
|
<TableHead className="min-w-[140px]">پیشرفت</TableHead>
|
|
<TableHead>وضعیت</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((r) => {
|
|
const ordered = num(r.ordered_qty);
|
|
const received = num(r.received_qty);
|
|
const pct = ordered > 0 ? Math.min(100, Math.round((received / ordered) * 100)) : 0;
|
|
const meta = STATE_META[r.state] ?? STATE_META.on_the_way;
|
|
const specs = specLabel(r.specs);
|
|
return (
|
|
<TableRow key={r.item_decision_id}>
|
|
<TableCell>
|
|
<div className="text-[13px] font-bold">{r.material.name}</div>
|
|
<div className="text-[11px] text-muted">
|
|
{r.supplier.name}
|
|
{r.brand ? ` · ${r.brand.name}` : ""}
|
|
</div>
|
|
{specs && <div className="text-[10.5px] text-muted">{specs}</div>}
|
|
{r.project && (
|
|
<div className="mt-0.5 text-[10.5px] text-accent">{r.project.name}</div>
|
|
)}
|
|
</TableCell>
|
|
<TableCell className="text-end font-bold tabular-nums">
|
|
{faNum(ordered.toLocaleString("en-US"))}
|
|
<div className="text-[10px] font-normal text-muted">
|
|
{r.unit?.name_fa || r.unit?.code}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-end font-bold tabular-nums">
|
|
{faNum(received.toLocaleString("en-US"))}
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-2">
|
|
<div className="h-1.5 w-full overflow-hidden rounded-full bg-slate-bg">
|
|
<div
|
|
className={cn("h-full rounded-full", meta.bar)}
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
<span className="shrink-0 text-[10.5px] tabular-nums text-muted">
|
|
{faNum(pct)}٪
|
|
</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge className={cn("px-2 py-0.5 text-[10.5px]", meta.cls)}>{meta.label}</Badge>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
)}
|
|
</ChartCard>
|
|
);
|
|
}
|