// ============================================================ // ویجت ۱ — ماتریس Drill-Down سه‌سطحی (سبک Power BI) // سطح ۱ «کل سازمان» → یک ستون: تجمیع موجودی همه‌ی انبارهای سازمان // سطح ۲ «پروژه‌ها» → با کلیک روی سطح ۱: سهم هر پروژه // سطح ۳ «انبارها» → با کلیک روی هر پروژه: انبارهای همان پروژه // همه‌ی سطوح از پاسخ /dashboard/inventory/drilldown ساخته می‌شوند — // هیچ مقدار/نام/وضعیتی در فرانت‌اند هارد‌کد نشده است. اگر پروژه‌ی // انتخاب‌شده با تغییر فیلترها از داده حذف شود، سطح خودکار به بالا برمی‌گردد. // ============================================================ import { useEffect, useMemo, useState } from "react"; import { Bar, BarChart, CartesianGrid, Cell, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts"; import { ChevronLeft, MousePointerClick } from "lucide-react"; import { useDrilldown } from "../../hooks/useDashboardData"; import { fmtNum } from "../../lib/formatters"; import { CHART_COLORS, ChartCard, ChartEmpty, ChartLoading, ChartTooltip, UnitPicker, faAxisTick, } from "./chartShared"; type Level = 0 | 1 | 2; // 0=سازمان، 1=پروژه‌ها، 2=انبارها interface Slice { id: string; name: string; value: number; rows: number; } const LEVEL_TITLES: Record = { 0: "کل سازمان", 1: "پروژه‌ها", 2: "انبارها", }; // پالت سطح انبارها — سایه‌های ایندیگو برای تمایز انبارهای یک پروژه const WAREHOUSE_PALETTE = ["#6366f1", "#818cf8", "#a5b4fc", "#c7d2fe"]; export function DrilldownMatrix() { const query = useDrilldown(); const [unit, setUnit] = useState(null); const [level, setLevel] = useState(0); const [projectId, setProjectId] = useState(null); const data = query.data; // واحد فعال: انتخاب کاربر یا بزرگ‌ترین مجموع (پایدار بین رندرها) const activeUnit = useMemo(() => { if (unit && data?.units?.[unit]) return unit; const entries = Object.entries(data?.total?.qty_by_unit ?? {}); if (!entries.length) return null; return entries.sort((a, b) => b[1] - a[1])[0][0]; }, [unit, data]); const selectedProject = useMemo( () => (projectId && data ? data.projects.find((p) => p.id === projectId) ?? null : null), [data, projectId], ); // کلمپِ خودکار: اگر پروژه‌ی انتخابی با فیلترهای جدید از داده خارج شد، // به سطح پروژه‌ها برگرد (بدون هیچ مقدار ثابت). useEffect(() => { if (level === 2 && projectId && data && !selectedProject) { setProjectId(null); setLevel(1); } if (data && data.projects.length === 0 && level !== 0) { setProjectId(null); setLevel(0); } }, [data, level, projectId, selectedProject]); const pick = (q: Record) => (activeUnit ? q[activeUnit] ?? 0 : 0); // برش‌های سطح فعال — کاملاً از پاسخ API const slices: Slice[] = useMemo(() => { if (!data) return []; if (level === 0) { return [{ id: "__org__", name: "کل سازمان", value: pick(data.total.qty_by_unit), rows: data.total.rows }]; } if (level === 1) { return data.projects.map((p) => ({ id: p.id, name: p.name, value: pick(p.qty_by_unit), rows: p.rows, })); } return (selectedProject?.warehouses ?? []).map((w) => ({ id: w.id, name: w.name, value: pick(w.qty_by_unit), rows: w.rows, })); }, [data, level, selectedProject, activeUnit]); const unitLabel = activeUnit ? data?.units?.[activeUnit] || activeUnit : ""; const levelTotal = slices.reduce((s, x) => s + x.value, 0); const total = data ? pick(data.total.qty_by_unit) : 0; const handleBarClick = (slice: Slice) => { if (level === 0 && data && data.projects.length > 0) { setLevel(1); } else if (level === 1) { setProjectId(slice.id); setLevel(2); } // سطح ۳ برگ است — کلیک دیگری لازم نیست }; const subText = level === 0 ? "تجمیع موجودی همه‌ی انبارهای سازمان برای فیلترهای فعال — برای مشاهده‌ی سهم پروژه‌ها کلیک کنید" : level === 1 ? "سهم هر پروژه از موجودی — برای مشاهده‌ی انبارهای هر پروژه روی ستون آن کلیک کنید" : `انبارهای پروژه «${selectedProject?.name ?? ""}» — برای بازگشت روی مسیر کلیک کنید`; return ( : null} > {/* مسیر Drill-Down (RTL) */} {query.isLoading ? ( ) : !slices.length || (levelTotal === 0 && level === 0) ? ( ) : ( <>
4 ? -18 : 0} textAnchor={slices.length > 4 ? "end" : "middle"} /> `${fmtNum(v)} ${unitLabel}`} /> } /> { const payload = (entry as { payload?: Slice })?.payload; if (payload) handleBarClick(payload); }} className={level < 2 ? "cursor-pointer" : ""} > { const n = Number(v); return Number.isFinite(n) && n > 0 ? fmtNum(n) : ""; }} /> {slices.map((s, i) => ( ))}
{level < 2 && (

{level === 0 ? "برای دیدن پروژه‌ها روی ستون کلیک کنید" : "برای دیدن انبارهای هر پروژه روی ستون آن کلیک کنید"}

)} )}
); }