Merge remote-tracking branch 'origin/prod_v2' into integration

This commit is contained in:
MMD
2026-09-20 20:35:19 +03:30
6 changed files with 201 additions and 71 deletions
+70 -34
View File
@@ -1,15 +1,27 @@
import { ClipboardList, Inbox, CheckCircle2, Timer, type LucideIcon } from "lucide-react";
import { ClipboardList, Inbox, CheckCircle2, PackageCheck, type LucideIcon } from "lucide-react";
import { fmtNum } from "../lib/formatters";
export type StatusFilter = "all" | "offered" | "accepted" | "arrived";
export interface StatsBarProps {
total: number;
offered: number;
accepted: number;
avgLeadTime: number | null; // روز
arrived: number;
activeFilter?: StatusFilter;
onFilterChange?: (filter: StatusFilter) => void;
}
export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProps) {
export function StatsBar({
total,
offered,
accepted,
arrived,
activeFilter = "all",
onFilterChange,
}: StatsBarProps) {
const stats: {
key: StatusFilter;
label: string;
shortLabel: string;
value: string;
@@ -18,6 +30,7 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
icon: LucideIcon;
}[] = [
{
key: "all",
label: "کل پیشنهادها",
shortLabel: "کل",
value: fmtNum(total),
@@ -25,6 +38,7 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
icon: ClipboardList,
},
{
key: "offered",
label: "پیشنهاد باز",
shortLabel: "باز",
value: fmtNum(offered),
@@ -32,6 +46,7 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
icon: Inbox,
},
{
key: "accepted",
label: "پذیرفته‌شده",
shortLabel: "پذیرفته",
value: fmtNum(accepted),
@@ -39,48 +54,69 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
icon: CheckCircle2,
},
{
label: "میانگین زمان تحویل",
shortLabel: "میانگین تحویل",
value: avgLeadTime == null ? "—" : fmtNum(avgLeadTime),
unit: avgLeadTime != null ? "روز" : undefined,
key: "arrived",
label: "تحویل‌شده به کارگاه",
shortLabel: "تحویل",
value: fmtNum(arrived),
color: "text-green",
icon: Timer,
icon: PackageCheck,
},
];
const handleClick = (key: StatusFilter) => {
if (!onFilterChange) return;
if (key === "all") {
onFilterChange("all");
} else {
onFilterChange(activeFilter === key ? "all" : key);
}
};
return (
<section className="grid grid-cols-4 gap-1.5 sm:gap-3">
{stats.map((s) => (
<div
key={s.label}
className="rounded-xl border border-line bg-panel p-2 text-center transition-colors hover:border-accent/40 sm:rounded-lg sm:p-3.5 sm:text-right"
>
{/* عنوان کارت */}
<div className="flex items-center justify-center sm:justify-between">
<span className="truncate text-[10px] font-medium text-muted sm:text-[11px]">
<span className="sm:hidden">{s.shortLabel}</span>
<span className="hidden sm:inline">{s.label}</span>
</span>
{stats.map((s) => {
const isClickable = Boolean(onFilterChange);
const isActive = activeFilter === s.key;
{/* آیکون در دسکتاپ */}
<span className="hidden h-6 w-6 shrink-0 items-center justify-center rounded-md bg-slate-bg text-muted sm:flex">
<s.icon className="h-3.5 w-3.5" strokeWidth={2} />
</span>
</div>
{/* مقدار عددی به همراه واحد روز */}
return (
<div
className={`mt-0.5 flex items-baseline justify-center sm:justify-start gap-1 text-base font-extrabold leading-5 tabular-nums sm:mt-1 sm:text-[22px] sm:leading-8 ${s.color}`}
key={s.label}
onClick={() => handleClick(s.key)}
className={`rounded-xl border p-2 text-center transition-all sm:rounded-lg sm:p-3.5 sm:text-right ${
isClickable ? "cursor-pointer select-none" : ""
} ${
isActive
? "border-accent ring-2 ring-accent/20 bg-accent/5 shadow-sm"
: "border-line bg-panel hover:border-accent/40"
}`}
>
<span>{s.value}</span>
{s.unit && (
<span className="text-[10px] font-medium text-muted sm:text-xs">
{s.unit}
{/* عنوان کارت */}
<div className="flex items-center justify-center sm:justify-between">
<span className="truncate text-[10px] font-medium text-muted sm:text-[11px]">
<span className="sm:hidden">{s.shortLabel}</span>
<span className="hidden sm:inline">{s.label}</span>
</span>
)}
{/* آیکون در دسکتاپ */}
<span className="hidden h-6 w-6 shrink-0 items-center justify-center rounded-md bg-slate-bg text-muted sm:flex">
<s.icon className="h-3.5 w-3.5" strokeWidth={2} />
</span>
</div>
{/* مقدار عددی */}
<div
className={`mt-0.5 flex items-baseline justify-center sm:justify-start gap-1 text-base font-extrabold leading-5 tabular-nums sm:mt-1 sm:text-[22px] sm:leading-8 ${s.color}`}
>
<span>{s.value}</span>
{s.unit && (
<span className="text-[10px] font-medium text-muted sm:text-xs">
{s.unit}
</span>
)}
</div>
</div>
</div>
))}
);
})}
</section>
);
}