From 17a6da2f8bc38933826d043ef256d7052cea4bd5 Mon Sep 17 00:00:00 2001
From: nns
Date: Sat, 30 May 2026 10:38:31 +0500
Subject: [PATCH] =?UTF-8?q?feat(web):=20ConfirmDialog=20=D0=BA=D0=BE=D0=BC?=
=?UTF-8?q?=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20+=20useConfirm=20hook=20?=
=?UTF-8?q?=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20window.confirm()?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Item 2 Sprint 7 — заменил все нативные confirm() в фронте на собственный
с понятной типографикой, Esc=cancel, focus-on-Cancel
(чтобы случайный Enter не подтверждал удаление), tone='danger' | 'warning'.
Компоненты:
- src/components/ConfirmDialog.tsx — UI поверх Modal-overlay, AlertTriangle
иконка, primary/danger кнопки. Текст description конкретный («Удалить
товар «Молоко 3.2%»? Действие необратимо»). aria-labelledby выставлен.
- src/lib/useConfirm.ts — хук-обёртка: const { confirm, dialogProps } =
useConfirm(); if (await confirm({...})) action(). Возвращает Promise.
Button: переведён на forwardRef, чтобы dialog мог поставить фокус на Cancel.
Применено (17 страниц + 1 компонент):
- ProductEditPage (delete product)
- DemandEditPage / EnterEditPage / InventoryEditPage / LossEditPage /
SupplierReturnEditPage / TransferEditPage / SupplyEditPage / RetailSaleEditPage:
delete draft + post + unpost (всего 3 диалога на форму)
- EmployeesPage: уволить (warning) / удалить навсегда (danger), сохранена
динамика по статусу
- CounterpartiesPage / StoresPage / ProductGroupsPage / RetailPointsPage /
CountriesPage / PriceTypesPage / EmployeeRolesPage / SuperAdminUnitsOfMeasurePage:
delete с именем сущности в description
- ProductImageGallery: delete image
tsc --noEmit: clean. Текстов: в описаниях есть имена сущностей (товар,
номер документа), чтобы было ясно что именно удаляется.
Co-Authored-By: Claude Opus 4.7
---
src/food-market.web/src/components/Button.tsx | 10 ++-
.../src/components/ConfirmDialog.tsx | 89 +++++++++++++++++++
.../src/components/ProductImageGallery.tsx | 13 ++-
src/food-market.web/src/lib/useConfirm.ts | 59 ++++++++++++
.../src/pages/CounterpartiesPage.tsx | 10 ++-
.../src/pages/CountriesPage.tsx | 10 ++-
.../src/pages/DemandEditPage.tsx | 28 +++++-
.../src/pages/EmployeeRolesPage.tsx | 10 ++-
.../src/pages/EmployeesPage.tsx | 22 ++++-
.../src/pages/EnterEditPage.tsx | 28 +++++-
.../src/pages/InventoryEditPage.tsx | 28 +++++-
.../src/pages/LossEditPage.tsx | 28 +++++-
.../src/pages/PriceTypesPage.tsx | 10 ++-
.../src/pages/ProductEditPage.tsx | 12 ++-
.../src/pages/ProductGroupsPage.tsx | 10 ++-
.../src/pages/RetailPointsPage.tsx | 10 ++-
.../src/pages/RetailSaleEditPage.tsx | 19 +++-
src/food-market.web/src/pages/StoresPage.tsx | 10 ++-
.../pages/SuperAdminUnitsOfMeasurePage.tsx | 10 ++-
.../src/pages/SupplierReturnEditPage.tsx | 28 +++++-
.../src/pages/SupplyEditPage.tsx | 28 +++++-
.../src/pages/TransferEditPage.tsx | 28 +++++-
22 files changed, 453 insertions(+), 47 deletions(-)
create mode 100644 src/food-market.web/src/components/ConfirmDialog.tsx
create mode 100644 src/food-market.web/src/lib/useConfirm.ts
diff --git a/src/food-market.web/src/components/Button.tsx b/src/food-market.web/src/components/Button.tsx
index 704a935..21cedbf 100644
--- a/src/food-market.web/src/components/Button.tsx
+++ b/src/food-market.web/src/components/Button.tsx
@@ -1,4 +1,4 @@
-import type { ButtonHTMLAttributes, ReactNode } from 'react'
+import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { useReadOnly } from '@/lib/useReadOnly'
@@ -27,7 +27,10 @@ const sizes: Record = {
md: 'px-3.5 py-1.5 text-sm',
}
-export function Button({ variant = 'primary', size = 'md', mutating, className, children, disabled, title, ...rest }: ButtonProps) {
+export const Button = forwardRef(function Button(
+ { variant = 'primary', size = 'md', mutating, className, children, disabled, title, ...rest },
+ ref,
+) {
const ro = useReadOnly()
// Variant primary/danger по умолчанию считаем мутирующими (Добавить/
// Сохранить/Удалить/Создать почти всегда primary либо danger). Secondary/
@@ -38,6 +41,7 @@ export function Button({ variant = 'primary', size = 'md', mutating, className,
const blocked = isMutating && ro.readOnly
return (
+
>
)
}
diff --git a/src/food-market.web/src/pages/EnterEditPage.tsx b/src/food-market.web/src/pages/EnterEditPage.tsx
index 332b268..fb8fb7e 100644
--- a/src/food-market.web/src/pages/EnterEditPage.tsx
+++ b/src/food-market.web/src/pages/EnterEditPage.tsx
@@ -7,6 +7,8 @@ import { Button } from '@/components/Button'
import { Field, TextArea, Select, MoneyInput, NumberInput, Checkbox } from '@/components/Field'
import { DateField } from '@/components/DateField'
import { ProductPicker } from '@/components/ProductPicker'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useStores, useCurrencies } from '@/lib/useLookups'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { EnterStatus, type EnterDto, type Product } from '@/lib/types'
@@ -49,6 +51,7 @@ export function EnterEditPage() {
const stores = useStores()
const currencies = useCurrencies()
const org = useOrgSettings()
+ const { confirm, dialogProps } = useConfirm()
const [form, setForm] = useState
)
}
diff --git a/src/food-market.web/src/pages/LossEditPage.tsx b/src/food-market.web/src/pages/LossEditPage.tsx
index 93f43b7..19f93d4 100644
--- a/src/food-market.web/src/pages/LossEditPage.tsx
+++ b/src/food-market.web/src/pages/LossEditPage.tsx
@@ -7,6 +7,8 @@ import { Button } from '@/components/Button'
import { Field, TextArea, Select, MoneyInput, NumberInput, Checkbox } from '@/components/Field'
import { DateField } from '@/components/DateField'
import { ProductPicker } from '@/components/ProductPicker'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useStores, useCurrencies } from '@/lib/useLookups'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { LossStatus, LossReason, lossReasonLabel, type LossDto, type Product } from '@/lib/types'
@@ -48,6 +50,7 @@ export function LossEditPage() {
const stores = useStores()
const currencies = useCurrencies()
const org = useOrgSettings()
+ const { confirm, dialogProps } = useConfirm()
const [form, setForm] = useState(emptyForm)
const [pickerOpen, setPickerOpen] = useState(false)
@@ -205,7 +208,13 @@ export function LossEditPage() {
{isDraft && !isNew && (
- { if (confirm('Удалить черновик?')) remove.mutate() }}>
+ {
+ if (await confirm({
+ title: 'Удалить черновик списания?',
+ description: <>Удалить черновик № {existing.data?.number || '—'}? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) remove.mutate()
+ }}>
Удалить
)}
@@ -266,11 +275,21 @@ export function LossEditPage() {
label="Проведено"
checked={isPosted}
disabled={post.isPending || unpost.isPending || form.lines.length === 0}
- onChange={(v) => {
+ onChange={async (v) => {
if (v) {
- if (confirm('Провести? Товар будет списан со склада.')) post.mutate()
+ if (await confirm({
+ title: 'Провести списание?',
+ description: 'Товар будет списан со склада.',
+ confirmLabel: 'Провести',
+ tone: 'warning',
+ })) post.mutate()
} else {
- if (confirm('Снять проведение? Списанный товар вернётся на остаток.')) unpost.mutate()
+ if (await confirm({
+ title: 'Снять проведение?',
+ description: 'Списанный товар вернётся на остаток.',
+ confirmLabel: 'Снять',
+ tone: 'warning',
+ })) unpost.mutate()
}
}}
/>
@@ -357,6 +376,7 @@ export function LossEditPage() {
onClose={() => setPickerOpen(false)}
onPick={(p) => { addLineFromProduct(p); setPickerOpen(false) }}
/>
+
)
}
diff --git a/src/food-market.web/src/pages/PriceTypesPage.tsx b/src/food-market.web/src/pages/PriceTypesPage.tsx
index de6d72b..4e2c05e 100644
--- a/src/food-market.web/src/pages/PriceTypesPage.tsx
+++ b/src/food-market.web/src/pages/PriceTypesPage.tsx
@@ -7,6 +7,8 @@ import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput, Checkbox } from '@/components/Field'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog'
import { useQueryClient } from '@tanstack/react-query'
import type { PriceType } from '@/lib/types'
@@ -29,6 +31,7 @@ export function PriceTypesPage() {
const [form, setForm] = useState(null)
const [nameErr, setNameErr] = useState(null)
const qc = useQueryClient()
+ const { confirm, dialogProps } = useConfirm()
const save = async () => {
if (!form) return
@@ -92,7 +95,11 @@ export function PriceTypesPage() {
<>
{form?.id && !form.isSystem && (
{
- if (confirm('Удалить тип цены?')) {
+ if (await confirm({
+ title: 'Удалить тип цены?',
+ description: <>Удалить «{form.name || 'без названия'}»? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) {
await remove.mutateAsync(form.id!)
await qc.invalidateQueries({ queryKey: ['lookup:price-types'] })
setForm(null); setNameErr(null)
@@ -134,6 +141,7 @@ export function PriceTypesPage() {
)}
+
>
)
}
diff --git a/src/food-market.web/src/pages/ProductEditPage.tsx b/src/food-market.web/src/pages/ProductEditPage.tsx
index af788c5..1ac03ef 100644
--- a/src/food-market.web/src/pages/ProductEditPage.tsx
+++ b/src/food-market.web/src/pages/ProductEditPage.tsx
@@ -11,6 +11,8 @@ import {
import { useOrgSettings } from '@/lib/useOrgSettings'
import { BarcodeType, Packaging, type Product } from '@/lib/types'
import { ProductImageGallery } from '@/components/ProductImageGallery'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { generateEan13InternalPrefix2, generateBarcode, generateArticle } from '@/lib/barcode'
interface PriceRow { id?: string; priceTypeId: string; amount: number; currencyId: string }
@@ -60,6 +62,7 @@ export function ProductEditPage() {
const groups = useProductGroups()
const countries = useCountries()
const currencies = useCurrencies()
+ const { confirm, dialogProps } = useConfirm()
const priceTypes = usePriceTypes()
const org = useOrgSettings()
@@ -242,7 +245,13 @@ export function ProductEditPage() {
type="button"
variant="danger"
size="sm"
- onClick={() => { if (confirm('Удалить товар?')) remove.mutate() }}
+ onClick={async () => {
+ if (await confirm({
+ title: 'Удалить товар?',
+ description: <>Удалить «{form.name || 'без названия'}»? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) remove.mutate()
+ }}
>
Удалить
@@ -534,6 +543,7 @@ export function ProductEditPage() {
)}
+
)
}
diff --git a/src/food-market.web/src/pages/ProductGroupsPage.tsx b/src/food-market.web/src/pages/ProductGroupsPage.tsx
index 8bbc421..f9fe108 100644
--- a/src/food-market.web/src/pages/ProductGroupsPage.tsx
+++ b/src/food-market.web/src/pages/ProductGroupsPage.tsx
@@ -7,6 +7,8 @@ import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput, Select, PercentInput } from '@/components/Field'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog'
import type { ProductGroup } from '@/lib/types'
import { useQueryClient } from '@tanstack/react-query'
@@ -22,6 +24,7 @@ export function ProductGroupsPage() {
const { create, update, remove } = useCatalogMutations(URL, URL)
const [form, setForm] = useState(null)
const qc = useQueryClient()
+ const { confirm, dialogProps } = useConfirm()
// inline-сохранение наценки прямо из таблицы — без открытия модалки.
const saveMarkup = async (g: ProductGroup, markupPercent: number | null) => {
await api.put(`/api/catalog/product-groups/${g.id}`, {
@@ -102,7 +105,11 @@ export function ProductGroupsPage() {
<>
{form?.id && (
{
- if (confirm('Удалить группу? (должна быть пустой)')) {
+ if (await confirm({
+ title: 'Удалить группу?',
+ description: <>Удалить «{form.name || 'без названия'}»? Группа должна быть пустой.>,
+ confirmLabel: 'Удалить',
+ })) {
try { await remove.mutateAsync(form.id!); setForm(null) }
catch (e) { alert((e as Error).message) }
}
@@ -151,6 +158,7 @@ export function ProductGroupsPage() {
)}
+
>
)
}
diff --git a/src/food-market.web/src/pages/RetailPointsPage.tsx b/src/food-market.web/src/pages/RetailPointsPage.tsx
index 95f7c11..0447c81 100644
--- a/src/food-market.web/src/pages/RetailPointsPage.tsx
+++ b/src/food-market.web/src/pages/RetailPointsPage.tsx
@@ -9,6 +9,8 @@ import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput, Select, Checkbox } from '@/components/Field'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog'
import type { PagedResult, RetailPoint, Store } from '@/lib/types'
@@ -36,6 +38,7 @@ export function RetailPointsPage() {
const { create, update, remove } = useCatalogMutations(URL, URL)
const [form, setForm] = useState(null)
const [nameErr, setNameErr] = useState(null)
+ const { confirm, dialogProps } = useConfirm()
const stores = useQuery({
queryKey: ['stores-lookup'],
@@ -102,7 +105,11 @@ export function RetailPointsPage() {
<>
{form?.id && (
{
- if (confirm('Удалить кассу?')) {
+ if (await confirm({
+ title: 'Удалить кассу?',
+ description: <>Удалить «{form.name || 'без названия'}»? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) {
await remove.mutateAsync(form.id!)
setForm(null); setNameErr(null)
}
@@ -152,6 +159,7 @@ export function RetailPointsPage() {
)}
+
>
)
}
diff --git a/src/food-market.web/src/pages/RetailSaleEditPage.tsx b/src/food-market.web/src/pages/RetailSaleEditPage.tsx
index 2931db1..18fcf6e 100644
--- a/src/food-market.web/src/pages/RetailSaleEditPage.tsx
+++ b/src/food-market.web/src/pages/RetailSaleEditPage.tsx
@@ -6,6 +6,8 @@ import { api } from '@/lib/api'
import { Button } from '@/components/Button'
import { Field, TextInput, TextArea, Select, AsyncSelect, MoneyInput, NumberInput } from '@/components/Field'
import { ProductPicker } from '@/components/ProductPicker'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useStores, useCurrencies } from '@/lib/useLookups'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { RetailSaleStatus, PaymentMethod, type RetailSaleDto, type Product } from '@/lib/types'
@@ -53,6 +55,7 @@ export function RetailSaleEditPage() {
const stores = useStores()
const currencies = useCurrencies()
const org = useOrgSettings()
+ const { confirm, dialogProps } = useConfirm()
const [form, setForm] = useState(empty)
const [pickerOpen, setPickerOpen] = useState(false)
@@ -225,7 +228,12 @@ export function RetailSaleEditPage() {
type="button"
variant="secondary"
onClick={async () => {
- if (!confirm('Создать возврат по этому чеку?')) return
+ if (!await confirm({
+ title: 'Создать возврат?',
+ description: <>Создать возврат по чеку {existing.data?.number || '—'}?>,
+ confirmLabel: 'Создать возврат',
+ tone: 'warning',
+ })) return
const r = await api.post(`/api/sales/retail/${id}/create-return`)
navigate(`/sales/retail/${r.data.id}`)
}}
@@ -239,7 +247,13 @@ export function RetailSaleEditPage() {
)}
{isDraft && !isNew && (
- { if (confirm('Удалить черновик?')) remove.mutate() }}>
+ {
+ if (await confirm({
+ title: 'Удалить черновик чека?',
+ description: <>Удалить черновик № {existing.data?.number || '—'}? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) remove.mutate()
+ }}>
Удалить
)}
@@ -417,6 +431,7 @@ export function RetailSaleEditPage() {
setPickerOpen(false)} onPick={addLineFromProduct} />
+
)
}
diff --git a/src/food-market.web/src/pages/StoresPage.tsx b/src/food-market.web/src/pages/StoresPage.tsx
index 206c06d..279d355 100644
--- a/src/food-market.web/src/pages/StoresPage.tsx
+++ b/src/food-market.web/src/pages/StoresPage.tsx
@@ -7,6 +7,8 @@ import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput, Checkbox } from '@/components/Field'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog'
import { type Store } from '@/lib/types'
@@ -33,6 +35,7 @@ export function StoresPage() {
const { create, update, remove } = useCatalogMutations(URL, URL)
const [form, setForm] = useState(null)
const [nameErr, setNameErr] = useState(null)
+ const { confirm, dialogProps } = useConfirm()
const save = async () => {
if (!form) return
@@ -87,7 +90,11 @@ export function StoresPage() {
<>
{form?.id && (
{
- if (confirm('Удалить склад?')) {
+ if (await confirm({
+ title: 'Удалить склад?',
+ description: <>Удалить «{form.name || 'без названия'}»? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) {
await remove.mutateAsync(form.id!)
setForm(null); setNameErr(null)
}
@@ -128,6 +135,7 @@ export function StoresPage() {
)}
+
>
)
}
diff --git a/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx b/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx
index a9edb62..2652d3a 100644
--- a/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx
+++ b/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx
@@ -7,6 +7,8 @@ import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput } from '@/components/Field'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog'
import type { UnitOfMeasure } from '@/lib/types'
@@ -25,6 +27,7 @@ export function SuperAdminUnitsOfMeasurePage() {
const { create, update, remove } = useCatalogMutations(URL, URL)
const [form, setForm] = useState(null)
const [submitError, setSubmitError] = useState(null)
+ const { confirm, dialogProps } = useConfirm()
const save = async () => {
if (!form) return
@@ -43,7 +46,11 @@ export function SuperAdminUnitsOfMeasurePage() {
const onDelete = async () => {
if (!form?.id) return
- if (!confirm('Деактивировать единицу? Если на неё ссылаются товары или орги — операция не пройдёт.')) return
+ if (!await confirm({
+ title: 'Деактивировать единицу?',
+ description: <>Деактивировать «{form.name || 'без названия'}»? Если на неё ссылаются товары или орги — операция не пройдёт.>,
+ confirmLabel: 'Деактивировать',
+ })) return
try {
await remove.mutateAsync(form.id)
setForm(null)
@@ -125,6 +132,7 @@ export function SuperAdminUnitsOfMeasurePage() {
)}
+
>
)
}
diff --git a/src/food-market.web/src/pages/SupplierReturnEditPage.tsx b/src/food-market.web/src/pages/SupplierReturnEditPage.tsx
index 5a2ca64..c4110c8 100644
--- a/src/food-market.web/src/pages/SupplierReturnEditPage.tsx
+++ b/src/food-market.web/src/pages/SupplierReturnEditPage.tsx
@@ -7,6 +7,8 @@ import { Button } from '@/components/Button'
import { Field, TextArea, Select, AsyncSelect, MoneyInput, NumberInput, Checkbox } from '@/components/Field'
import { DateField } from '@/components/DateField'
import { ProductPicker } from '@/components/ProductPicker'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useStores, useCurrencies } from '@/lib/useLookups'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { SupplierReturnStatus, type SupplierReturnDto, type Product } from '@/lib/types'
@@ -50,6 +52,7 @@ export function SupplierReturnEditPage() {
const stores = useStores()
const currencies = useCurrencies()
const org = useOrgSettings()
+ const { confirm, dialogProps } = useConfirm()
const [form, setForm] = useState(emptyForm)
const [pickerOpen, setPickerOpen] = useState(false)
@@ -208,7 +211,13 @@ export function SupplierReturnEditPage() {
{isDraft && !isNew && (
- { if (confirm('Удалить черновик?')) remove.mutate() }}>
+ {
+ if (await confirm({
+ title: 'Удалить черновик возврата?',
+ description: <>Удалить черновик № {existing.data?.number || '—'}? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) remove.mutate()
+ }}>
Удалить
)}
@@ -270,11 +279,21 @@ export function SupplierReturnEditPage() {
label="Проведено"
checked={isPosted}
disabled={post.isPending || unpost.isPending || form.lines.length === 0}
- onChange={(v) => {
+ onChange={async (v) => {
if (v) {
- if (confirm('Провести? Товар спишется со склада в адрес поставщика.')) post.mutate()
+ if (await confirm({
+ title: 'Провести возврат поставщику?',
+ description: 'Товар спишется со склада в адрес поставщика.',
+ confirmLabel: 'Провести',
+ tone: 'warning',
+ })) post.mutate()
} else {
- if (confirm('Снять проведение? Товар вернётся на склад.')) unpost.mutate()
+ if (await confirm({
+ title: 'Снять проведение?',
+ description: 'Товар вернётся на склад.',
+ confirmLabel: 'Снять',
+ tone: 'warning',
+ })) unpost.mutate()
}
}}
/>
@@ -361,6 +380,7 @@ export function SupplierReturnEditPage() {
onClose={() => setPickerOpen(false)}
onPick={(p) => { addLineFromProduct(p); setPickerOpen(false) }}
/>
+
)
}
diff --git a/src/food-market.web/src/pages/SupplyEditPage.tsx b/src/food-market.web/src/pages/SupplyEditPage.tsx
index a7bc7d1..810ca40 100644
--- a/src/food-market.web/src/pages/SupplyEditPage.tsx
+++ b/src/food-market.web/src/pages/SupplyEditPage.tsx
@@ -8,6 +8,8 @@ import { Field, TextArea, Select, AsyncSelect, Checkbox, MoneyInput, NumberInput
import { DateField } from '@/components/DateField'
import { ProductPicker } from '@/components/ProductPicker'
import { SupplyLineQuickAdd, type AddedProduct } from '@/components/SupplyLineQuickAdd'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useStores, useCurrencies, usePriceTypes } from '@/lib/useLookups'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { SupplyStatus, type SupplyDto, type Product } from '@/lib/types'
@@ -64,6 +66,7 @@ export function SupplyEditPage() {
const currencies = useCurrencies()
const org = useOrgSettings()
const priceTypes = usePriceTypes()
+ const { confirm, dialogProps } = useConfirm()
// Системный (главный) тип цен — на нём по умолчанию ведётся розница на кассе.
// Заголовок колонки «Розничная» подменяется его именем чтобы соответствовать
// тому, что увидит пользователь в карточке товара и в справочнике типов цен.
@@ -282,7 +285,13 @@ export function SupplyEditPage() {
{isDraft && !isNew && (
-
{ if (confirm('Удалить черновик приёмки?')) remove.mutate() }}>
+ {
+ if (await confirm({
+ title: 'Удалить черновик приёмки?',
+ description: <>Удалить черновик № {existing.data?.number || '—'}? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) remove.mutate()
+ }}>
Удалить
)}
@@ -357,11 +366,21 @@ export function SupplyEditPage() {
checked={isPosted}
disabled={post.isPending || unpost.isPending || form.lines.length === 0
|| form.lines.some(l => l.quantity <= 0 || l.unitPrice <= 0)}
- onChange={(v) => {
+ onChange={async (v) => {
if (v) {
- if (confirm('После проведения товары будут оприходованы на склад и обновят себестоимость (скользящее среднее). Продолжить?')) post.mutate()
+ if (await confirm({
+ title: 'Провести приёмку?',
+ description: 'После проведения товары будут оприходованы на склад и обновят себестоимость (скользящее среднее).',
+ confirmLabel: 'Провести',
+ tone: 'warning',
+ })) post.mutate()
} else {
- if (confirm('Снять проведение? Остатки откатятся, себестоимость останется (пересчитать вручную при необходимости).')) unpost.mutate()
+ if (await confirm({
+ title: 'Снять проведение?',
+ description: 'Остатки откатятся, себестоимость останется (пересчитать вручную при необходимости).',
+ confirmLabel: 'Снять',
+ tone: 'warning',
+ })) unpost.mutate()
}
}}
/>
@@ -492,6 +511,7 @@ export function SupplyEditPage() {
)}
setPickerOpen(false)} onPick={addLineFromProduct} />
+
)
}
diff --git a/src/food-market.web/src/pages/TransferEditPage.tsx b/src/food-market.web/src/pages/TransferEditPage.tsx
index 5d549af..b1f7d32 100644
--- a/src/food-market.web/src/pages/TransferEditPage.tsx
+++ b/src/food-market.web/src/pages/TransferEditPage.tsx
@@ -7,6 +7,8 @@ import { Button } from '@/components/Button'
import { Field, TextArea, Select, MoneyInput, NumberInput, Checkbox } from '@/components/Field'
import { DateField } from '@/components/DateField'
import { ProductPicker } from '@/components/ProductPicker'
+import { ConfirmDialog } from '@/components/ConfirmDialog'
+import { useConfirm } from '@/lib/useConfirm'
import { useStores } from '@/lib/useLookups'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { TransferStatus, type TransferDto, type Product } from '@/lib/types'
@@ -46,6 +48,7 @@ export function TransferEditPage() {
const stores = useStores()
const org = useOrgSettings()
+ const { confirm, dialogProps } = useConfirm()
const [form, setForm] = useState(emptyForm)
const [pickerOpen, setPickerOpen] = useState(false)
@@ -190,7 +193,13 @@ export function TransferEditPage() {
{isDraft && !isNew && (
- { if (confirm('Удалить черновик?')) remove.mutate() }}>
+ {
+ if (await confirm({
+ title: 'Удалить черновик перемещения?',
+ description: <>Удалить черновик № {existing.data?.number || '—'}? Действие необратимо.>,
+ confirmLabel: 'Удалить',
+ })) remove.mutate()
+ }}>
Удалить
)}
@@ -248,11 +257,21 @@ export function TransferEditPage() {
label="Проведено"
checked={isPosted}
disabled={post.isPending || unpost.isPending || form.lines.length === 0}
- onChange={(v) => {
+ onChange={async (v) => {
if (v) {
- if (confirm('Провести? Товар спишется с первого склада и встанет на второй.')) post.mutate()
+ if (await confirm({
+ title: 'Провести перемещение?',
+ description: 'Товар спишется с первого склада и встанет на второй.',
+ confirmLabel: 'Провести',
+ tone: 'warning',
+ })) post.mutate()
} else {
- if (confirm('Снять проведение? Остатки обоих складов вернутся к предыдущим значениям.')) unpost.mutate()
+ if (await confirm({
+ title: 'Снять проведение?',
+ description: 'Остатки обоих складов вернутся к предыдущим значениям.',
+ confirmLabel: 'Снять',
+ tone: 'warning',
+ })) unpost.mutate()
}
}}
/>
@@ -339,6 +358,7 @@ export function TransferEditPage() {
onClose={() => setPickerOpen(false)}
onPick={(p) => { addLineFromProduct(p); setPickerOpen(false) }}
/>
+
)
}