feat(web): статусы DocStatusBadge на всех списках документов + кнопка «Сгенерировать» ШК
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions

П.9: DocStatusBadge (Черновик/Проведён с подсказкой) раскатан на Оприходования,
Списания, Перемещения, Возвраты поставщикам, Отгрузки (было: приёмки/продажи/инвент.).
П.4: в карточке товара рядом с полем штрихкода — кнопка «Сгенерировать» (внутренний
EAN-13). Группа по умолчанию — «Все товары», единица — «шт» (уже было).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-09 06:32:58 +05:00
parent 8c163e63fc
commit 18752497a8
6 changed files with 21 additions and 18 deletions

View file

@ -1,4 +1,5 @@
import { useRef } from 'react' import { useRef } from 'react'
import { DocStatusBadge } from '@/components/DocStatusBadge'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus, Truck } from 'lucide-react' import { Plus, Truck } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
@ -67,9 +68,7 @@ export function DemandsPage() {
{ header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> }, { header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> },
{ header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') }, { header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') },
{ header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => ( { header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => (
r.status === DemandStatus.Posted <DocStatusBadge status={r.status === DemandStatus.Posted ? 'posted' : 'draft'} />
? <span className="text-xs px-2 py-0.5 rounded bg-green-50 text-green-700">Проведён</span>
: <span className="text-xs px-2 py-0.5 rounded bg-slate-100 text-slate-600">Черновик</span>
)}, )},
{ header: 'Контрагент', sortKey: 'customer', cell: (r) => r.customerName }, { header: 'Контрагент', sortKey: 'customer', cell: (r) => r.customerName },
{ header: 'Склад', width: '160px', cell: (r) => r.storeName }, { header: 'Склад', width: '160px', cell: (r) => r.storeName },

View file

@ -1,4 +1,5 @@
import { useRef } from 'react' import { useRef } from 'react'
import { DocStatusBadge } from '@/components/DocStatusBadge'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus, PackagePlus } from 'lucide-react' import { Plus, PackagePlus } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
@ -67,9 +68,7 @@ export function EntersPage() {
{ header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> }, { header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> },
{ header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') }, { header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') },
{ header: 'Статус', width: '130px', sortKey: 'status', cell: (r) => ( { header: 'Статус', width: '130px', sortKey: 'status', cell: (r) => (
r.status === EnterStatus.Posted <DocStatusBadge status={r.status === EnterStatus.Posted ? 'posted' : 'draft'} />
? <span className="text-xs px-2 py-0.5 rounded bg-green-50 text-green-700">Проведён</span>
: <span className="text-xs px-2 py-0.5 rounded bg-slate-100 text-slate-600">Черновик</span>
)}, )},
{ header: 'Склад', sortKey: 'store', cell: (r) => r.storeName }, { header: 'Склад', sortKey: 'store', cell: (r) => r.storeName },
{ header: 'Позиций', width: '100px', className: 'text-right', cell: (r) => r.lineCount }, { header: 'Позиций', width: '100px', className: 'text-right', cell: (r) => r.lineCount },

View file

@ -1,4 +1,5 @@
import { useRef } from 'react' import { useRef } from 'react'
import { DocStatusBadge } from '@/components/DocStatusBadge'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus, Trash2 } from 'lucide-react' import { Plus, Trash2 } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
@ -67,9 +68,7 @@ export function LossesPage() {
{ header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> }, { header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> },
{ header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') }, { header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') },
{ header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => ( { header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => (
r.status === LossStatus.Posted <DocStatusBadge status={r.status === LossStatus.Posted ? 'posted' : 'draft'} />
? <span className="text-xs px-2 py-0.5 rounded bg-green-50 text-green-700">Проведён</span>
: <span className="text-xs px-2 py-0.5 rounded bg-slate-100 text-slate-600">Черновик</span>
)}, )},
{ header: 'Причина', width: '140px', sortKey: 'reason', cell: (r) => lossReasonLabel[r.reason] ?? r.reason }, { header: 'Причина', width: '140px', sortKey: 'reason', cell: (r) => lossReasonLabel[r.reason] ?? r.reason },
{ header: 'Склад', sortKey: 'store', cell: (r) => r.storeName }, { header: 'Склад', sortKey: 'store', cell: (r) => r.storeName },

View file

@ -1,7 +1,7 @@
import { useState, useEffect, type FormEvent, type ReactNode } from 'react' import { useState, useEffect, type FormEvent, type ReactNode } from 'react'
import { useNavigate, useParams, Link } from 'react-router-dom' import { useNavigate, useParams, Link } from 'react-router-dom'
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query' import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'
import { ArrowLeft, Plus, Trash2, Save } from 'lucide-react' import { ArrowLeft, Plus, Trash2, Save, RefreshCw } from 'lucide-react'
import { api, humanizeError } from '@/lib/api' import { api, humanizeError } from '@/lib/api'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { Field, TextInput, TextArea, Select, AsyncSelect, Checkbox, MoneyInput, NumberInput } from '@/components/Field' import { Field, TextInput, TextArea, Select, AsyncSelect, Checkbox, MoneyInput, NumberInput } from '@/components/Field'
@ -329,8 +329,16 @@ export function ProductEditPage() {
<div className="space-y-2"> <div className="space-y-2">
{form.barcodes.map((b, i) => ( {form.barcodes.map((b, i) => (
<div key={i} className="grid grid-cols-12 gap-2 items-center"> <div key={i} className="grid grid-cols-12 gap-2 items-center">
<div className="col-span-6"> <div className="col-span-6 flex gap-1.5">
<TextInput placeholder="Код" value={b.code} onChange={(e) => updateBarcode(i, { code: e.target.value })} /> <TextInput placeholder="Код" value={b.code} onChange={(e) => updateBarcode(i, { code: e.target.value })} className="flex-1" />
<button
type="button"
onClick={() => updateBarcode(i, { code: generateBarcode(b.type) })}
className="shrink-0 px-2 rounded-md border border-slate-200 dark:border-slate-700 text-slate-500 hover:text-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800"
title="Сгенерировать внутренний штрихкод"
>
<RefreshCw className="w-4 h-4" />
</button>
</div> </div>
<div className="col-span-3"> <div className="col-span-3">
<Select value={b.type} onChange={(e) => { <Select value={b.type} onChange={(e) => {

View file

@ -1,4 +1,5 @@
import { useRef } from 'react' import { useRef } from 'react'
import { DocStatusBadge } from '@/components/DocStatusBadge'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus, Undo2 } from 'lucide-react' import { Plus, Undo2 } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
@ -67,9 +68,7 @@ export function SupplierReturnsPage() {
{ header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> }, { header: '№', width: '160px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600">{r.number}</span> },
{ header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') }, { header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') },
{ header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => ( { header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => (
r.status === SupplierReturnStatus.Posted <DocStatusBadge status={r.status === SupplierReturnStatus.Posted ? 'posted' : 'draft'} />
? <span className="text-xs px-2 py-0.5 rounded bg-green-50 text-green-700">Проведён</span>
: <span className="text-xs px-2 py-0.5 rounded bg-slate-100 text-slate-600">Черновик</span>
)}, )},
{ header: 'Поставщик', sortKey: 'supplier', cell: (r) => r.supplierName }, { header: 'Поставщик', sortKey: 'supplier', cell: (r) => r.supplierName },
{ header: 'По приёмке', width: '140px', cell: (r) => r.referenceSupplyNumber ? <span className="font-mono text-xs text-slate-500">{r.referenceSupplyNumber}</span> : '—' }, { header: 'По приёмке', width: '140px', cell: (r) => r.referenceSupplyNumber ? <span className="font-mono text-xs text-slate-500">{r.referenceSupplyNumber}</span> : '—' },

View file

@ -1,4 +1,5 @@
import { useRef } from 'react' import { useRef } from 'react'
import { DocStatusBadge } from '@/components/DocStatusBadge'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus, ArrowRight, ArrowLeftRight } from 'lucide-react' import { Plus, ArrowRight, ArrowLeftRight } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
@ -67,9 +68,7 @@ export function TransfersPage() {
{ header: '№', width: '180px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600 dark:text-slate-300">{r.number}</span> }, { header: '№', width: '180px', sortKey: 'number', cell: (r) => <span className="font-mono text-slate-600 dark:text-slate-300">{r.number}</span> },
{ header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') }, { header: 'Дата', width: '120px', sortKey: 'date', cell: (r) => new Date(r.date).toLocaleDateString('ru') },
{ header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => ( { header: 'Статус', width: '110px', sortKey: 'status', cell: (r) => (
r.status === TransferStatus.Posted <DocStatusBadge status={r.status === TransferStatus.Posted ? 'posted' : 'draft'} />
? <span className="text-xs px-2 py-0.5 rounded bg-green-50 text-green-700">Проведён</span>
: <span className="text-xs px-2 py-0.5 rounded bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300">Черновик</span>
)}, )},
{ header: 'Откуда → Куда', cell: (r) => ( { header: 'Откуда → Куда', cell: (r) => (
<span className="inline-flex items-center gap-2 text-slate-700 dark:text-slate-200"> <span className="inline-flex items-center gap-2 text-slate-700 dark:text-slate-200">