Update ERP configuration and line item handling

This commit is contained in:
Behrad
2026-09-28 00:28:34 +03:30
parent 253c922280
commit cff39542b0
4 changed files with 80 additions and 129 deletions
+4 -6
View File
@@ -8,14 +8,13 @@ export interface NumberInputProps {
placeholder?: string;
className?: string;
inputClassName?: string;
align?: "right" | "center" | "left"; // تنظیم جهت متن و اعداد (پیش‌فرض: راست‌چین)
showWords?: boolean; // نمایش معادل فارسی زیر input
align?: "right" | "center" | "left";
showWords?: boolean;
inputMode?: "numeric" | "decimal" | undefined;
maxDecimals?: number; // حداکثر رقم اعشار
maxDecimals?: number;
disabled?: boolean;
}
// ورودی عددی با فرمت‌دهی خودکار (groupCommas) و نمایش کلمات فارسی
export function NumberInput({
value,
onChange,
@@ -28,7 +27,6 @@ export function NumberInput({
maxDecimals,
disabled = false,
}: NumberInputProps) {
// فرمت با رعایت حداکثر اعشار (گرد کردن)
const formatValue = (n: number): string => {
let v = n;
if (maxDecimals != null) {
@@ -75,7 +73,7 @@ export function NumberInput({
type="text"
disabled={disabled}
className={cn(
"num-input h-9 w-full rounded-lg border border-line bg-panel px-3 py-1 text-sm text-ink shadow-xs transition-colors placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 disabled:cursor-not-allowed disabled:opacity-60",
"num-input h-9 w-full rounded-lg border border-line bg-panel px-3 py-1 text-xs font-normal text-ink shadow-xs transition-colors placeholder:text-xs placeholder:font-normal placeholder:text-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 disabled:cursor-not-allowed disabled:opacity-60",
alignClass,
inputClassName,
)}
+48 -16
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { Trash2, Plus } from "lucide-react";
import { Trash2, Plus, Copy } from "lucide-react";
import type { Material } from "../../lib/types";
import { normalize } from "../../lib/normalize";
import { useMaterials } from "../../hooks";
@@ -35,7 +35,6 @@ export interface Step2Props {
onRemember: (rec: MaterialRec) => void;
}
// برچسب انسانی یک قلم (برای termsheet، درگ‌اندروپ و بازبینی)
export function itemRowLabel(it: ItemDraft): string {
const parts = [it.cat, it.brand, it.specs.join(" - ")].filter(Boolean);
const name = parts.length ? parts.join(" - ") : "(قلم بدون‌نام)";
@@ -43,7 +42,6 @@ export function itemRowLabel(it: ItemDraft): string {
return qtyPart ? `${name} -- ${qtyPart}` : name;
}
// مقادیر مجزای name/brand/spec در کاتالوگ سراسری
export function materialOptions(
materials: Material[],
_topic: string,
@@ -87,7 +85,6 @@ export function materialOptions(
return [...base].sort((a, b) => a.localeCompare(b, "fa"));
}
// مرحله ۲ — مدیریت اقلام سفارش
export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember }: Step2Props) {
const materials = useMaterials();
@@ -151,6 +148,22 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
]);
};
// قابلیت تکرار قلم در ردیف بعد با کپی عمیق فیلدها و شناسه یکتا
const duplicate = (index: number) => {
const target = items[index];
if (!target) return;
const clonedItem: ItemDraft = {
...target,
tempId: "it" + Date.now() + Math.random().toString(36).slice(2, 6),
specs: [...target.specs],
};
const nextItems = [...items];
nextItems.splice(index + 1, 0, clonedItem);
onItemsChange(nextItems);
};
const rememberField = (field: "i-cat" | "i-brand" | "i-spec", it: ItemDraft) => {
if (!selectedTopic) return;
let rec: MaterialRec;
@@ -169,6 +182,8 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
onRemember(rec);
};
const commonInputClass = "!h-9 !text-xs font-normal text-right placeholder:text-xs placeholder:font-normal";
return (
<div className="space-y-4">
{/* هدر */}
@@ -208,17 +223,31 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
{it.cat || "قلم جدید"} {it.brand ? `• ${it.brand}` : ""}
</span>
</div>
{items.length > 1 && (
{/* اکشن‌های کارت: کپی و حذف */}
<div className="flex items-center gap-1">
<button
type="button"
className="inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs text-rose-500 hover:bg-rose-500/10 transition-colors"
title="حذف قلم"
onClick={() => remove(it.tempId)}
className="inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs text-muted hover:text-ink hover:bg-line/50 transition-colors"
title="تکرار این قلم"
onClick={() => duplicate(idx)}
>
<Trash2 className="h-3.5 w-3.5" />
<span className="text-[11px]">حذف</span>
<Copy className="h-3.5 w-3.5" />
<span className="text-[11px]">کپی</span>
</button>
)}
{items.length > 1 && (
<button
type="button"
className="inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs text-rose-500 hover:bg-rose-500/10 transition-colors"
title="حذف قلم"
onClick={() => remove(it.tempId)}
>
<Trash2 className="h-3.5 w-3.5" />
<span className="text-[11px]">حذف</span>
</button>
)}
</div>
</div>
{/* ردیف ۱: متریال و برند */}
@@ -234,7 +263,7 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
placeholder="نام متریال"
onInputChange={(v) => patch(it.tempId, { cat: v })}
onBlur={() => rememberField("i-cat", it)}
inputClassName="!text-xs !h-9 text-right"
inputClassName={commonInputClass}
className="min-w-0"
/>
</div>
@@ -250,7 +279,7 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
placeholder="برند"
onInputChange={(v) => patch(it.tempId, { brand: v })}
onBlur={() => rememberField("i-brand", it)}
inputClassName="!text-xs !h-9 text-right"
inputClassName={commonInputClass}
className="min-w-0"
/>
</div>
@@ -284,6 +313,7 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
}}
placeholder="مقدار"
className="!text-right text-right"
inputClassName={commonInputClass}
/>
</div>
@@ -309,13 +339,13 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
})
.catch(() => {});
}}
inputClassName="!text-xs !h-9 text-right"
inputClassName={commonInputClass}
className="min-w-0"
/>
</div>
</div>
{/* ردیف ۴: قیمت هر واحد (راست‌چین شده) */}
{/* ردیف ۴: قیمت هر واحد */}
<div className="flex flex-col gap-1 min-w-0 border-t border-line/60 pt-2.5">
<label className="text-[10.5px] font-semibold text-muted text-right">
{it.unit ? `قیمت هر ${it.unit} (تومان)` : "قیمت هر واحد (تومان)"}
@@ -331,10 +361,11 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
placeholder="قیمت هر واحد"
showWords
className="!text-right text-right"
inputClassName={commonInputClass}
/>
</div>
{/* ردیف ۵: قیمت کل قلم (راست‌چین شده) */}
{/* ردیف ۵: قیمت کل قلم */}
<div className="flex flex-col gap-1 min-w-0 border-t border-line/60 pt-2.5">
<label className="text-[10.5px] font-semibold text-muted text-right">قیمت کل این قلم (تومان)</label>
<NumberInput
@@ -349,6 +380,7 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
placeholder="قیمت کل قلم"
showWords
className="!text-right text-right"
inputClassName={commonInputClass}
/>
</div>
</div>