Files
ERP/frontend/src/pages/Dashboard/FlowDiagram.tsx
T

79 lines
2.8 KiB
TypeScript

import { useCallback, useLayoutEffect, useState } from "react";
export interface FlowRefs {
items: Map<string, HTMLDivElement>;
termsheets: Map<string, HTMLDivElement>;
}
export interface FlowDiagramProps {
flowEl: HTMLDivElement | null;
refs: FlowRefs;
redrawKey?: string; // وقتی داده‌ی quote عوض شد (پذیرش/رسید/ویرایش) خطوط دوباره رسم شوند
}
interface PathSpec {
d: string;
accepted: boolean;
}
// خطوط اتصال SVG بین جعبه‌های اقلام و ترم‌شیت‌ها — موقعیت جعبه‌ها با
// getBoundingClientRect خوانده می‌شود و با ResizeObserver در جریان می‌ماند.
// خاکستری = باز، آبی = جفت پذیرفته‌شده‌ی دقیق.
export function FlowDiagram({ flowEl, refs, redrawKey = "" }: FlowDiagramProps) {
const [paths, setPaths] = useState<PathSpec[]>([]);
const draw = useCallback(() => {
if (!flowEl) return;
const crect = flowEl.getBoundingClientRect();
if (!crect.width) return;
const out: PathSpec[] = [];
refs.items.forEach((item) => {
const ir = item.getBoundingClientRect();
const x1 = ir.left - crect.left; // لبه‌ی چپ جعبه‌ی قلم (رو به فاصله)
const y1 = ir.top + ir.height / 2 - crect.top;
const isRejected = item.classList.contains("rejected");
const acceptedTsId = item.dataset.acceptedTs || "";
const tsIds = (item.dataset.tsIds || "").split(",").filter(Boolean);
tsIds.forEach((tsId) => {
const t = refs.termsheets.get(tsId);
if (!t) return;
const tr = t.getBoundingClientRect();
const x2 = tr.right - crect.left; // لبه‌ی راست جعبه‌ی termsheet
const y2 = tr.top + tr.height / 2 - crect.top;
const cx = (x1 + x2) / 2;
// آبی فقط برای جفت دقیقی که قلم پذیرفته — بقیه خاکستری
const accepted = !isRejected && acceptedTsId === tsId;
out.push({
d: `M ${x1} ${y1} C ${cx} ${y1}, ${cx} ${y2}, ${x2} ${y2}`,
accepted,
});
});
});
setPaths(out);
}, [flowEl, refs, redrawKey]);
useLayoutEffect(() => {
draw();
if (!flowEl) return;
const ro = new ResizeObserver(draw);
ro.observe(flowEl);
window.addEventListener("resize", draw);
if (typeof document !== "undefined" && document.fonts && document.fonts.ready) {
void document.fonts.ready.then(draw);
}
return () => {
ro.disconnect();
window.removeEventListener("resize", draw);
};
}, [draw, flowEl]);
return (
<svg className="flow-lines" aria-hidden="true">
{paths.map((p, i) => (
<path key={i} className={`flow-line${p.accepted ? " accepted" : ""}`} d={p.d} />
))}
</svg>
);
}