From 821bc4ed8df8c183f649260870676bdffb684d40 Mon Sep 17 00:00:00 2001 From: nns Date: Sat, 30 May 2026 11:25:32 +0500 Subject: [PATCH] =?UTF-8?q?feat(web):=20Breadcrumbs=20=D0=BD=D0=B0=20edit-?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=D1=85=20(?= =?UTF-8?q?=D0=9A=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=20/=20=D0=A2=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D1=8B=20/=20=D0=9C=D0=BE=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=203.2%)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item 6 Sprint 7 — реюзабельный над h1 на 9 edit-pages. Компонент: src/components/Breadcrumbs.tsx — Lucide ChevronRight как разделитель, последний item — текущий (semibold темнее, без линка), не- последние — кликабельные Link'и react-router (если задан to). Truncate с title-tooltip для длинных названий. Применено: - ProductEditPage: Каталог / Товары / - SupplyEditPage: Закупки / Приёмки / - EnterEditPage / LossEditPage / TransferEditPage / InventoryEditPage: Остатки / <тип> / - SupplierReturnEditPage: Закупки / Возвраты поставщикам / - DemandEditPage: Продажи / Оптовые отгрузки / - RetailSaleEditPage: Продажи / Чеки / Side-effect: на doc-edit pages убрана дублирующая subtitle «Черновик — товар не списан, пока не проведёшь» (breadcrumbs дают контекст). Зелёная плашка «Проведён <дата>» сохранилась — она несёт реальную инфу. tsc clean. Co-Authored-By: Claude Opus 4.7 --- .../src/components/Breadcrumbs.tsx | 43 +++++++++++++++++++ .../src/pages/DemandEditPage.tsx | 16 ++++--- .../src/pages/EnterEditPage.tsx | 16 ++++--- .../src/pages/InventoryEditPage.tsx | 16 ++++--- .../src/pages/LossEditPage.tsx | 16 ++++--- .../src/pages/ProductEditPage.tsx | 9 ++-- .../src/pages/RetailSaleEditPage.tsx | 16 ++++--- .../src/pages/SupplierReturnEditPage.tsx | 16 ++++--- .../src/pages/SupplyEditPage.tsx | 16 ++++--- .../src/pages/TransferEditPage.tsx | 16 ++++--- 10 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 src/food-market.web/src/components/Breadcrumbs.tsx diff --git a/src/food-market.web/src/components/Breadcrumbs.tsx b/src/food-market.web/src/components/Breadcrumbs.tsx new file mode 100644 index 0000000..a365809 --- /dev/null +++ b/src/food-market.web/src/components/Breadcrumbs.tsx @@ -0,0 +1,43 @@ +import { Fragment, type ReactNode } from 'react' +import { Link } from 'react-router-dom' +import { ChevronRight } from 'lucide-react' + +/** + * Хлебные крошки для edit-страниц: «Каталог / Товары / Молоко 3.2%». + * + * Каждый item с `to` — кликабельная ссылка (sm grey). Последний item — + * текущий, не кликабельный, semibold темнее. Разделитель — ChevronRight. + * + * Используется через PageHeader/инлайн в шапке формы. + */ +export interface BreadcrumbItem { + label: ReactNode + to?: string +} + +interface Props { + items: BreadcrumbItem[] + className?: string +} + +export function Breadcrumbs({ items, className }: Props) { + if (items.length === 0) return null + return ( + + ) +} diff --git a/src/food-market.web/src/pages/DemandEditPage.tsx b/src/food-market.web/src/pages/DemandEditPage.tsx index 5aac9ae..d282fb8 100644 --- a/src/food-market.web/src/pages/DemandEditPage.tsx +++ b/src/food-market.web/src/pages/DemandEditPage.tsx @@ -9,6 +9,7 @@ import { DateField } from '@/components/DateField' import { ProductPicker } from '@/components/ProductPicker' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores, useCurrencies } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -221,14 +222,19 @@ export function DemandEditPage() {
+

{isNew ? 'Новая отгрузка' : existing.data?.number ?? 'Отгрузка'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не списан, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/EnterEditPage.tsx b/src/food-market.web/src/pages/EnterEditPage.tsx index 97c79ac..be866fc 100644 --- a/src/food-market.web/src/pages/EnterEditPage.tsx +++ b/src/food-market.web/src/pages/EnterEditPage.tsx @@ -9,6 +9,7 @@ import { DateField } from '@/components/DateField' import { ProductPicker } from '@/components/ProductPicker' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores, useCurrencies } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -201,14 +202,19 @@ export function EnterEditPage() {
+

{isNew ? 'Новое оприходование' : existing.data?.number ?? 'Оприходование'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не попадает на склад, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/InventoryEditPage.tsx b/src/food-market.web/src/pages/InventoryEditPage.tsx index a92cfd3..3ac4a19 100644 --- a/src/food-market.web/src/pages/InventoryEditPage.tsx +++ b/src/food-market.web/src/pages/InventoryEditPage.tsx @@ -8,6 +8,7 @@ import { Field, TextArea, Select, NumberInput, Checkbox } from '@/components/Fie import { DateField } from '@/components/DateField' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores } from '@/lib/useLookups' import { InventoryStatus, type InventoryDto } from '@/lib/types' @@ -217,14 +218,19 @@ export function InventoryEditPage() {
+

{isNew ? 'Новая инвентаризация' : existing.data?.number ?? 'Инвентаризация'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — расхождения не отражаются на остатках, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/LossEditPage.tsx b/src/food-market.web/src/pages/LossEditPage.tsx index c99af54..55cdb9b 100644 --- a/src/food-market.web/src/pages/LossEditPage.tsx +++ b/src/food-market.web/src/pages/LossEditPage.tsx @@ -9,6 +9,7 @@ import { DateField } from '@/components/DateField' import { ProductPicker } from '@/components/ProductPicker' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores, useCurrencies } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -204,14 +205,19 @@ export function LossEditPage() {
+

{isNew ? 'Новое списание' : existing.data?.number ?? 'Списание'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не списан, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/ProductEditPage.tsx b/src/food-market.web/src/pages/ProductEditPage.tsx index 774d6bf..11fbc68 100644 --- a/src/food-market.web/src/pages/ProductEditPage.tsx +++ b/src/food-market.web/src/pages/ProductEditPage.tsx @@ -13,6 +13,7 @@ import { BarcodeType, Packaging, type Product } from '@/lib/types' import { ProductImageGallery } from '@/components/ProductImageGallery' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { generateEan13InternalPrefix2, generateBarcode, generateArticle } from '@/lib/barcode' @@ -238,12 +239,14 @@ export function ProductEditPage() {
+

{isNew ? 'Новый товар' : form.name || 'Товар'}

-

- {isNew ? 'Создание новой позиции каталога' : 'Редактирование'} -

diff --git a/src/food-market.web/src/pages/RetailSaleEditPage.tsx b/src/food-market.web/src/pages/RetailSaleEditPage.tsx index d9ed513..7a0d7cb 100644 --- a/src/food-market.web/src/pages/RetailSaleEditPage.tsx +++ b/src/food-market.web/src/pages/RetailSaleEditPage.tsx @@ -8,6 +8,7 @@ import { Field, TextInput, TextArea, Select, AsyncSelect, MoneyInput, NumberInpu import { ProductPicker } from '@/components/ProductPicker' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores, useCurrencies } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -220,14 +221,19 @@ export function RetailSaleEditPage() {
+

{isNew ? 'Новый чек' : existing.data?.number ?? 'Чек'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не списывается со склада до проведения'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/SupplierReturnEditPage.tsx b/src/food-market.web/src/pages/SupplierReturnEditPage.tsx index 4172750..ddb051c 100644 --- a/src/food-market.web/src/pages/SupplierReturnEditPage.tsx +++ b/src/food-market.web/src/pages/SupplierReturnEditPage.tsx @@ -9,6 +9,7 @@ import { DateField } from '@/components/DateField' import { ProductPicker } from '@/components/ProductPicker' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores, useCurrencies } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -207,14 +208,19 @@ export function SupplierReturnEditPage() {
+

{isNew ? 'Новый возврат поставщику' : existing.data?.number ?? 'Возврат поставщику'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не списан, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/SupplyEditPage.tsx b/src/food-market.web/src/pages/SupplyEditPage.tsx index 653f425..1dbfbac 100644 --- a/src/food-market.web/src/pages/SupplyEditPage.tsx +++ b/src/food-market.web/src/pages/SupplyEditPage.tsx @@ -10,6 +10,7 @@ import { ProductPicker } from '@/components/ProductPicker' import { SupplyLineQuickAdd, type AddedProduct } from '@/components/SupplyLineQuickAdd' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores, useCurrencies, usePriceTypes } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -281,14 +282,19 @@ export function SupplyEditPage() {
+

{isNew ? 'Новая приёмка' : existing.data?.number ?? 'Приёмка'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не попадает на склад, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}
diff --git a/src/food-market.web/src/pages/TransferEditPage.tsx b/src/food-market.web/src/pages/TransferEditPage.tsx index e572189..09ca7c2 100644 --- a/src/food-market.web/src/pages/TransferEditPage.tsx +++ b/src/food-market.web/src/pages/TransferEditPage.tsx @@ -9,6 +9,7 @@ import { DateField } from '@/components/DateField' import { ProductPicker } from '@/components/ProductPicker' import { ConfirmDialog } from '@/components/ConfirmDialog' import { FormSkeleton } from '@/components/Skeleton' +import { Breadcrumbs } from '@/components/Breadcrumbs' import { useConfirm } from '@/lib/useConfirm' import { useStores } from '@/lib/useLookups' import { useOrgSettings } from '@/lib/useOrgSettings' @@ -189,14 +190,19 @@ export function TransferEditPage() {
+

{isNew ? 'Новое перемещение' : existing.data?.number ?? 'Перемещение'}

-

- {isPosted - ? Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} - : 'Черновик — товар не перемещён, пока не проведёшь'} -

+ {isPosted && ( +

+ Проведён {existing.data?.postedAt ? new Date(existing.data.postedAt).toLocaleString('ru') : ''} +

+ )}