import { useState } from "react"; import { DndContext, PointerSensor, TouchSensor, useDraggable, useDroppable, useSensor, useSensors, type DragEndEvent } from "@dnd-kit/core"; import { CSS } from "@dnd-kit/utilities"; import { toast } from "sonner"; import { faNum } from "../../lib/formatters"; import { normalize } from "../../lib/normalize"; import { itemRowLabel, type ItemDraft } from "./Step2LineItems"; import { cn } from "../../lib/utils"; export interface Step4Props { items: ItemDraft[]; projects: string[]; assignment: Record; // tempId → project names onAssign: (tempId: string, proj: string) => void; onUnassign: (tempId: string, proj: string) => void; onAddProject: (name: string) => void; } const ALL_KEY = "__all__"; function DraggableItem({ item, label, assignedCount, assignedNames, picked, isAll, onPick, }: { item?: ItemDraft; label: string; assignedCount: number; assignedNames: string[]; picked: boolean; isAll: boolean; onPick: () => void; }) { const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ id: isAll ? "item-" + ALL_KEY : "item-" + item!.tempId, data: { tempId: isAll ? ALL_KEY : item!.tempId, isAll }, }); return (
{label} {assignedNames.length > 0 && (
لینک‌شده به: {assignedNames.join("، ")}
)}
{isAll ? "" : assignedCount ? faNum(assignedCount) + " پروژه" : "بدون پروژه"}
); } function DroppableProject({ name, tags, onTagRemove, onPickProject, }: { name: string; tags: { tempId: string; label: string }[]; onTagRemove: (tempId: string) => void; onPickProject: () => void; }) { const { setNodeRef, isOver } = useDroppable({ id: "proj-" + name, data: { proj: name } }); return (
🏗️ {name}
{tags.length ? ( tags.map((t) => ( {t.label}{" "} )) ) : (
قلمی برای این پروژه رها نشده
)}
); } // مرحله ۴ — تخصیص پروژه‌ها با درگ‌اندروپ (یا کلیک-قلم بعد کلیک-پروژه) export function Step4Projects({ items, projects, assignment, onAssign, onUnassign, onAddProject }: Step4Props) { const [picked, setPicked] = useState(null); const [newProj, setNewProj] = useState(""); // فاصله‌ی حداقلی قبل از شروع درگ — تا کلیک ساده روی قلم، «انتخاب قلم» باشد // نه شروع درگ (بدون این، dnd-kit رویداد click را می‌بلعد و کلیک-انتخاب کار نمی‌کند) // TouchSensor با تاخیر فعال‌سازی: درگ لمسی روی موبایل کار می‌کند بدون اینکه // اسکرول صفحه را بدزدد (کلیک-قلم بعد کلیک-پروژه هم‌چنان مسیر جایگزین است). const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 8 } }), ); // افزودن پروژه به‌صورت درجا — بدون رفتن به صفحه‌ی پروژه‌ها const addProjectInline = () => { const name = newProj.trim(); if (!name) return; const dup = projects.some((p) => normalize(p) === normalize(name)); if (dup) { toast.error("این پروژه قبلاً ثبت شده است."); return; } onAddProject(name); setNewProj(""); toast.success("پروژه افزوده شد."); }; const assignAll = (proj: string) => { items.forEach((it) => onAssign(it.tempId, proj)); }; const onDragEnd = (e: DragEndEvent) => { const tempId = (e.active.data.current as { tempId?: string; isAll?: boolean } | undefined)?.tempId; const proj = (e.over?.data.current as { proj?: string } | undefined)?.proj; if (!proj) return; if (tempId === ALL_KEY) { // همه‌ی اقلام به این پروژه assignAll(proj); } else if (tempId) { onAssign(tempId, proj); } }; return (

۴ پروژه‌ها

بکشید و رها کنید (یا روی قلم کلیک کنید، بعد روی پروژه کلیک کنید). هر قلم می‌تواند روی چند پروژه باشد. گزینه‌ی «همه اقلام» همه‌ی اقلام را به پروژه‌ی موردنظر اختصاص می‌دهد.' }} />

اقلام

قلم را بکشید و روی پروژه رها کنید — یا روی قلم کلیک کنید، بعد روی پروژه کلیک کنید
{/* همه اقلام — اختصاص همه به یک پروژه */} setPicked(picked === ALL_KEY ? null : ALL_KEY)} /> {items.map((it) => ( setPicked(picked === it.tempId ? null : it.tempId)} /> ))}

پروژه‌ها

{projects.map((pname) => ( (assignment[it.tempId] || []).includes(pname)) .map((it) => ({ tempId: it.tempId, label: itemRowLabel(it) }))} onTagRemove={(tempId) => onUnassign(tempId, pname)} onPickProject={() => { if (picked) { if (picked === ALL_KEY) assignAll(pname); else onAssign(picked, pname); setPicked(null); } }} /> ))} {!projects.length && (
هنوز پروژه‌ای تعریف نشده است — از پایین یک پروژه اضافه کنید.
)} {/* افزودن پروژه به‌صورت درجا — زیر لیست پروژه‌ها */}
افزودن پروژه‌ی جدید:
setNewProj(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addProjectInline(); } }} />
); }