feat(product-prices): inputs по справочнику PriceType — без dropdown'a
Some checks are pending
Some checks are pending
Раньше каждая цена в карточке товара рендерилась как dropdown «выбор PriceType» + поле ввода + кнопка удаления. Это было избыточно: типы цен и так фиксированы справочником, выбирать нечего. Теперь: - Идём по справочнику PriceType (отсортирован по SortOrder→Name). - На каждый PriceType — одна строка: label = pt.Name, поле MoneyInput. - IsRequired запись помечается красной звёздочкой * после имени. - Стерев значение — строка убирается из form.prices (UI пусто). - Введя значение — создаётся новая запись (currency = KZT fallback из справочника), либо обновляется существующая. - Кнопка «+ Добавить» и иконка удаления убраны — управление набором типов цен теперь только через «Настройки → Типы цен». addPrice/removePrice вспомогательные функции удалены за ненадобностью. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7451996f50
commit
748abf7eff
|
|
@ -179,12 +179,6 @@ export function ProductEditPage() {
|
||||||
|
|
||||||
const onSubmit = (e: FormEvent) => { e.preventDefault(); save.mutate() }
|
const onSubmit = (e: FormEvent) => { e.preventDefault(); save.mutate() }
|
||||||
|
|
||||||
const addPrice = () => setForm({ ...form, prices: [...form.prices, {
|
|
||||||
priceTypeId: priceTypes.data?.find(p => !form.prices.some(x => x.priceTypeId === p.id))?.id ?? priceTypes.data?.[0]?.id ?? '',
|
|
||||||
amount: 0,
|
|
||||||
currencyId: currencies.data?.find(c => c.code === 'KZT')?.id ?? '',
|
|
||||||
}] })
|
|
||||||
const removePrice = (i: number) => setForm({ ...form, prices: form.prices.filter((_, ix) => ix !== i) })
|
|
||||||
const updatePrice = (i: number, patch: Partial<PriceRow>) =>
|
const updatePrice = (i: number, patch: Partial<PriceRow>) =>
|
||||||
setForm({ ...form, prices: form.prices.map((p, ix) => ix === i ? { ...p, ...patch } : p) })
|
setForm({ ...form, prices: form.prices.map((p, ix) => ix === i ? { ...p, ...patch } : p) })
|
||||||
|
|
||||||
|
|
@ -409,49 +403,51 @@ export function ProductEditPage() {
|
||||||
Привести к себестоимости
|
Привести к себестоимости
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button type="button" variant="secondary" size="sm" onClick={addPrice}><Plus className="w-3.5 h-3.5" /> Добавить</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{form.prices.length === 0 ? (
|
{/* Список цен рендерится по справочнику PriceType: одно поле на каждый
|
||||||
<div className="text-sm text-slate-400 py-2">Цен ещё нет. Добавь хотя бы розничную.</div>
|
* тип, без выпадашки выбора. Значение хранится в form.prices,
|
||||||
) : (
|
* key = priceTypeId. Для отсутствующих записей при наборе создаётся
|
||||||
<div className="space-y-2">
|
* новая, при стирании — null Amount (UI пустое). Системная запись
|
||||||
{form.prices.map((p, i) => (
|
* (IsSystem) и обязательные (IsRequired) помечаются звёздочкой. */}
|
||||||
<div key={i} className="grid grid-cols-12 gap-2 items-center">
|
<div className="space-y-3">
|
||||||
<div className={org.data?.multiCurrencyEnabled ? 'col-span-6' : 'col-span-8'}>
|
{priceTypes.data?.slice().sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name)).map((pt) => {
|
||||||
<Select value={p.priceTypeId} onChange={(e) => updatePrice(i, { priceTypeId: e.target.value })}>
|
const idx = form.prices.findIndex(p => p.priceTypeId === pt.id)
|
||||||
{priceTypes.data?.map((pt) => <option key={pt.id} value={pt.id}>{pt.name}</option>)}
|
const row = idx >= 0 ? form.prices[idx] : null
|
||||||
</Select>
|
const required = pt.isRequired
|
||||||
</div>
|
return (
|
||||||
<div className="col-span-3">
|
<div key={pt.id} className="grid grid-cols-1 sm:grid-cols-3 gap-2 items-start">
|
||||||
|
<label className="text-sm text-slate-700 dark:text-slate-200 sm:pt-2">
|
||||||
|
{pt.name}{required && <span className="text-red-500"> *</span>}
|
||||||
|
</label>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
<MoneyInput
|
<MoneyInput
|
||||||
value={p.amount}
|
value={row?.amount ?? null}
|
||||||
onChange={(n) => updatePrice(i, { amount: n ?? 0 })}
|
onChange={(n) => {
|
||||||
currencyCode={currencies.data?.find((c) => c.id === p.currencyId)?.code ?? org.data?.defaultCurrencyCode ?? undefined}
|
if (n == null) {
|
||||||
currencySymbol={currencies.data?.find((c) => c.id === p.currencyId)?.symbol ?? org.data?.defaultCurrencySymbol ?? undefined}
|
// Стерли значение — удаляем строку, чтобы не слать 0 как required.
|
||||||
|
setForm({ ...form, prices: form.prices.filter(x => x.priceTypeId !== pt.id) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (idx >= 0) {
|
||||||
|
updatePrice(idx, { amount: n })
|
||||||
|
} else {
|
||||||
|
const cur = currencies.data?.find(c => c.code === 'KZT')?.id ?? currencies.data?.[0]?.id ?? ''
|
||||||
|
setForm({ ...form, prices: [...form.prices, { priceTypeId: pt.id, amount: n, currencyId: cur }] })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
currencyCode={currencies.data?.find((c) => c.id === row?.currencyId)?.code ?? org.data?.defaultCurrencyCode ?? undefined}
|
||||||
|
currencySymbol={currencies.data?.find((c) => c.id === row?.currencyId)?.symbol ?? org.data?.defaultCurrencySymbol ?? undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{org.data?.multiCurrencyEnabled && (
|
|
||||||
<div className="col-span-2">
|
|
||||||
<Select value={p.currencyId} onChange={(e) => updatePrice(i, { currencyId: e.target.value })}>
|
|
||||||
{currencies.data?.map((c) => <option key={c.id} value={c.id}>{c.code}</option>)}
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{priceTypes.data?.length === 0 && (
|
||||||
|
<div className="text-sm text-slate-400 py-2">Нет ни одного типа цен. Создай в «Настройки → Типы цен».</div>
|
||||||
)}
|
)}
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => removePrice(i)}
|
|
||||||
className="col-span-1 text-slate-400 hover:text-red-600 flex justify-center"
|
|
||||||
title="Удалить строку"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{!isNew && id && (
|
{!isNew && id && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue