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:
@@ -24,7 +24,6 @@ import {
|
|||||||
import { MilestoneRow } from "../../components/MilestoneRow";
|
import { MilestoneRow } from "../../components/MilestoneRow";
|
||||||
import { FileList } from "../../components/FileList";
|
import { FileList } from "../../components/FileList";
|
||||||
import { JalaliDatePicker } from "../../components/JalaliDatePicker";
|
import { JalaliDatePicker } from "../../components/JalaliDatePicker";
|
||||||
import { MultiSelectCombobox } from "../../components/MultiSelectCombobox";
|
|
||||||
|
|
||||||
interface CardDraft {
|
interface CardDraft {
|
||||||
domId: string;
|
domId: string;
|
||||||
@@ -54,7 +53,7 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
|
|||||||
const [removedItems, setRemovedItems] = useState<Set<string>>(new Set());
|
const [removedItems, setRemovedItems] = useState<Set<string>>(new Set());
|
||||||
const [assignments, setAssignments] = useState<Record<string, string>>({});
|
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 [acceptDate, setAcceptDate] = useState<string | null>(() => new Date().toISOString());
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
@@ -72,17 +71,15 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
|
|||||||
});
|
});
|
||||||
setRemovedItems(rem);
|
setRemovedItems(rem);
|
||||||
setAssignments({});
|
setAssignments({});
|
||||||
// پروژههای هر قلم — از تخصیصهای قبلی، وگرنه پروژههای سطح پیشنهاد
|
// پروژهی هر قلم — تخصیصِ تکی: از تخصیص قبلی قلم، وگرنه اولین پروژهی پیشنهاد
|
||||||
const projSel: Record<string, string[]> = {};
|
const projSel: Record<string, string> = {};
|
||||||
q.line_items.forEach((li) => {
|
q.line_items.forEach((li) => {
|
||||||
const names = (li.projects || []).map((p) => p.name).filter(Boolean);
|
const names = (li.projects || []).map((p) => p.name).filter(Boolean);
|
||||||
projSel[li.id] = names.length
|
projSel[li.id] = names.length
|
||||||
? names
|
? names[0]
|
||||||
: q.projects && q.projects.length
|
: q.projects && q.projects.length
|
||||||
? [...q.projects]
|
? q.projects[0]
|
||||||
: q.project
|
: q.project || "";
|
||||||
? [q.project]
|
|
||||||
: [];
|
|
||||||
});
|
});
|
||||||
setItemProjectsSel(projSel);
|
setItemProjectsSel(projSel);
|
||||||
setReady(true);
|
setReady(true);
|
||||||
@@ -221,14 +218,14 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
|
|||||||
if (t.status === "accepted" && !usedIds.has(t.id)) t.status = "offered";
|
if (t.status === "accepted" && !usedIds.has(t.id)) t.status = "offered";
|
||||||
});
|
});
|
||||||
|
|
||||||
// پروژههای هر قلم + همگامسازی پروژههای سطح پیشنهاد
|
// پروژهی هر قلم (تکی) + همگامسازی پروژههای سطح پیشنهاد
|
||||||
const projUnion = new Set<string>();
|
const projUnion = new Set<string>();
|
||||||
next.line_items.forEach((li) => {
|
next.line_items.forEach((li) => {
|
||||||
if (li.rejected) return;
|
if (li.rejected) return;
|
||||||
const names = itemProjectsSel[li.id];
|
const name = itemProjectsSel[li.id];
|
||||||
if (names === undefined) return;
|
if (name === undefined) return;
|
||||||
li.projects = names.map((n) => ({ id: "", name: n }));
|
li.projects = name ? [{ id: "", name }] : [];
|
||||||
li.projects.forEach((p) => projUnion.add(p.name));
|
if (name) projUnion.add(name);
|
||||||
});
|
});
|
||||||
next.projects = [...projUnion];
|
next.projects = [...projUnion];
|
||||||
next.project = next.projects[0] || "";
|
next.project = next.projects[0] || "";
|
||||||
@@ -342,11 +339,11 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* تخصیص قلمها به پروژهها */}
|
{/* تخصیص قلمها به پروژه */}
|
||||||
<div className="border-t border-line pt-3">
|
<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 className="mb-2 text-[11.5px] text-muted">
|
||||||
برای هر متریال مشخص کنید به کدام پروژه (ها) تعلق میگیرد.
|
برای هر متریال فقط یک پروژه انتخاب میشود — مثل شرایط پرداخت.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{allItems
|
{allItems
|
||||||
@@ -365,12 +362,28 @@ export function AcceptModal({ quoteId, onClose }: AcceptModalProps) {
|
|||||||
{fmtNum(li.quantity)} {li.unit}
|
{fmtNum(li.quantity)} {li.unit}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<MultiSelectCombobox
|
{projectOptions.length ? (
|
||||||
values={itemProjectsSel[li.id] || []}
|
<select
|
||||||
onChange={(names) => setItemProjectsSel((s) => ({ ...s, [li.id]: names }))}
|
className="min-w-[180px] rounded-md border border-line bg-panel px-2 py-1.5 font-inherit text-[12px]"
|
||||||
options={projectOptions}
|
value={itemProjectsSel[li.id] ?? ""}
|
||||||
placeholder="انتخاب پروژه..."
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user