import { useState } from "react"; import { Link } from "react-router-dom"; import { toast } from "sonner"; import { Building2, Inbox, CheckCircle2, Truck, Phone, FolderKanban } from "lucide-react"; import type { Project, Quote, Store } from "../../lib/types"; import { nextId } from "../../lib/api"; import { useAddProject, useMaterials, useProjects, useQuotes, useStoreData, useSuppliers, useUnits, } from "../../hooks"; import { normalize } from "../../lib/normalize"; import { useAppStore } from "../../store"; import { activeItems, lineItemTotal, materialById, materialLabel, refConverted, supplierById, } from "../../lib/quoteUtils"; import { faNum, fmtDate } from "../../lib/formatters"; import { Header, PageLayout } from "../../components/layout/Header"; import { EmptyState } from "../../components/EmptyState"; import { Button } from "../../components/ui/button"; function projectQuotes(quotes: Quote[], name: string): Quote[] { return quotes.filter((q) => (q.projects || []).includes(name) || q.project === name); } // اقلام مربوط به این پروژه در یک quote function itemsForProject(q: Quote, name: string): Quote["line_items"] { const items = (q.line_items || []).filter((li) => !li.rejected); if (items.some((li) => li.projects && li.projects.length)) { return items.filter((li) => (li.projects || []).some((p) => p.name === name)); } return items; } function qTotal(q: Quote): number { return activeItems(q).reduce((sum, li) => { const t = lineItemTotal(li); return sum + (t || 0); }, 0); } function daysSince(iso: string | null): number | null { if (!iso) return null; const d = new Date(iso); if (Number.isNaN(d.getTime())) return null; return Math.floor((Date.now() - d.getTime()) / 86400000); } // صفحه‌ی پروژه‌ها — لیست + پنل جزئیات ۳ ستونه export function ProjectsPage() { useStoreData(); const projects = useProjects(); const quotes = useQuotes(); const suppliers = useSuppliers(); const materials = useMaterials(); const { categoryUnits, unitConversions, standardConversions } = useUnits(); const addProject = useAddProject(); const topics = useAppStore((s) => s.topics); const store: Store = { quotes, suppliers, materials, projects, categoryUnits, unitConversions, standardConversions, topics }; const [openProject, setOpenProject] = useState(null); const [newName, setNewName] = useState(""); const addProj = () => { const name = newName.trim(); if (!name) return; const dup = projects.some((p) => normalize(p.name) === normalize(name)); if (dup) { toast.error("این پروژه قبلاً ثبت شده است."); return; } const p: Project = { id: nextId("p", projects), name }; void addProject.mutateAsync(p); setNewName(""); setOpenProject(name); toast.success("پروژه افزوده شد."); }; return (

+ افزودن پروژه‌ی جدید

setNewName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addProj(); } }} />
{!projects.length && !quotes.length ? ( ) : (
{projects.map((p) => { const count = projectQuotes(quotes, p.name).length; const open = openProject === p.name; return (
{/* جزئیات دقیقاً زیر همان پروژه باز می‌شود */} {open &&
{renderProjectDetail(store, p.name)}
}
); })}
)}
); } // ---------- پنل جزئیات یک پروژه ---------- function renderProjectDetail(store: Store, name: string): React.ReactElement { const quotes = projectQuotes(store.quotes, name); const sumBy = (st: Quote["status"]) => quotes .filter((q) => q.status === st) .reduce((s, q) => s + qTotal(q), 0); const matName = (mid: string) => { const m = materialById(store.materials, mid); return materialLabel(m); }; const supName = (sid: string) => { const s = supplierById(store.suppliers, sid); if (s.name === "؟") return "؟"; return ( {s.name} ); }; const itemLine = (li: Quote["line_items"][number]) => { const total = lineItemTotal(li); const conv = refConverted(store, li); return (
{matName(li.material_id)} — {faNum(li.quantity)} {li.unit} × {faNum(li.unit_price || 0)} ={" "} {faNum(total || 0)} تومان {conv && ( {" "} — ≈ {faNum(conv.qty)} {conv.unit}({faNum(conv.perRef ?? 0)} تومان/{conv.unit}) )}
); }; const offered = quotes .filter((q) => q.status === "offered") .sort((a, b) => new Date(a.expiry_date || 0).getTime() - new Date(b.expiry_date || 0).getTime()); const accepted = quotes.filter((q) => q.status === "accepted"); const arrived = quotes.filter((q) => q.status === "arrived"); const offeredRow = (q: Quote) => { const days = q.expiry_date ? Math.ceil((new Date(q.expiry_date).getTime() - Date.now()) / 86400000) : null; return (
{supName(q.supplier_id)}
{itemsForProject(q, name).map(itemLine)}
جمع: {faNum(qTotal(q))} تومان
اعتبار تا {fmtDate(q.expiry_date)}
{q.expiry_date == null ? ( بدون انقضا ) : days != null && days < 0 ? ( منقضی شده ) : days === 0 ? ( امروز آخرین مهلت ) : ( {faNum(days)} روز مانده )}
); }; const acceptedRow = (q: Quote) => { const d = daysSince(q.accepted_date); return (
{supName(q.supplier_id)}
{itemsForProject(q, name).map(itemLine)}
تایید: {fmtDate(q.accepted_date)} {d != null && {faNum(d)} روز پیش}
); }; const arrivedRow = (q: Quote) => (
{supName(q.supplier_id)}
{itemsForProject(q, name).map(itemLine)}
هزینه: {faNum(qTotal(q))} تومان
رسید: {fmtDate(q.arrived_date)}
); return (

{name}

کل پیشنهادها: {faNum(quotes.length)} ارزش باز: {faNum(sumBy("offered"))} تومان ارزش پذیرفته: {faNum(sumBy("accepted"))} تومان ارزش رسیده: {faNum(sumBy("arrived"))} تومان
پیشنهاد شده ({faNum(offered.length)})
{offered.length ? offered.map(offeredRow) :
—
}
پذیرفته شده ({faNum(accepted.length)})
{accepted.length ? accepted.map(acceptedRow) :
—
}
رسیده ({faNum(arrived.length)})
{arrived.length ? arrived.map(arrivedRow) :
—
}
); }