fix(wizard,quotes,dates): reliable error routing, single-project accept, Persian date/time input
Wizard: - failed saves now route to the exact step+field: a new data-field attribute on every required section (supplier_id, line_items, termsheets, project, quote_date, expiry_date, notes, proforma) plus resolveErrorTarget() that maps ApiError.field / message keywords (supplier, line item/material/unit/quantity, termsheet/schedule/milestone, project, dates, notes, file) to the offending step — the «رفع خطا» toast action scrolls/focuses the right control instead of guessing - material records are no longer created on the fly while typing (rememberMaterial no longer fires addMaterial per blur); materials/specs/units are written once during the final save, avoiding half-created catalog rows Accept flow: - quote_service no longer rewrites LineItemProjects on accept — those candidate-project rows stay 100% untouched and only item_decisions.project_id is written, matching the per-item target-project model and the UI payload (item_projects: []) Dates/time: - JalaliDatePicker: hour/minute are now editable text fields (Persian digits, clamping, 3-digit retype handling) with a controlled useEffect that only rewrites the inputs when the numeric value actually changes — no more wiping in-progress typing; date-only detection tightened to a full YYYY-MM-DD match so datetime strings are not misread Also: /api validation logging (RequestValidationError) now includes the path and error list for faster diagnosis. Verified: api + frontend image builds (python, tsc, vite) clean on top of origin/production (incl. the team's mobile layout merges).
This commit is contained in:
@@ -243,7 +243,32 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
))}
|
||||
</InfoCell>
|
||||
|
||||
{/* ۳. مجموع مبلغ */}
|
||||
<InfoCell label="پروژه">
|
||||
<span className="inline-flex items-center gap-1 text-xs sm:text-[13px]">
|
||||
<Building2 className="h-3.5 w-3.5 shrink-0 text-muted" />
|
||||
<span className="truncate">
|
||||
{(() => {
|
||||
const activeItems = (q.line_items || []).filter((li) => !li.rejected);
|
||||
if (q.status === "accepted" || q.status === "arrived") {
|
||||
// فقط پروژهی هدف پذیرفتهشده — همان منطق itemProjects در وضعیت accepted
|
||||
const targetProj = activeItems.find((li) => li.target_project)?.target_project;
|
||||
return targetProj?.name ?? projectCell(q);
|
||||
}
|
||||
// وضعیت offered: همهی پروژههای کاندیدا از line_items (همان منطق itemProjects)
|
||||
const names = new Set<string>();
|
||||
activeItems.forEach((li) =>
|
||||
(li.projects || []).forEach((p) => {
|
||||
const name = typeof p === "string" ? p : p.name;
|
||||
if (name) names.add(name);
|
||||
})
|
||||
);
|
||||
if (names.size > 0) return [...names].join("، ");
|
||||
return projectCell(q);
|
||||
})()}
|
||||
</span>
|
||||
</span>
|
||||
</InfoCell>
|
||||
|
||||
<InfoCell label="مجموع مبلغ" className="col-span-1">
|
||||
{pc ? (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user