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 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>) =>
|
||||
setForm({ ...form, prices: form.prices.map((p, ix) => ix === i ? { ...p, ...patch } : p) })
|
||||
|
||||
|
|
@ -409,49 +403,51 @@ export function ProductEditPage() {
|
|||
Привести к себестоимости
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" variant="secondary" size="sm" onClick={addPrice}><Plus className="w-3.5 h-3.5" /> Добавить</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{form.prices.length === 0 ? (
|
||||
<div className="text-sm text-slate-400 py-2">Цен ещё нет. Добавь хотя бы розничную.</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{form.prices.map((p, i) => (
|
||||
<div key={i} className="grid grid-cols-12 gap-2 items-center">
|
||||
<div className={org.data?.multiCurrencyEnabled ? 'col-span-6' : 'col-span-8'}>
|
||||
<Select value={p.priceTypeId} onChange={(e) => updatePrice(i, { priceTypeId: e.target.value })}>
|
||||
{priceTypes.data?.map((pt) => <option key={pt.id} value={pt.id}>{pt.name}</option>)}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
{/* Список цен рендерится по справочнику PriceType: одно поле на каждый
|
||||
* тип, без выпадашки выбора. Значение хранится в form.prices,
|
||||
* key = priceTypeId. Для отсутствующих записей при наборе создаётся
|
||||
* новая, при стирании — null Amount (UI пустое). Системная запись
|
||||
* (IsSystem) и обязательные (IsRequired) помечаются звёздочкой. */}
|
||||
<div className="space-y-3">
|
||||
{priceTypes.data?.slice().sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name)).map((pt) => {
|
||||
const idx = form.prices.findIndex(p => p.priceTypeId === pt.id)
|
||||
const row = idx >= 0 ? form.prices[idx] : null
|
||||
const required = pt.isRequired
|
||||
return (
|
||||
<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
|
||||
value={p.amount}
|
||||
onChange={(n) => updatePrice(i, { amount: n ?? 0 })}
|
||||
currencyCode={currencies.data?.find((c) => c.id === p.currencyId)?.code ?? org.data?.defaultCurrencyCode ?? undefined}
|
||||
currencySymbol={currencies.data?.find((c) => c.id === p.currencyId)?.symbol ?? org.data?.defaultCurrencySymbol ?? undefined}
|
||||
|
||||
value={row?.amount ?? null}
|
||||
onChange={(n) => {
|
||||
if (n == null) {
|
||||
// Стерли значение — удаляем строку, чтобы не слать 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>
|
||||
{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>
|
||||
)}
|
||||
<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>
|
||||
)}
|
||||
)
|
||||
})}
|
||||
{priceTypes.data?.length === 0 && (
|
||||
<div className="text-sm text-slate-400 py-2">Нет ни одного типа цен. Создай в «Настройки → Типы цен».</div>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{!isNew && id && (
|
||||
|
|
|
|||
Loading…
Reference in a new issue