feat(web): Empty states с CTA на list-страницах
Some checks are pending
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

Item 5 Sprint 7 — заменил сухое «Нет данных» в DataTable на дружелюбный
центрированный блок: иконка + заголовок + объяснение + CTA «Создать первый
…». Показывается только когда нет поиска/фильтров (truly fresh org), иначе
обычный fallback DataTable.empty.

Компонент: src/components/EmptyState.tsx — Lucide-иконка, optional
actionLabel + onAction, optional secondaryLabel + onSecondary.

Применено (14 страниц):
- Catalog: Products (Package → /catalog/products/new), Counterparties
  (Users → открыть create-modal через setForm)
- Inventory: Enters (PackagePlus), Losses (Trash2),
  Transfers (ArrowLeftRight), Inventories (ClipboardList)
- Purchases: Supplies (PackagePlus), SupplierReturns (Undo2)
- Sales: Demands (Truck), RetailSales (Receipt)
- Reports: Sales (BarChart3), Stock (Warehouse), Profit (TrendingUp),
  Abc (BarChart3) — без CTA, текст «отчёт построится когда…»

Тексты — конкретные («Списания фиксируют выбытие — просрочка, брак»),
не «нажмите чтобы создать».

tsc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
nns 2026-05-30 11:16:11 +05:00
parent cd83269d3a
commit 8d532927e2
15 changed files with 430 additions and 244 deletions

View file

@ -0,0 +1,56 @@
import type { ReactNode } from 'react'
import type { LucideIcon } from 'lucide-react'
import { Button } from './Button'
/**
* Centered empty-state block для list-страниц, когда данных пока нет.
* Заменяет «Нет данных» в DataTable на дружественный CTA: иконка +
* заголовок + пояснение + кнопка «Создать первый ».
*
* Использование на list-pages: проверить (items.length===0 && !isLoading)
* и подменить таблицу на <EmptyState>. Можно использовать как `empty`
* prop у DataTable он отрендерит внутри пустой строки.
*/
interface EmptyStateProps {
icon: LucideIcon
title: string
description?: ReactNode
/** CTA-кнопка справа от описания: для создания первой записи. */
actionLabel?: string
onAction?: () => void
/** Если задано, рисуется secondary-link под основной кнопкой. */
secondaryLabel?: string
onSecondary?: () => void
}
export function EmptyState({
icon: Icon, title, description, actionLabel, onAction, secondaryLabel, onSecondary,
}: EmptyStateProps) {
return (
<div className="h-full min-h-[400px] flex flex-col items-center justify-center text-center px-6 py-12">
<div className="w-16 h-16 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center mb-4">
<Icon className="w-7 h-7 text-slate-400 dark:text-slate-500" />
</div>
<h3 className="text-base font-semibold text-slate-900 dark:text-slate-100">{title}</h3>
{description && (
<p className="mt-1.5 text-sm text-slate-500 dark:text-slate-400 max-w-md">{description}</p>
)}
{(actionLabel || secondaryLabel) && (
<div className="mt-5 flex flex-col items-center gap-2">
{actionLabel && onAction && (
<Button onClick={onAction}>{actionLabel}</Button>
)}
{secondaryLabel && onSecondary && (
<button
type="button"
onClick={onSecondary}
className="text-xs text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 underline-offset-4 hover:underline"
>
{secondaryLabel}
</button>
)}
</div>
)}
</div>
)
}

View file

@ -1,8 +1,9 @@
import { useState } from 'react' import { useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { Download } from 'lucide-react' import { Download, BarChart3 } from 'lucide-react'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { EmptyState } from '@/components/EmptyState'
import { Field, Select } from '@/components/Field' import { Field, Select } from '@/components/Field'
import { DateField } from '@/components/DateField' import { DateField } from '@/components/DateField'
import { useStores, useProductGroups } from '@/lib/useLookups' import { useStores, useProductGroups } from '@/lib/useLookups'
@ -141,7 +142,11 @@ export function AbcReportPage() {
<section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4"> <section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4">
{rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>} {rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>}
{!rep.isLoading && (rep.data?.length ?? 0) === 0 && ( {!rep.isLoading && (rep.data?.length ?? 0) === 0 && (
<div className="text-sm text-slate-500 py-6 text-center">За период нет продаж.</div> <EmptyState
icon={BarChart3}
title="Данных для ABC-анализа нет"
description="Анализ запускается по периоду продаж — пока их недостаточно, классы не построятся."
/>
)} )}
{!rep.isLoading && rep.data && rep.data.length > 0 && ( {!rep.isLoading && rep.data && rep.data.length > 0 && (
<div className="overflow-x-auto"> <div className="overflow-x-auto">

View file

@ -1,10 +1,11 @@
import { useState } from 'react' import { useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { validateEmail, validatePhone } from '@/lib/validation' import { validateEmail, validatePhone } from '@/lib/validation'
import { Plus, Trash2 } from 'lucide-react' import { Plus, Trash2, Users } from 'lucide-react'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -87,6 +88,15 @@ export function CounterpartiesPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={Users}
title="Контрагентов пока нет"
description="Здесь будут поставщики и покупатели — добавьте первого."
actionLabel="Добавить контрагента"
onAction={() => { setForm(blankForm); setFieldErrors({}) }}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -109,6 +119,7 @@ export function CounterpartiesPage() {
{ header: 'Страна', width: '120px', sortKey: 'country', cell: (r) => r.countryName ?? '—' }, { header: 'Страна', width: '120px', sortKey: 'country', cell: (r) => r.countryName ?? '—' },
]} ]}
/> />
)}
</ListPageShell> </ListPageShell>
<Modal <Modal

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, Truck } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function DemandsPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={Truck}
title="Оптовых отгрузок пока нет"
description="Отгрузки оформляются для оптовых клиентов с оплатой постфактум или предоплатой."
actionLabel="Создать отгрузку"
onAction={() => navigate('/sales/demands/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -61,6 +71,7 @@ export function DemandsPage() {
]} ]}
empty="Отгрузок пока нет. Создай первую — товар спишется со склада после проведения." empty="Отгрузок пока нет. Создай первую — товар спишется со склада после проведения."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, PackagePlus } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function EntersPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={PackagePlus}
title="Оприходований пока нет"
description="Оприходование используется для первичной партии товаров — например при запуске магазина без приёмки."
actionLabel="Создать оприходование"
onAction={() => navigate('/inventory/enters/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -58,6 +68,7 @@ export function EntersPage() {
]} ]}
empty="Оприходований пока нет. Создай первое — товар попадёт на склад после проведения." empty="Оприходований пока нет. Создай первое — товар попадёт на склад после проведения."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, ClipboardList } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function InventoriesPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={ClipboardList}
title="Инвентаризаций пока нет"
description="Инвентаризация сверяет фактические остатки с книжными и формирует корректировки."
actionLabel="Создать инвентаризацию"
onAction={() => navigate('/inventory/inventories/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -59,6 +69,7 @@ export function InventoriesPage() {
]} ]}
empty="Инвентаризаций пока нет." empty="Инвентаризаций пока нет."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, Trash2 } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function LossesPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={Trash2}
title="Списаний пока нет"
description="Списания фиксируют выбытие товаров (просрочка, брак, потери)."
actionLabel="Создать списание"
onAction={() => navigate('/inventory/losses/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -59,6 +69,7 @@ export function LossesPage() {
]} ]}
empty="Списаний пока нет." empty="Списаний пока нет."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,10 +1,11 @@
import { useState } from 'react' import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { Plus, Filter, X, FolderTree } from 'lucide-react' import { Plus, Filter, X, FolderTree, Package } from 'lucide-react'
import { useCatalogList } from '@/lib/useCatalog' import { useCatalogList } from '@/lib/useCatalog'
import { useOrgSettings } from '@/lib/useOrgSettings' import { useOrgSettings } from '@/lib/useOrgSettings'
import { usePriceTypes } from '@/lib/useLookups' import { usePriceTypes } from '@/lib/useLookups'
@ -282,6 +283,15 @@ export function ProductsPage() {
{/* Table */} {/* Table */}
<div className="flex-1 min-h-0 overflow-auto"> <div className="flex-1 min-h-0 overflow-auto">
{!isLoading && (data?.items?.length ?? 0) === 0 && !search && activeCount === 0 ? (
<EmptyState
icon={Package}
title="Здесь пока пусто"
description="Товары появятся когда вы создадите первый или сделаете приёмку."
actionLabel="Создать первый товар"
onAction={() => navigate('/catalog/products/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -293,6 +303,7 @@ export function ProductsPage() {
columns={baseColumns} columns={baseColumns}
empty="Товаров ещё нет. Они появятся после приёмки или через API." empty="Товаров ещё нет. Они появятся после приёмки или через API."
/> />
)}
</div> </div>
{data && data.total > 0 && ( {data && data.total > 0 && (

View file

@ -1,8 +1,9 @@
import { useState } from 'react' import { useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { Download } from 'lucide-react' import { Download, TrendingUp } from 'lucide-react'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { EmptyState } from '@/components/EmptyState'
import { Field, Select } from '@/components/Field' import { Field, Select } from '@/components/Field'
import { DateField } from '@/components/DateField' import { DateField } from '@/components/DateField'
import { useStores, useProductGroups } from '@/lib/useLookups' import { useStores, useProductGroups } from '@/lib/useLookups'
@ -139,7 +140,11 @@ export function ProfitReportPage() {
<section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4"> <section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4">
{rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>} {rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>}
{!rep.isLoading && (rep.data?.length ?? 0) === 0 && ( {!rep.isLoading && (rep.data?.length ?? 0) === 0 && (
<div className="text-sm text-slate-500 py-6 text-center">За период нет данных.</div> <EmptyState
icon={TrendingUp}
title="Данных для отчёта прибыли нет"
description="Отчёт строится по разнице цена-себестоимость по проведённым продажам."
/>
)} )}
{!rep.isLoading && rep.data && rep.data.length > 0 && ( {!rep.isLoading && rep.data && rep.data.length > 0 && (
<div className="overflow-x-auto"> <div className="overflow-x-auto">

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, Receipt } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -44,6 +45,15 @@ export function RetailSalesPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={Receipt}
title="Чеков пока нет"
description="Чеки приходят с касс POS либо создаются вручную."
actionLabel="Создать чек"
onAction={() => navigate('/sales/retail/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -69,6 +79,7 @@ export function RetailSalesPage() {
]} ]}
empty="Чеков пока нет. На Phase 5 здесь начнут падать продажи с касс. Сейчас можно создать вручную для теста." empty="Чеков пока нет. На Phase 5 здесь начнут падать продажи с касс. Сейчас можно создать вручную для теста."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,8 +1,9 @@
import { useState } from 'react' import { useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { Download } from 'lucide-react' import { Download, BarChart3 } from 'lucide-react'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { EmptyState } from '@/components/EmptyState'
import { Field, Select } from '@/components/Field' import { Field, Select } from '@/components/Field'
import { DateField } from '@/components/DateField' import { DateField } from '@/components/DateField'
import { useStores, useProductGroups } from '@/lib/useLookups' import { useStores, useProductGroups } from '@/lib/useLookups'
@ -137,7 +138,11 @@ export function SalesReportPage() {
<section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4"> <section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4">
{rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>} {rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>}
{!rep.isLoading && (rep.data?.length ?? 0) === 0 && ( {!rep.isLoading && (rep.data?.length ?? 0) === 0 && (
<div className="text-sm text-slate-500 py-6 text-center">За период нет данных.</div> <EmptyState
icon={BarChart3}
title="Данных для отчёта нет"
description="Отчёт строится по проведённым продажам — пока их нет, графика и таблиц нет."
/>
)} )}
{!rep.isLoading && rep.data && rep.data.length > 0 && ( {!rep.isLoading && rep.data && rep.data.length > 0 && (
<div className="overflow-x-auto"> <div className="overflow-x-auto">

View file

@ -1,8 +1,9 @@
import { useState } from 'react' import { useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { Download } from 'lucide-react' import { Download, Warehouse } from 'lucide-react'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
import { EmptyState } from '@/components/EmptyState'
import { Field, Select, Checkbox } from '@/components/Field' import { Field, Select, Checkbox } from '@/components/Field'
import { DateField } from '@/components/DateField' import { DateField } from '@/components/DateField'
import { useStores, useProductGroups } from '@/lib/useLookups' import { useStores, useProductGroups } from '@/lib/useLookups'
@ -114,7 +115,11 @@ export function StockReportPage() {
<section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4"> <section className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 p-4">
{rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>} {rep.isLoading && <div className="text-sm text-slate-500 py-6 text-center">Загружаю</div>}
{!rep.isLoading && (rep.data?.length ?? 0) === 0 && ( {!rep.isLoading && (rep.data?.length ?? 0) === 0 && (
<div className="text-sm text-slate-500 py-6 text-center">На эту дату нет остатков.</div> <EmptyState
icon={Warehouse}
title="Остатки пусты"
description="Остатки появятся после первой приёмки или оприходования."
/>
)} )}
{!rep.isLoading && rep.data && rep.data.length > 0 && ( {!rep.isLoading && rep.data && rep.data.length > 0 && (
<div className="overflow-x-auto"> <div className="overflow-x-auto">

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, Undo2 } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function SupplierReturnsPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={Undo2}
title="Возвратов поставщикам пока нет"
description="Возврат поставщику оформляется на брак или излишки приёмки."
actionLabel="Создать возврат"
onAction={() => navigate('/purchases/supplier-returns/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -60,6 +70,7 @@ export function SupplierReturnsPage() {
]} ]}
empty="Возвратов поставщикам нет." empty="Возвратов поставщикам нет."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus, PackagePlus } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function SuppliesPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={PackagePlus}
title="Приёмок пока нет"
description="Приёмки пополняют склад и обновляют себестоимость скользящим средним."
actionLabel="Создать приёмку"
onAction={() => navigate('/purchases/supplies/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -59,6 +69,7 @@ export function SuppliesPage() {
]} ]}
empty="Приёмок пока нет. Создай первую — товар попадёт на склад после проведения." empty="Приёмок пока нет. Создай первую — товар попадёт на склад после проведения."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }

View file

@ -1,7 +1,8 @@
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Plus, ArrowRight } from 'lucide-react' import { Plus, ArrowRight, ArrowLeftRight } from 'lucide-react'
import { ListPageShell } from '@/components/ListPageShell' import { ListPageShell } from '@/components/ListPageShell'
import { DataTable } from '@/components/DataTable' import { DataTable } from '@/components/DataTable'
import { EmptyState } from '@/components/EmptyState'
import { Pagination } from '@/components/Pagination' import { Pagination } from '@/components/Pagination'
import { SearchBar } from '@/components/SearchBar' import { SearchBar } from '@/components/SearchBar'
import { Button } from '@/components/Button' import { Button } from '@/components/Button'
@ -36,6 +37,15 @@ export function TransfersPage() {
<Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} /> <Pagination page={page} pageSize={data.pageSize} total={data.total} onPageChange={setPage} />
)} )}
> >
{!isLoading && (data?.items?.length ?? 0) === 0 && !search ? (
<EmptyState
icon={ArrowLeftRight}
title="Перемещений пока нет"
description="Перемещения переносят товар между складами."
actionLabel="Создать перемещение"
onAction={() => navigate('/inventory/transfers/new')}
/>
) : (
<DataTable <DataTable
rows={data?.items ?? []} rows={data?.items ?? []}
isLoading={isLoading} isLoading={isLoading}
@ -64,6 +74,7 @@ export function TransfersPage() {
]} ]}
empty="Перемещений пока нет." empty="Перемещений пока нет."
/> />
)}
</ListPageShell> </ListPageShell>
) )
} }