124 lines
4.2 KiB
TypeScript
124 lines
4.2 KiB
TypeScript
// ============================================================
|
|
// ویجت ۸ — تراکم پروژهها (Project Density)
|
|
// نمودار میلهای افقی: توزیع بار تدارکات روی پروژههای فعال —
|
|
// سفارشهای باز (ستهشده با پیشنهادهای باز).
|
|
// ============================================================
|
|
|
|
import { useMemo } from "react";
|
|
import {
|
|
Bar,
|
|
BarChart,
|
|
CartesianGrid,
|
|
Legend,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
XAxis,
|
|
YAxis,
|
|
} from "recharts";
|
|
import { useProcurementWorkload } from "../../hooks/useDashboardData";
|
|
import { faNum } from "../../lib/formatters";
|
|
import {
|
|
CHART_COLORS,
|
|
ChartCard,
|
|
ChartEmpty,
|
|
ChartLoading,
|
|
ChartTooltip,
|
|
faAxisTick,
|
|
} from "./chartShared";
|
|
|
|
export function ProjectDensityChart() {
|
|
const query = useProcurementWorkload();
|
|
|
|
const rows = useMemo(() => {
|
|
const projects = query.data?.projects ?? [];
|
|
return projects
|
|
.filter((p) => p.open_orders > 0 || p.offered_quotes > 0 || p.chosen_items > 0)
|
|
.map((p) => ({
|
|
name: p.name,
|
|
"سفارش باز (در راه)": p.open_orders,
|
|
"پیشنهاد باز": p.offered_quotes,
|
|
_ordered: p.ordered_qty,
|
|
_received: p.received_qty,
|
|
}))
|
|
.sort((a, b) => (b["سفارش باز (در راه)"] + b["پیشنهاد باز"]) - (a["سفارش باز (در راه)"] + a["پیشنهاد باز"]));
|
|
}, [query.data]);
|
|
|
|
return (
|
|
<ChartCard
|
|
title="تراکم بار تدارکات روی پروژهها"
|
|
sub="تعداد سفارشهای باز و پیشنهادهای در جریان به تفکیک پروژه"
|
|
>
|
|
{query.isLoading ? (
|
|
<ChartLoading />
|
|
) : rows.length === 0 ? (
|
|
<ChartEmpty message="بار تدارکاتی فعالی روی پروژهها وجود ندارد" />
|
|
) : (
|
|
<div dir="ltr" className="w-full" style={{ height: Math.max(180, rows.length * 56 + 60) }}>
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<BarChart
|
|
data={rows}
|
|
layout="vertical"
|
|
margin={{ top: 8, right: 16, left: 8, bottom: 4 }}
|
|
barCategoryGap="28%"
|
|
>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#e5e9f0" horizontal={false} />
|
|
<XAxis
|
|
type="number"
|
|
tick={{ fontSize: 11, fill: "#64748b" }}
|
|
tickFormatter={faAxisTick}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
allowDecimals={false}
|
|
/>
|
|
<YAxis
|
|
type="category"
|
|
dataKey="name"
|
|
width={150}
|
|
tick={{ fontSize: 11, fill: "#0f172a", fontFamily: "Vazirmatn" }}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
/>
|
|
<Tooltip
|
|
cursor={{ fill: "rgba(79, 70, 229, 0.05)" }}
|
|
content={
|
|
<ChartTooltip
|
|
labelFormatter={(l) => String(l)}
|
|
valueFormatter={(v) => `${faNum(v)} مورد`}
|
|
/>
|
|
}
|
|
/>
|
|
<Legend
|
|
wrapperStyle={{ direction: "rtl", width: "100%" }}
|
|
verticalAlign="top"
|
|
height={26}
|
|
iconType="circle"
|
|
iconSize={8}
|
|
formatter={(value: unknown) => (
|
|
<span dir="rtl" className="text-[11px] text-muted">
|
|
{String(value)}
|
|
</span>
|
|
)}
|
|
/>
|
|
<Bar
|
|
dataKey="سفارش باز (در راه)"
|
|
stackId="load"
|
|
fill={CHART_COLORS.blue}
|
|
maxBarSize={22}
|
|
isAnimationActive={false}
|
|
/>
|
|
<Bar
|
|
dataKey="پیشنهاد باز"
|
|
stackId="load"
|
|
fill={CHART_COLORS.amber}
|
|
radius={[0, 4, 4, 0]}
|
|
maxBarSize={22}
|
|
isAnimationActive={false}
|
|
/>
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
)}
|
|
</ChartCard>
|
|
);
|
|
}
|