From 8fb55993a16e3a251c6a478d6db2f27288cd6df9 Mon Sep 17 00:00:00 2001 From: nns <278048682+nurdotnet@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:08:03 +0500 Subject: [PATCH] feat(onboarding): welcome dashboard with first-steps cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Дефолтная страница после логина (/) — OnboardingPage по образу МойСклад «Первые шаги». Старый DashboardPage с KPI и графиком переехал на /dashboard, в меню «Главная» теперь два пункта: «Главная» (онбординг) и «Аналитика» (KPI/графики). useOnboardingProgress() — хук, считает 4 шага: - orgConfigured: country + defaultCurrency установлены - hasEmployees: > 1 сотрудник (помимо админа) - hasProducts: > 0 товаров - hasSupplies: > 0 приёмок OnboardingPage: - Прогресс-бар «N из 4 шагов» с процентом - 4 карточки задач: Настройки → Сотрудники → Каталог/Импорт → Приёмка - Каждая показывает иконку (CheckCircle2 если done) + бэйдж категории + заголовок + описание + CTA-кнопка с ArrowRight, меняющая текст и ссылку в зависимости от done. - Когда все 4 шага сделаны — плашка «🎉 Готово!» + переход на /dashboard. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/food-market.web/src/App.tsx | 4 +- .../src/components/AppLayout.tsx | 3 +- .../src/lib/useOnboardingProgress.ts | 44 ++++++ .../src/pages/OnboardingPage.tsx | 143 ++++++++++++++++++ 4 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/food-market.web/src/lib/useOnboardingProgress.ts create mode 100644 src/food-market.web/src/pages/OnboardingPage.tsx diff --git a/src/food-market.web/src/App.tsx b/src/food-market.web/src/App.tsx index d4cc66b..9057459 100644 --- a/src/food-market.web/src/App.tsx +++ b/src/food-market.web/src/App.tsx @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { LoginPage } from '@/pages/LoginPage' import { DashboardPage } from '@/pages/DashboardPage' +import { OnboardingPage } from '@/pages/OnboardingPage' import { CountriesPage } from '@/pages/CountriesPage' import { UnitsOfMeasurePage } from '@/pages/UnitsOfMeasurePage' import { PriceTypesPage } from '@/pages/PriceTypesPage' @@ -41,7 +42,8 @@ export default function App() { } /> }> }> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/food-market.web/src/components/AppLayout.tsx b/src/food-market.web/src/components/AppLayout.tsx index 682a5ff..ccbfef3 100644 --- a/src/food-market.web/src/components/AppLayout.tsx +++ b/src/food-market.web/src/components/AppLayout.tsx @@ -31,7 +31,8 @@ function buildNav(): NavSection[] { ] return [ { group: 'Главное', items: [ - { to: '/', icon: LayoutDashboard, label: 'Dashboard', end: true }, + { to: '/', icon: LayoutDashboard, label: 'Главная', end: true }, + { to: '/dashboard', icon: LayoutDashboard, label: 'Аналитика' }, ]}, { group: 'Каталог', items: catalog }, { group: 'Контрагенты', items: [ diff --git a/src/food-market.web/src/lib/useOnboardingProgress.ts b/src/food-market.web/src/lib/useOnboardingProgress.ts new file mode 100644 index 0000000..211a6a4 --- /dev/null +++ b/src/food-market.web/src/lib/useOnboardingProgress.ts @@ -0,0 +1,44 @@ +import { useQuery } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { PagedResult } from '@/lib/types' +import { useOrgSettings } from '@/lib/useOrgSettings' + +interface EmployeeRow { id: string; userId: string | null } + +/** Прогресс по «первым шагам» новой организации: настройки + сотрудники + + * товары + первая приёмка. Используется в OnboardingPage. */ +export function useOnboardingProgress() { + const org = useOrgSettings() + const employees = useQuery({ + queryKey: ['onboarding:employees-count'], + queryFn: async () => (await api.get>('/api/organization/employees?pageSize=1')).data.total, + staleTime: 60_000, + }) + const products = useQuery({ + queryKey: ['onboarding:products-count'], + queryFn: async () => (await api.get>('/api/catalog/products?pageSize=1')).data.total, + staleTime: 60_000, + }) + const supplies = useQuery({ + queryKey: ['onboarding:supplies-count'], + queryFn: async () => (await api.get>('/api/purchases/supplies?pageSize=1')).data.total, + staleTime: 60_000, + }) + + const orgConfigured = !!org.data?.defaultCurrencyId && !!org.data?.countryCode + const hasEmployees = (employees.data ?? 0) > 1 // больше одного admin'а + const hasProducts = (products.data ?? 0) > 0 + const hasSupplies = (supplies.data ?? 0) > 0 + + const steps = { orgConfigured, hasEmployees, hasProducts, hasSupplies } + const done = Object.values(steps).filter(Boolean).length + const total = Object.keys(steps).length + return { + isLoading: org.isLoading || employees.isLoading || products.isLoading || supplies.isLoading, + ...steps, + doneCount: done, + totalCount: total, + overall: total === 0 ? 0 : Math.round((done / total) * 100), + allDone: done === total, + } +} diff --git a/src/food-market.web/src/pages/OnboardingPage.tsx b/src/food-market.web/src/pages/OnboardingPage.tsx new file mode 100644 index 0000000..34654ed --- /dev/null +++ b/src/food-market.web/src/pages/OnboardingPage.tsx @@ -0,0 +1,143 @@ +import { Link } from 'react-router-dom' +import { Settings, UserCog, Package, ShoppingCart, ArrowRight, CheckCircle2, BarChart3 } from 'lucide-react' +import { PageHeader } from '@/components/PageHeader' +import { useOnboardingProgress } from '@/lib/useOnboardingProgress' + +interface StepCard { + done: boolean + badge: string + title: string + description: string + ctaLabel: string + ctaTo: string + ctaDisabled?: boolean + icon: React.ComponentType<{ className?: string }> +} + +export function OnboardingPage() { + const p = useOnboardingProgress() + + const cards: StepCard[] = [ + { + done: p.orgConfigured, + badge: 'Запуск магазина', + title: 'Основные настройки бизнеса', + description: 'Укажите название, страну и валюту. От этого зависят валюта цен и ставка НДС в карточках товаров.', + ctaLabel: p.orgConfigured ? 'Изменить настройки' : 'Перейти к настройкам', + ctaTo: '/settings/organization', + icon: Settings, + }, + { + done: p.hasEmployees, + badge: 'Команда магазина', + title: 'Добавьте кассира, кладовщика и других сотрудников', + description: 'Каждому сотруднику — своя роль с набором прав. Кассирам можно привязать конкретные кассы.', + ctaLabel: p.hasEmployees ? 'Управление сотрудниками' : 'Добавить сотрудников', + ctaTo: '/settings/employees', + icon: UserCog, + }, + { + done: p.hasProducts, + badge: 'Каталог', + title: 'Заведите товары вручную или импортируйте из МойСклад', + description: 'Товары — основа всего. Импорт подтянет наименования, штрихкоды, цены и остатки одним нажатием.', + ctaLabel: p.hasProducts ? 'Перейти в каталог' : 'Добавить товары', + ctaTo: p.hasProducts ? '/catalog/products' : '/admin/import/moysklad', + icon: Package, + }, + { + done: p.hasSupplies, + badge: 'Закупки', + title: 'Создайте первую приёмку товара', + description: 'Проведённая приёмка кладёт товар на склад и обновляет себестоимость. Сканируйте штрихкоды или ищите вручную.', + ctaLabel: p.hasSupplies ? 'К приёмкам' : 'Создать приёмку', + ctaTo: p.hasSupplies ? '/purchases/supplies' : '/purchases/supplies/new', + icon: ShoppingCart, + }, + ] + + return ( +
+
+ + + {/* Прогресс-плашка */} +
+
+
+
Готовность системы
+
{p.doneCount} из {p.totalCount} шагов
+
+ {p.allDone ? ( + + Открыть дашборд + + ) : ( + {p.overall}% + )} +
+
+
+
+ {p.allDone && ( +
+ 🎉 Готово! Система настроена и готова к работе. +
+ )} +
+ + {/* Карточки шагов */} +
+ {cards.map((c) => ( +
+
+
+ {c.done ? : } +
+
+
{c.badge}
+

{c.title}

+
+
+

{c.description}

+ {c.ctaDisabled ? ( + + ) : ( + + {c.ctaLabel} + + )} +
+ ))} +
+ + {/* Поддержка */} +
+

Помощь и поддержка

+

+ Возникли вопросы по настройке или работе системы? Напишите администратору + или загляните в документацию проекта на Forgejo. +

+
+
+
+ ) +}