feat(quotes-accept): single-project select per line item

Replaces the MultiSelectCombobox project picker in the accept modal with a
single-select dropdown styled like the payment-terms select. Each kept item
defaults to its previous project (or the quote's first), carries exactly one
project into item_projects AND into the decision payload on accept —
matching the backend's single purchased-project model.
This commit is contained in:
2026-09-08 14:42:24 +03:30
parent dbe53a727c
commit eb1043cc3a
+36 -23
View File
@@ -24,7 +24,6 @@ import {
import { MilestoneRow } from "../../components/MilestoneRow";
import { FileList } from "../../components/FileList";
import { JalaliDatePicker } from "../../components/JalaliDatePicker";
import { MultiSelectCombobox } from "../../components/MultiSelectCombobox";
interface CardDraft {
domId: string;
@@ -54,7 +53,7 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
const [removedItems, setRemovedItems] = useState<Set<string>>(new Set());
const [assignments, setAssignments] = useState<Record<string, string>>({});
// تخصیص هر قلم به پروژه‌ها (نام‌ها)
const [itemProjectsSel, setItemProjectsSel] = useState<Record<string, string[]>>({});
const [itemProjectsSel, setItemProjectsSel] = useState<Record<string, string>>({});
// تاریخ و زمان پذیرش — پیش‌فرض الان
const [acceptDate, setAcceptDate] = useState<string | null>(() => new Date().toISOString());
const [ready, setReady] = useState(false);
@@ -72,17 +71,15 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
});
setRemovedItems(rem);
setAssignments({});
// پروژه‌های هر قلم — از تخصیص‌های قبلی، وگرنه پروژه‌های سطح پیشنهاد
const projSel: Record<string, string[]> = {};
// پروژه‌ی هر قلم — تخصیصِ تکی: از تخصیص قبلی قلم، وگرنه اولین پروژه‌ی پیشنهاد
const projSel: Record<string, string> = {};
q.line_items.forEach((li) => {
const names = (li.projects || []).map((p) => p.name).filter(Boolean);
projSel[li.id] = names.length
? names
? names[0]
: q.projects && q.projects.length
? [...q.projects]
: q.project
? [q.project]
: [];
? q.projects[0]
: q.project || "";
});
setItemProjectsSel(projSel);
setReady(true);
@@ -221,14 +218,14 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
if (t.status === "accepted" && !usedIds.has(t.id)) t.status = "offered";
});
// پروژه‌های هر قلم + همگام‌سازی پروژه‌های سطح پیشنهاد
// پروژه‌ی هر قلم (تکی) + همگام‌سازی پروژه‌های سطح پیشنهاد
const projUnion = new Set<string>();
next.line_items.forEach((li) => {
if (li.rejected) return;
const names = itemProjectsSel[li.id];
if (names === undefined) return;
li.projects = names.map((n) => ({ id: "", name: n }));
li.projects.forEach((p) => projUnion.add(p.name));
const name = itemProjectsSel[li.id];
if (name === undefined) return;
li.projects = name ? [{ id: "", name }] : [];
if (name) projUnion.add(name);
});
next.projects = [...projUnion];
next.project = next.projects[0] || "";
@@ -342,11 +339,11 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
</div>
</div>
{/* تخصیص قلم‌ها به پروژه‌ها */}
{/* تخصیص قلم‌ها به پروژه */}
<div className="border-t border-line pt-3">
<div className="mb-1 text-[13px] font-bold">تخصیص قلم‌ها به پروژه‌ها:</div>
<div className="mb-1 text-[13px] font-bold">پروژه‌ی هر قلم:</div>
<div className="mb-2 text-[11.5px] text-muted">
برای هر متریال مشخص کنید به کدام پروژه (ها) تعلق می‌گیرد.
برای هر متریال فقط یک پروژه انتخاب می‌شود — مثل شرایط پرداخت.
</div>
<div className="flex flex-col gap-2">
{allItems
@@ -365,12 +362,28 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
{fmtNum(li.quantity)} {li.unit}
</span>
</div>
<MultiSelectCombobox
values={itemProjectsSel[li.id] || []}
onChange={(names) => setItemProjectsSel((s) => ({ ...s, [li.id]: names }))}
options={projectOptions}
placeholder="انتخاب پروژه..."
/>
{projectOptions.length ? (
<select
className="min-w-[180px] rounded-md border border-line bg-panel px-2 py-1.5 font-inherit text-[12px]"
value={itemProjectsSel[li.id] ?? ""}
onChange={(e) =>
setItemProjectsSel((s) => ({ ...s, [li.id]: e.target.value }))
}
>
{(itemProjectsSel[li.id] ? [] : [{ name: "" }]).map((_, i) => (
<option key={i} value="">
انتخاب پروژه...
</option>
))}
{projectOptions.map((n) => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
) : (
<div className="text-[12px] text-muted">پروژه‌ای ثبت نشده است.</div>
)}
</div>
);
})}