79 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|