docs: mark admin panel spec as implemented
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# Admin Panel Design — Осколкам.Нет
|
||||
|
||||
**Дата:** 2026-05-07
|
||||
**Статус:** Implemented
|
||||
**Проект:** glass (бронирование окон)
|
||||
|
||||
---
|
||||
|
||||
## Контекст
|
||||
|
||||
Публичный сайт `Осколкам.Нет` — React 19 + Vite, single-page маркетинговый лендинг на русском языке. Форма заявки отправляет данные в Telegram Bot API. Бекенда нет. Цель — добавить внутреннюю систему управления заявками и заказами, не меняя публичную часть.
|
||||
|
||||
---
|
||||
|
||||
## Технический стек
|
||||
|
||||
| Компонент | Решение |
|
||||
|-----------|---------|
|
||||
| Роутинг | `react-router-dom` — маршрут `/admin/*` в том же приложении |
|
||||
| База данных | Supabase (PostgreSQL) |
|
||||
| Авторизация | Supabase Auth — email + пароль |
|
||||
| Drag-and-drop | `@dnd-kit/core` + `@dnd-kit/sortable` |
|
||||
| Графики | `recharts` |
|
||||
| Стиль | Те же CSS-переменные и шрифты, что у публичного сайта |
|
||||
|
||||
Новые зависимости: `@supabase/supabase-js`, `react-router-dom`, `recharts`, `@dnd-kit/core`, `@dnd-kit/sortable`
|
||||
|
||||
---
|
||||
|
||||
## Схема базы данных
|
||||
|
||||
### Таблица `orders`
|
||||
```sql
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid()
|
||||
created_at timestamptz DEFAULT now()
|
||||
name text NOT NULL -- ФИО / организация
|
||||
phone text NOT NULL
|
||||
object_type text -- industrial | commercial | infrastructure | residential | other
|
||||
area_sqm numeric -- площадь из калькулятора
|
||||
film_thickness integer -- 200 | 300 микрон
|
||||
estimated_cost numeric -- из калькулятора
|
||||
final_cost numeric -- фактическая стоимость после замера
|
||||
status text DEFAULT 'новая' -- новая | замер | согласование | монтаж | закрыт
|
||||
notes text
|
||||
assigned_to text -- email ответственного
|
||||
closed_at timestamptz
|
||||
source text DEFAULT 'form' -- form | manual
|
||||
```
|
||||
|
||||
### Таблица `order_events`
|
||||
```sql
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid()
|
||||
order_id uuid REFERENCES orders(id) ON DELETE CASCADE
|
||||
created_at timestamptz DEFAULT now()
|
||||
created_by text -- email пользователя
|
||||
event_type text -- status_change | note | edit
|
||||
description text -- человекочитаемое описание события
|
||||
```
|
||||
|
||||
RLS политики: все операции только для аутентифицированных пользователей.
|
||||
|
||||
---
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
src/
|
||||
lib/
|
||||
supabase.js ← единственный Supabase-клиент
|
||||
|
||||
admin/
|
||||
auth/
|
||||
LoginPage.jsx
|
||||
ProtectedRoute.jsx
|
||||
useAuth.js ← хук: сессия, login, logout, onAuthStateChange
|
||||
|
||||
layout/
|
||||
AdminLayout.jsx ← обёртка: сайдбар + шапка + <Outlet />
|
||||
Sidebar.jsx ← навигация, кнопка выхода
|
||||
|
||||
orders/
|
||||
KanbanBoard.jsx ← DndContext, 5 колонок
|
||||
KanbanColumn.jsx ← SortableContext, карточки
|
||||
OrderCard.jsx ← краткая инфо, drag handle
|
||||
OrderModal.jsx ← детали + редактирование + лог событий
|
||||
useOrders.js ← CRUD, смена статуса, realtime subscription
|
||||
|
||||
analytics/
|
||||
AnalyticsDashboard.jsx ← KPI-карточки + 4 графика
|
||||
CompletedOrdersTable.jsx ← таблица закрытых заказов + экспорт CSV
|
||||
useAnalytics.js ← агрегированные запросы по периоду
|
||||
charts/
|
||||
FunnelChart.jsx
|
||||
RevenueChart.jsx
|
||||
ObjectTypeChart.jsx
|
||||
AvgTimeChart.jsx
|
||||
|
||||
App.jsx ← добавляются маршруты /admin/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Маршруты
|
||||
|
||||
```
|
||||
/admin → redirect → /admin/kanban
|
||||
/admin/login → LoginPage (public)
|
||||
/admin/kanban → AdminLayout > KanbanBoard (protected)
|
||||
/admin/analytics → AdminLayout > AnalyticsDashboard (protected)
|
||||
/admin/completed → AdminLayout > CompletedOrdersTable (protected)
|
||||
```
|
||||
|
||||
`ProtectedRoute` проверяет сессию через `supabase.auth.getSession()`. Если сессии нет — редирект на `/admin/login` с сохранением `returnUrl`.
|
||||
|
||||
---
|
||||
|
||||
## Канбан-доска
|
||||
|
||||
**Стадии:** `новая → замер → согласование → монтаж → закрыт`
|
||||
|
||||
- Drag-and-drop через `@dnd-kit`. При drop: `UPDATE orders SET status = $1` + INSERT в `order_events`.
|
||||
- `useOrders` подписывается на Supabase Realtime — изменения от другого пользователя появляются без перезагрузки.
|
||||
- Карточка `OrderCard` показывает: имя, тип объекта, площадь, ориентировочная стоимость, дата создания.
|
||||
- Клик на карточку → `OrderModal`: все поля редактируемые, лог событий снизу, кнопка смены статуса (для мобильного).
|
||||
- Кнопка «+ Заявка» в шапке → модалка создания заказа вручную (`source: 'manual'`).
|
||||
|
||||
**Интеграция с формой сайта:**
|
||||
`LeadForm.jsx` после успешной отправки в Telegram делает `supabase.from('orders').insert(...)` со статусом `новая`. Ошибка вставки в Supabase не блокирует отправку в Telegram (try/catch раздельно).
|
||||
|
||||
---
|
||||
|
||||
## Аналитика
|
||||
|
||||
**Фильтр периода:** 7 дней / 30 дней / 90 дней / 365 дней / произвольный диапазон дат.
|
||||
|
||||
**KPI-карточки (4 шт.):**
|
||||
1. Всего заявок за период
|
||||
2. Закрыто + конверсия %
|
||||
3. Выручка (сумма `final_cost` закрытых заказов)
|
||||
4. Средний чек
|
||||
|
||||
**Графики:**
|
||||
1. `FunnelChart` — горизонтальный BarChart: количество заказов в каждой стадии + % перехода
|
||||
2. `RevenueChart` — LineChart: выручка по месяцам (закрытые заказы)
|
||||
3. `ObjectTypeChart` — PieChart (donut): распределение по типам объектов
|
||||
4. `AvgTimeChart` — BarChart: среднее время (дни) в каждой стадии. Рассчитывается из `order_events`: разница `created_at` между событиями смены статуса для каждого заказа, усреднённая по всем заказам в периоде.
|
||||
|
||||
**Вкладка «Завершённые»:**
|
||||
Таблица закрытых заказов с колонками: дата закрытия, имя, тип объекта, площадь, итоговая стоимость, ответственный. Сортировка по любой колонке. Экспорт в CSV через `Blob` + `URL.createObjectURL`.
|
||||
|
||||
---
|
||||
|
||||
## Layout
|
||||
|
||||
**Сайдбар** (фиксированный, 240px):
|
||||
- Фон `#0f172a` (тёмный, соответствует `--text-main` сайта)
|
||||
- Логотип «Осколкам.Нет» вверху
|
||||
- Навигация: Канбан / Аналитика / Завершённые
|
||||
- Внизу: email пользователя + кнопка «Выйти»
|
||||
|
||||
**Шапка** (фиксированная, 60px):
|
||||
- Название текущего раздела
|
||||
- Badge с количеством новых необработанных заявок
|
||||
- Кнопка «+ Заявка»
|
||||
|
||||
**Стиль:** Те же CSS-переменные (`--accent-blue`, `--accent-orange`, `--font-heading`, etc.), те же компоненты `.glass-panel`, `.btn-primary`.
|
||||
|
||||
---
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Добавляются в `.env` и `.env.example`:
|
||||
```
|
||||
VITE_SUPABASE_URL=
|
||||
VITE_SUPABASE_ANON_KEY=
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Аудит текущего кода
|
||||
|
||||
Обнаруженные проблемы, которые нужно исправить в рамках задачи:
|
||||
|
||||
1. **`Estimator.jsx:8`** — `windows` state существует, но не участвует в расчёте стоимости (формула зависит только от `area` и `thickness`). Удалить слайдер «Количество окон» из UI как вводящий в заблуждение.
|
||||
2. **`Navbar.jsx:52`** — `href="tel:112"` — заглушка. Требует реального номера (вне скоупа этой задачи, задокументировано).
|
||||
3. **`LeadForm.jsx`** — нет интеграции с Supabase. Добавляется в рамках этой задачи.
|
||||
4. **`App.jsx`** — нет роутера. Добавляется `BrowserRouter` + `Routes`.
|
||||
Reference in New Issue
Block a user