ui(product-card): «Закупка» и «Цены продажи» в две колонки на десктопе
Внутри секции «Цены» теперь двухколоночная сетка (lg:grid-cols-2): закупка слева, цены продажи справа, с вертикальным разделителем. На узких экранах (<lg) колонки складываются вертикально, как раньше. В правой колонке цены продажи переведены на стандартный <Field> с label сверху, чтобы выравниваться с полями закупки. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
168b12345d
commit
28b264f43b
|
|
@ -423,57 +423,56 @@ export function ProductEditPage() {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-slate-500 mb-3">Закупка</h3>
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-x-6 gap-y-5">
|
||||||
<Grid cols={4}>
|
<div>
|
||||||
{org.data?.showReferencePriceOnProduct && (
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-slate-500 mb-3">Закупка</h3>
|
||||||
<Field label="Эталонная цена">
|
<div className="space-y-3">
|
||||||
<MoneyInput
|
{org.data?.showReferencePriceOnProduct && (
|
||||||
value={form.referencePrice === '' ? null : Number(form.referencePrice)}
|
<Field label="Эталонная цена">
|
||||||
onChange={(n) => setForm({ ...form, referencePrice: n == null ? '' : String(n) })}
|
<MoneyInput
|
||||||
currencyCode={currencies.data?.find((c) => c.id === form.purchaseCurrencyId)?.code ?? org.data?.defaultCurrencyCode ?? undefined}
|
value={form.referencePrice === '' ? null : Number(form.referencePrice)}
|
||||||
currencySymbol={currencies.data?.find((c) => c.id === form.purchaseCurrencyId)?.symbol ?? org.data?.defaultCurrencySymbol ?? undefined}
|
onChange={(n) => setForm({ ...form, referencePrice: n == null ? '' : String(n) })}
|
||||||
/>
|
currencyCode={currencies.data?.find((c) => c.id === form.purchaseCurrencyId)?.code ?? org.data?.defaultCurrencyCode ?? undefined}
|
||||||
<p className="text-xs text-slate-400 mt-1">не обязательное поле</p>
|
currencySymbol={currencies.data?.find((c) => c.id === form.purchaseCurrencyId)?.symbol ?? org.data?.defaultCurrencySymbol ?? undefined}
|
||||||
</Field>
|
/>
|
||||||
)}
|
<p className="text-xs text-slate-400 mt-1">не обязательное поле</p>
|
||||||
<Field label="Себестоимость">
|
</Field>
|
||||||
<MoneyInput
|
)}
|
||||||
value={existing.data?.cost ?? 0}
|
<Field label="Себестоимость">
|
||||||
onChange={() => {}}
|
<MoneyInput
|
||||||
disabled
|
value={existing.data?.cost ?? 0}
|
||||||
currencyCode={org.data?.defaultCurrencyCode ?? undefined}
|
onChange={() => {}}
|
||||||
currencySymbol={org.data?.defaultCurrencySymbol ?? undefined}
|
disabled
|
||||||
/>
|
currencyCode={org.data?.defaultCurrencyCode ?? undefined}
|
||||||
<p className="text-xs text-slate-400 mt-1">расчётная (скользящее среднее)</p>
|
currencySymbol={org.data?.defaultCurrencySymbol ?? undefined}
|
||||||
</Field>
|
/>
|
||||||
{org.data?.multiCurrencyEnabled && (
|
<p className="text-xs text-slate-400 mt-1">расчётная (скользящее среднее)</p>
|
||||||
<Field label="Валюта закупки">
|
</Field>
|
||||||
<Select value={form.purchaseCurrencyId} onChange={(e) => setForm({ ...form, purchaseCurrencyId: e.target.value })}>
|
{org.data?.multiCurrencyEnabled && (
|
||||||
<option value="">—</option>
|
<Field label="Валюта закупки">
|
||||||
{currencies.data?.map((c) => <option key={c.id} value={c.id}>{c.code}</option>)}
|
<Select value={form.purchaseCurrencyId} onChange={(e) => setForm({ ...form, purchaseCurrencyId: e.target.value })}>
|
||||||
</Select>
|
<option value="">—</option>
|
||||||
</Field>
|
{currencies.data?.map((c) => <option key={c.id} value={c.id}>{c.code}</option>)}
|
||||||
)}
|
</Select>
|
||||||
</Grid>
|
</Field>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 pt-5 border-t border-slate-100 dark:border-slate-800">
|
<div className="lg:border-l lg:border-slate-100 lg:dark:border-slate-800 lg:pl-6">
|
||||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-slate-500 mb-3">Цены продажи</h3>
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-slate-500 mb-3">Цены продажи</h3>
|
||||||
{/* Список цен рендерится по справочнику PriceType: одно поле на каждый
|
{/* Список цен рендерится по справочнику PriceType: одно поле на каждый
|
||||||
* тип, без выпадашки выбора. Значение хранится в form.prices,
|
* тип, без выпадашки выбора. Значение хранится в form.prices,
|
||||||
* key = priceTypeId. Для отсутствующих записей при наборе создаётся
|
* key = priceTypeId. Для отсутствующих записей при наборе создаётся
|
||||||
* новая, при стирании — null Amount (UI пустое). Обязательные (IsRequired)
|
* новая, при стирании — null Amount (UI пустое). Обязательные (IsRequired)
|
||||||
* помечаются звёздочкой. */}
|
* помечаются звёздочкой. */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{priceTypes.data?.slice().sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name)).map((pt) => {
|
{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 idx = form.prices.findIndex(p => p.priceTypeId === pt.id)
|
||||||
const row = idx >= 0 ? form.prices[idx] : null
|
const row = idx >= 0 ? form.prices[idx] : null
|
||||||
const required = pt.isRequired
|
const required = pt.isRequired
|
||||||
return (
|
return (
|
||||||
<div key={pt.id} className="grid grid-cols-1 sm:grid-cols-3 gap-2 items-start">
|
<Field key={pt.id} label={`${pt.name}${required ? ' *' : ''}`}>
|
||||||
<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={row?.amount ?? null}
|
value={row?.amount ?? null}
|
||||||
onChange={(n) => {
|
onChange={(n) => {
|
||||||
|
|
@ -491,20 +490,20 @@ export function ProductEditPage() {
|
||||||
currencyCode={currencies.data?.find((c) => c.id === row?.currencyId)?.code ?? org.data?.defaultCurrencyCode ?? undefined}
|
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}
|
currencySymbol={currencies.data?.find((c) => c.id === row?.currencyId)?.symbol ?? org.data?.defaultCurrencySymbol ?? undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Field>
|
||||||
</div>
|
)
|
||||||
)
|
})}
|
||||||
})}
|
|
||||||
{priceTypes.data?.length === 0 && (
|
{priceTypes.data?.length === 0 && (
|
||||||
<div className="text-sm text-slate-400 py-2">Нет ни одного типа цен. Создай в «Настройки → Типы цен».</div>
|
<div className="text-sm text-slate-400 py-2">Нет ни одного типа цен. Создай в «Настройки → Типы цен».</div>
|
||||||
)}
|
)}
|
||||||
{missingRequiredPrices.length > 0 && (
|
</div>
|
||||||
<div className="text-sm text-red-600 mt-2">
|
|
||||||
Заполни обязательные цены: {missingRequiredPrices.map((pt) => `«${pt.name}»`).join(', ')} (значение должно быть больше 0).
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{missingRequiredPrices.length > 0 && (
|
||||||
|
<div className="text-sm text-red-600 mt-3">
|
||||||
|
Заполни обязательные цены: {missingRequiredPrices.map((pt) => `«${pt.name}»`).join(', ')} (значение должно быть больше 0).
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{org.data?.showMinMaxStock && (
|
{org.data?.showMinMaxStock && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue