- 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).
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
// ============================================================
|
||
// ویرایشگر مشخصههای فنی 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>
|
||
);
|
||
}
|