feat(inventory): list-based ChangeInventoryPage + mobile-first UX + return ledger controls
- All ChangeInventoryPage sections (withdrawal/damage/waste/loss/transfer/ stock-count/project-transfer) become list-based like the withdrawal section: pick a warehouse, see its stock rows, material name is only an optional filter; manual material/brand/unit entry removed everywhere except PURCHASE. - RETURN redesigned: day-range presets (today/yesterday/7d/30d + Jalali custom range) with server-side filters, immutable-ledger returnable remainder per withdrawal (REF:[tx-id] note tag + FIFO attribution for legacy returns, round4 float-safe), destination warehouse selector. - PROJECT_TRANSFER: row-based list with one destination project per batch; sequential submits keep failed rows inline with errors and drop succeeded rows; history dashboard untouched. - PURCHASE: unit locked to catalog (search-only Combobox), wizard-style '+ register new material' option with yellow warning, optional Key-Value specs editor (replaces mandatory text chips), continuous-entry UX (warehouse/date kept, auto-focus, session log, duplicate warning). - Mobile-first: responsive table/cards core (StockTable), numeric keyboards (inputMode+pattern), sticky bottom submit bar with dynamic counter above MobileTabBar incl. safe-area inset, horizontally scrollable touch chips. - Backend fix (prereq of RETURN): GET /inventory/transactions date filters crashed with 500 under asyncpg (string vs timestamptz); parse to aware UTC datetimes (DB runs Etc/UTC). - docs/inventory-ledger-gaps.md: ledger gap report (server-side return cap, standalone TRANSFER_IN, transfer row fallback, audit coverage, legacy REF backfill, UTC day semantics, pagination gaps).
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// ============================================================
|
||||
// ویرایشگر مشخصههای فنی Key-Value (موجودی اولیه)
|
||||
// خروجی: jsonb استاندارد {عنوان: مقدار} همخوان با specs_hash بکاند.
|
||||
// مشخصات کاملاً اختیاری است؛ ردیف بدون عنوان دور ریخته میشود.
|
||||
// ============================================================
|
||||
|
||||
import { faNum } from "../../../lib/formatters";
|
||||
|
||||
export interface SpecKvRow { key: string; value: string; }
|
||||
|
||||
export function specsKvToObject(rows: SpecKvRow[]): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
for (const r of rows) {
|
||||
const k = r.key.trim();
|
||||
if (k) out[k] = r.value.trim();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
interface SpecsKvEditorProps {
|
||||
rows: SpecKvRow[];
|
||||
onChange: (rows: SpecKvRow[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function SpecsKvEditor({ rows, onChange, disabled }: SpecsKvEditorProps) {
|
||||
const update = (i: number, patch: Partial<SpecKvRow>) =>
|
||||
onChange(rows.map((r, ri) => (ri === i ? { ...r, ...patch } : r)));
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{rows.map((r, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
disabled={disabled}
|
||||
className="h-10 w-full rounded-lg border border-line bg-panel px-3 text-[13px] focus:border-accent"
|
||||
placeholder="عنوان مشخصه (مثلاً ضخامت)"
|
||||
value={r.key}
|
||||
onChange={(e) => update(i, { key: e.target.value })}
|
||||
/>
|
||||
<span className="shrink-0 text-[12px] text-muted">:</span>
|
||||
<input
|
||||
type="text"
|
||||
disabled={disabled}
|
||||
className="h-10 w-full rounded-lg border border-line bg-panel px-3 text-[13px] focus:border-accent"
|
||||
placeholder="مقدار (مثلاً ۲ میلیمتر)"
|
||||
value={r.value}
|
||||
onChange={(e) => update(i, { value: e.target.value })}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => onChange(rows.filter((_, ri) => ri !== i))}
|
||||
className="shrink-0 rounded-lg border border-line px-2.5 py-2 text-[13px] text-rose-600 hover:bg-rose-50 disabled:opacity-40"
|
||||
title="حذف این مشخصه"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => onChange([...rows, { key: "", value: "" }])}
|
||||
className="rounded-lg border border-dashed border-line px-3 py-2 text-[12px] font-medium text-muted hover:bg-bg disabled:opacity-40"
|
||||
>
|
||||
+ افزودن مشخصه {rows.length > 0 && `(${faNum(rows.length)})`}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user