ui(products): убрать колонку "Тип" из списка, спрятать min/max stock в "Расширенные"
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 41s
CI / Web (React + Vite) (push) Successful in 24s
Docker Images / API image (push) Successful in 34s
Docker Images / Web image (push) Successful in 26s
Docker Images / Deploy stage (push) Successful in 19s

В списке товаров колонка с бейджами Услуга/Весовой/Маркируемый только
занимает место и ничем не помогает — в UX MoySklad такого нет, убираю.
В форме редактирования минимальный/максимальный остаток (для
уведомлений о пополнении и автозаказа) — второстепенные поля, ушли в
раскрывающийся блок "Расширенные параметры" сразу после блока закупки.
Закупочная цена осталась основной.

НДС-колонка теперь рисует "—" если VatEnabled=false.
This commit is contained in:
nurdotnet 2026-04-24 10:53:32 +05:00
parent 57e8491f0d
commit d86b6ba742
2 changed files with 30 additions and 15 deletions

View file

@ -273,14 +273,8 @@ export function ProductEditPage() {
</div> </div>
</Section> </Section>
<Section title="Остатки и закупка"> <Section title="Закупка">
<Grid cols={4}> <Grid cols={4}>
<Field label="Мин. остаток">
<TextInput type="number" step="0.001" value={form.minStock} onChange={(e) => setForm({ ...form, minStock: e.target.value })} />
</Field>
<Field label="Макс. остаток">
<TextInput type="number" step="0.001" value={form.maxStock} onChange={(e) => setForm({ ...form, maxStock: e.target.value })} />
</Field>
<Field label="Закупочная цена"> <Field label="Закупочная цена">
<TextInput type="number" step="0.01" value={form.purchasePrice} onChange={(e) => setForm({ ...form, purchasePrice: e.target.value })} /> <TextInput type="number" step="0.01" value={form.purchasePrice} onChange={(e) => setForm({ ...form, purchasePrice: e.target.value })} />
</Field> </Field>
@ -293,6 +287,17 @@ export function ProductEditPage() {
</Grid> </Grid>
</Section> </Section>
<AdvancedSection>
<Grid cols={4}>
<Field label="Минимальный остаток (для уведомления)">
<TextInput type="number" step="0.001" value={form.minStock} onChange={(e) => setForm({ ...form, minStock: e.target.value })} placeholder="—" />
</Field>
<Field label="Максимальный остаток (для автозаказа)">
<TextInput type="number" step="0.001" value={form.maxStock} onChange={(e) => setForm({ ...form, maxStock: e.target.value })} placeholder="—" />
</Field>
</Grid>
</AdvancedSection>
<Section <Section
title="Цены продажи" title="Цены продажи"
action={<Button type="button" variant="secondary" size="sm" onClick={addPrice}><Plus className="w-3.5 h-3.5" /> Добавить</Button>} action={<Button type="button" variant="secondary" size="sm" onClick={addPrice}><Plus className="w-3.5 h-3.5" /> Добавить</Button>}
@ -396,3 +401,20 @@ function Grid({ cols, children }: { cols: 2 | 3 | 4; children: ReactNode }) {
const cls = cols === 2 ? 'grid-cols-1 md:grid-cols-2' : cols === 3 ? 'grid-cols-1 md:grid-cols-3' : 'grid-cols-2 md:grid-cols-4' const cls = cols === 2 ? 'grid-cols-1 md:grid-cols-2' : cols === 3 ? 'grid-cols-1 md:grid-cols-3' : 'grid-cols-2 md:grid-cols-4'
return <div className={`grid ${cls} gap-x-4 gap-y-3`}>{children}</div> return <div className={`grid ${cls} gap-x-4 gap-y-3`}>{children}</div>
} }
function AdvancedSection({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false)
return (
<section className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden">
<button
type="button"
onClick={() => setOpen((v) => !v)}
className="w-full flex items-center justify-between px-5 py-3 border-b border-slate-100 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-800/30 text-left hover:bg-slate-100/60 dark:hover:bg-slate-800/50"
>
<h2 className="text-sm font-semibold text-slate-900 dark:text-slate-100">Расширенные параметры</h2>
<span className="text-xs text-slate-500">{open ? 'свернуть' : 'развернуть'}</span>
</button>
{open && <div className="p-5">{children}</div>}
</section>
)
}

View file

@ -168,14 +168,7 @@ export function ProductsPage() {
)}, )},
{ header: 'Группа', width: '200px', cell: (r) => r.productGroupName ?? '—' }, { header: 'Группа', width: '200px', cell: (r) => r.productGroupName ?? '—' },
{ header: 'Ед.', width: '70px', cell: (r) => r.unitName }, { header: 'Ед.', width: '70px', cell: (r) => r.unitName },
{ header: 'НДС', width: '80px', className: 'text-right', cell: (r) => `${r.vat}%` }, { header: 'НДС', width: '80px', className: 'text-right', cell: (r) => r.vatEnabled ? `${r.vat}%` : '—' },
{ header: 'Тип', width: '140px', cell: (r) => (
<div className="flex gap-1 flex-wrap">
{r.isService && <span className="text-xs px-1.5 py-0.5 rounded bg-blue-50 text-blue-700">Услуга</span>}
{r.isWeighed && <span className="text-xs px-1.5 py-0.5 rounded bg-amber-50 text-amber-700">Весовой</span>}
{r.isMarked && <span className="text-xs px-1.5 py-0.5 rounded bg-purple-50 text-purple-700">Маркир.</span>}
</div>
)},
{ header: 'Штрихкодов', width: '120px', className: 'text-right', cell: (r) => r.barcodes.length }, { header: 'Штрихкодов', width: '120px', className: 'text-right', cell: (r) => r.barcodes.length },
{ header: 'Активен', width: '100px', cell: (r) => r.isActive ? '✓' : '—' }, { header: 'Активен', width: '100px', cell: (r) => r.isActive ? '✓' : '—' },
]} ]}