diff --git a/README.md b/README.md index c1eb585..bc7fe89 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Construction Delivery Control -React-приложение для управления заказами, производством, логистикой и чатбот-коммуникацией через VK, Telegram и Messenger Max. +React-приложение для управления заказами, доставкой, ролями сотрудников и публичным согласованием доставки с клиентом. ## Запуск @@ -9,26 +9,25 @@ npm install npm run dev ``` -## Что реализовано +## Главный документ -- OTP-вход по email через Supabase Auth с demo-режимом без backend-конфига. -- Installable PWA-режим: приложение можно добавить на домашний экран и открыть как отдельное окно. -- Offline demo flow: после первого запуска дашборд и локальные demo-данные доступны без интернета. -- Role-based dashboard для менеджера, производства, логиста и администратора. -- Форма создания и редактирования заказов с автоназначением логиста. -- Карточка заказа с историей статусов, действий, чата и слотов доставки. -- Панели очереди производства и администраторского обзора пользователей. -- Светлая и тёмная тема, адаптивный минималистичный UI. -- Supabase SQL-схема с RLS, аудитом и расширением под нескольких логистов. -- Тестируемый сервисный слой для фильтрации, смены статусов и генерации новых заказов. -- Документация по архитектуре, ботам и пользовательским сценариям. +- [Обзор системы](/Users/mihailkucer/Documents/super-sam/docs/product-overview.md) — назначение приложения, роли, сценарии, клиентский flow и подготовка к показу. + +## Что уже есть + +- OTP-вход по email через Supabase Auth. +- Role-based dashboard для менеджера, логиста, водителя и администратора. +- Карточка заказа с историей, чатом и слотами доставки. +- Публичная страница `/delivery/:token` для выбора даты и половины дня доставки. +- Supabase SQL-схема, таблицы приглашений и Edge Functions для invitation flow. +- Документация по архитектуре, сценариям и интеграциям. ## Структура - `src/` — интерфейс и клиентская логика. -- `public/` — PWA manifest, service worker и install icons. - `supabase/schema.sql` — структура БД, роли, индексы, RLS, триггеры. -- `supabase/functions/` — заготовки Edge Functions для webhook и отправки сообщений в боты. +- `supabase/functions/` — Edge Functions для приглашений, статусов и чат-коммуникаций. +- `supabase/seed/stage-1-demo.sql` — рабочий набор seed-данных для показа. - `docs/architecture.md` — архитектура фронтенда и модулей. -- `docs/chatbot-integration.md` — логика интеграции VK/Telegram/Messenger Max. +- `docs/product-overview.md` — общий обзор продукта, ролей и сценариев. - `docs/scenarios.md` — сценарии жизненного цикла заказа. diff --git a/docs/product-overview.md b/docs/product-overview.md new file mode 100644 index 0000000..d95e961 --- /dev/null +++ b/docs/product-overview.md @@ -0,0 +1,129 @@ +# SuperSam: Обзор системы + +`SuperSam` — это система управления доставкой заказов, которая объединяет внутренние рабочие кабинеты сотрудников и публичную страницу для клиента. Приложение помогает пройти путь от готовности заказа к отгрузке до согласования доставки, назначения исполнителей, фиксации результата и контроля исключений. + +## Задачи приложения + +- собрать в одном месте информацию по заказам, доставке, истории действий и коммуникациям; +- разделить рабочие зоны по ролям, чтобы каждый сотрудник видел только свой контур задач; +- дать логисту инструмент для запуска и контроля согласования доставки; +- дать клиенту простую ссылку, по которой он может выбрать дату и половину дня доставки; +- сохранить в Supabase историю изменений, статусы и интеграционные события. + +## Роли + +### Менеджер + +Менеджер работает с заказами на ранних этапах: +- видит список заказов и карточки клиентов; +- следит за составом заказа и комментариями; +- передаёт заказ дальше по процессу после подтверждения. + +### Логист + +Логист отвечает за доставку: +- видит готовые к запуску и проблемные заказы; +- контролирует статусы согласования доставки; +- назначает и корректирует слоты; +- переводит заказ в ручную обработку, если клиент не ответил; +- отслеживает историю и связанные сообщения. + +### Водитель + +Водитель работает только со своими доставками: +- видит назначенные маршруты; +- открывает карточку точки доставки; +- фиксирует ход доставки и итоговый статус. + +### Администратор + +Администратор видит всю систему: +- пользователей и роли; +- общие списки заказов и событий; +- состояние интеграций и служебные данные. + +### Клиент + +Клиент не входит во внутренний кабинет. Он получает публичную ссылку вида `/delivery/:token` и по ней: +- видит номер заказа; +- выбирает удобную дату; +- выбирает половину дня: `До обеда` или `После обеда`; +- подтверждает выбор. + +## Основные сценарии + +### Внутренний сценарий + +1. Заказ попадает в систему. +2. Менеджер и внутренние сотрудники ведут заказ по этапам. +3. Когда заказ готов к доставке, логист запускает приглашение клиенту. +4. Клиент выбирает слот по публичной ссылке. +5. Система переводит заказ в `Доставка согласована`. +6. Логист и водитель доводят доставку до результата. + +### Сценарий клиента + +Клиентская страница работает по token из таблицы `public.delivery_invitations`. Для рабочего показа используется заранее загруженный seed-набор данных. + +После загрузки seed можно открыть ссылку: + +`/delivery/client-flow-1001` + +Эта ссылка должна показывать: +- заказ `CD-240031`; +- четыре варианта слота; +- две даты; +- две половины дня: `До обеда` и `После обеда`. + +После подтверждения выбора: +- invitation переводится в состояние `agreed`; +- заказ переводится в `Доставка согласована`; +- в `order_history` появляется запись о подтверждении; +- в `delivery_slots` фиксируется подтверждённый слот. + +## Что хранится в Supabase + +### Основные таблицы + +- `public.users` — пользователи и роли; +- `public.orders` — заказы и текущие статусы; +- `public.order_history` — история изменений; +- `public.delivery_slots` — возможные и подтверждённые слоты доставки; +- `public.delivery_invitations` — публичные invitation token и состояние клиентского flow; +- `public.integration_events` — технические и интеграционные события. + +### Важные поля для клиентского flow + +- `delivery_invitations.token_hash` — хеш публичного токена; +- `delivery_invitations.state` — состояние приглашения; +- `delivery_invitations.available_slots` — список доступных вариантов для клиента; +- `delivery_invitations.delivery_date` и `delivery_invitations.delivery_time` — выбранный или основной слот; +- `orders.status` — текущий рабочий статус заказа; +- `orders.delivery_agreement_status` — статус согласования доставки. + +## Как подготовить систему к показу + +1. Загрузить схему `supabase/schema.sql`. +2. Создать нужных пользователей в `auth.users`. +3. Выполнить `supabase/seed/stage-1-demo.sql`. +4. Убедиться, что Edge Functions развернуты: + - `get-delivery-invitation` + - `confirm-delivery-choice` + - `create-delivery-invitation` +5. Открыть внутренний кабинет. +6. Открыть клиентскую ссылку `/delivery/client-flow-1001`. + +## Что показывать на встрече + +- вход во внутренний кабинет; +- список заказов для менеджера, логиста и водителя; +- карточку заказа и статусы; +- клиентскую ссылку с выбором даты и половины дня; +- изменение статуса заказа после подтверждения клиентом. + +## Полезные документы + +- [README](/Users/mihailkucer/Documents/super-sam/README.md) +- [Архитектура](/Users/mihailkucer/Documents/super-sam/docs/architecture.md) +- [Сценарии](/Users/mihailkucer/Documents/super-sam/docs/scenarios.md) +- [Edge Functions](/Users/mihailkucer/Documents/super-sam/supabase/functions/README.md) diff --git a/docs/superpowers/plans/2026-04-14-product-docs-and-real-client-flow.md b/docs/superpowers/plans/2026-04-14-product-docs-and-real-client-flow.md new file mode 100644 index 0000000..eeeb8dd --- /dev/null +++ b/docs/superpowers/plans/2026-04-14-product-docs-and-real-client-flow.md @@ -0,0 +1,79 @@ +# Product Docs And Real Client Flow Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Создать один общий документ по приложению и ролям, подготовить рабочие Supabase seed-данные для клиентского приглашения и довести `/delivery/:token` до реального flow. + +**Architecture:** Документация собирается в одном основном продуктово-ориентированном документе, на который начинает ссылаться `README`. Клиентский flow остаётся построен на `delivery_invitations` и Edge Functions, но приводится к фактическому фронтенд-контракту: фронтенд вызывает functions через Supabase invoke, а seed поднимает один реальный invitation token с рабочими слотами. + +**Tech Stack:** React 18, Vite, Supabase SQL, Supabase Edge Functions, Vitest. + +--- + +## Chunk 1: Real Client Invitation Contract + +### Task 1: Зафиксировать тестами рабочий invitation contract + +**Files:** +- Modify: `src/services/deliveryInvitationApi.test.js` +- Modify or Create: client page test if needed + +- [ ] **Step 1: Add failing tests for real token flow** +- [ ] **Step 2: Run targeted tests and confirm failure** +- [ ] **Step 3: Cover returned delivery date/time and real slot format** + +### Task 2: Привести Edge Function и frontend API к одному контракту + +**Files:** +- Modify: `supabase/functions/get-delivery-invitation/index.ts` +- Modify: `supabase/functions/confirm-delivery-choice/index.ts` +- Modify: `src/services/deliveryInvitationApi.js` +- Modify: `src/pages/ClientDeliveryPage.jsx` + +- [ ] **Step 1: Support frontend invoke contract in get-delivery-invitation** +- [ ] **Step 2: Return invitation delivery date/time fields** +- [ ] **Step 3: Keep confirm-delivery-choice compatible with selected slot data** +- [ ] **Step 4: Re-run targeted tests and confirm pass** + +## Chunk 2: Supabase Seed For Demoable Client Flow + +### Task 3: Подготовить реальный invitation token и слоты в seed + +**Files:** +- Modify: `supabase/seed/stage-1-demo.sql` + +- [ ] **Step 1: Add one known invitation token** +- [ ] **Step 2: Store stable slot values for tomorrow/after-tomorrow style choices** +- [ ] **Step 3: Keep seed re-runnable** +- [ ] **Step 4: Ensure order status matches awaiting client choice** + +## Chunk 3: One General Product Document + +### Task 4: Собрать единый общий документ по системе + +**Files:** +- Create: `docs/product-overview.md` +- Modify: `README.md` + +- [ ] **Step 1: Write the main product document** +- [ ] **Step 2: Cover goals, roles, scenarios, client flow and Supabase entities** +- [ ] **Step 3: Link README to the new main document** + +## Chunk 4: Final Verification + +### Task 5: Проверить итоговый сценарий + +**Files:** +- Reference: `src/pages/ClientDeliveryPage.jsx` +- Reference: `supabase/seed/stage-1-demo.sql` +- Reference: `docs/product-overview.md` + +- [ ] **Step 1: Run targeted tests** + +Run: `npm test -- src/services/deliveryInvitationApi.test.js src/components/client/DeliveryChoiceFlow.test.jsx src/components/client/DeliverySlotsPicker.test.jsx` +Expected: PASS + +- [ ] **Step 2: Run auth/order regression tests** + +Run: `npm test -- src/context/AuthContext.test.js src/components/auth/OtpLoginForm.test.jsx src/services/orderService.test.js src/components/orders/OrdersTable.test.jsx src/components/orders/OrderDetailPanel.test.jsx src/components/orders/OrderFilters.test.jsx` +Expected: PASS diff --git a/docs/superpowers/specs/2026-04-14-product-docs-and-real-client-flow-design.md b/docs/superpowers/specs/2026-04-14-product-docs-and-real-client-flow-design.md new file mode 100644 index 0000000..b3eefd4 --- /dev/null +++ b/docs/superpowers/specs/2026-04-14-product-docs-and-real-client-flow-design.md @@ -0,0 +1,62 @@ +# Product Docs And Real Client Flow Design + +**Goal:** Подготовить один общий документ по приложению с описанием задач, ролей и пользовательских сценариев, а также довести публичную клиентскую страницу до рабочего Supabase flow на реальном invitation token и seed-данных. + +## What We Are Building + +Нужен один основной документ, который можно открыть первым и понять: +- зачем существует приложение; +- какие роли в нём есть; +- что видит каждая роль после входа; +- как проходит согласование доставки с клиентом; +- какие данные должны быть подготовлены в Supabase для показа. + +Параллельно нужно довести клиентскую страницу `/delivery/:token` до реально работающего сценария на базе `delivery_invitations`, `orders` и `delivery_slots`, чтобы можно было показать не showcase-заглушку, а настоящий flow. + +## Product Documentation Shape + +Главный документ должен быть продуктовым, а не набором технических заметок. Рекомендованная структура: + +1. Назначение системы +2. Основные задачи приложения +3. Роли и зоны ответственности +4. Ключевые рабочие сценарии +5. Сценарий клиента по публичной ссылке +6. Какие данные живут в Supabase +7. Как подготовить систему к показу +8. Полезные ссылки на более глубокие технические документы + +Этот документ должен стать основной точкой входа из `README`. + +## Real Client Flow + +Клиентская страница должна работать на реальном token из `delivery_invitations`, без локального fallback как основного сценария. + +Для этого: +- `get-delivery-invitation` должен корректно работать с тем способом вызова, который использует фронтенд; +- ответ invitation должен возвращать все данные, нужные клиентскому экрану: заказ, клиент, state, доступные слоты, выбранные `delivery_date` и `delivery_time`; +- `confirm-delivery-choice` должен фиксировать выбор клиента и обновлять заказ/историю; +- seed должен создавать один рабочий invitation token, который можно открыть в браузере и пройти до подтверждения. + +## Seed Strategy + +Нужен один понятный рабочий пример для показа: +- заказ в статусе `Ожидает ответа клиента`; +- invitation с известным токеном; +- слоты на две даты и две половины дня; +- логист и менеджер уже привязаны; +- после подтверждения выбор уходит в историю и переводит заказ в `Доставка согласована`. + +Вместо текстовых слотов вида `14 апреля, первая половина дня` лучше хранить технически стабильный формат, который фронтенд однозначно разбирает, например `YYYY-MM-DD, До обеда`. + +## UX Decisions + +- В клиентском экране главное действие — выбор даты и половины дня без лишнего текста. +- Тексты должны звучать как рабочий сервис доставки. +- В документе роли описываются через задачи и результат, а не через внутреннюю реализацию интерфейса. + +## Validation + +- Тесты API/клиента фиксируют рабочий invitation flow. +- После seed известный токен должен открываться на `/delivery/:token`. +- Общий документ должен быть связан с `README` как основной обзор проекта. diff --git a/src/pages/ClientDeliveryPage.jsx b/src/pages/ClientDeliveryPage.jsx index 524c250..20820aa 100644 --- a/src/pages/ClientDeliveryPage.jsx +++ b/src/pages/ClientDeliveryPage.jsx @@ -9,7 +9,7 @@ import { fetchDeliveryInvitation, } from "../services/deliveryInvitationApi"; -const groupSlotsFromInvitation = (invitation) => { +export const groupSlotsFromInvitation = (invitation) => { if (!invitation) { return []; } @@ -49,6 +49,15 @@ const groupSlotsFromInvitation = (invitation) => { }); }; +export const buildDeliveryConfirmationPayload = ({ + slot, + invitation, + searchDate, +}) => ({ + deliveryDate: slot?.date || searchDate || invitation?.deliveryDate || undefined, + deliveryTime: slot?.time || invitation?.deliveryTime || undefined, +}); + export const ClientDeliveryPage = () => { const { token } = useParams(); const [searchParams] = useSearchParams(); @@ -103,7 +112,7 @@ export const ClientDeliveryPage = () => { const invitationState = invitation?.state || "awaiting_choice"; const handleConfirmChoice = React.useCallback( - async (deliveryTime) => { + async ({ deliveryDate, deliveryTime }) => { if (!token) { return; } @@ -114,7 +123,7 @@ export const ClientDeliveryPage = () => { await confirmDeliveryChoice({ token, deliveryTime, - deliveryDate: searchParams.get("date") || invitation?.deliveryDate || undefined, + deliveryDate, }); const loadedInvitation = await fetchDeliveryInvitation(token); setInvitation(loadedInvitation); @@ -124,15 +133,21 @@ export const ClientDeliveryPage = () => { setError(confirmError instanceof Error ? confirmError.message : "Не удалось сохранить выбор"); } }, - [searchParams, token, invitation], + [token, invitation], ); const handleSlotSelect = React.useCallback( (slot) => { setSelectedSlotId(slot.id); - handleConfirmChoice(slot.time); + handleConfirmChoice( + buildDeliveryConfirmationPayload({ + slot, + invitation, + searchDate: searchParams.get("date"), + }), + ); }, - [handleConfirmChoice], + [handleConfirmChoice, invitation, searchParams], ); const handleRequestNewLink = React.useCallback(() => { @@ -208,4 +223,4 @@ export const ClientDeliveryPage = () => { ); -}; \ No newline at end of file +}; diff --git a/src/pages/ClientDeliveryPage.test.js b/src/pages/ClientDeliveryPage.test.js new file mode 100644 index 0000000..e3d1e53 --- /dev/null +++ b/src/pages/ClientDeliveryPage.test.js @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; +import { + buildDeliveryConfirmationPayload, + groupSlotsFromInvitation, +} from "./ClientDeliveryPage"; + +describe("ClientDeliveryPage helpers", () => { + it("maps invitation slots in YYYY-MM-DD format to UI slots", () => { + expect( + groupSlotsFromInvitation({ + availableSlots: [ + "2026-04-15, До обеда", + "2026-04-15, После обеда", + ], + }), + ).toEqual([ + { + id: "slot-0-2026-04-15, До обеда", + date: "2026-04-15", + time: "До обеда", + }, + { + id: "slot-1-2026-04-15, После обеда", + date: "2026-04-15", + time: "После обеда", + }, + ]); + }); + + it("builds confirmation payload from the selected slot date and time", () => { + expect( + buildDeliveryConfirmationPayload({ + slot: { + id: "slot-1", + date: "2026-04-16", + time: "После обеда", + }, + invitation: { + deliveryDate: "2026-04-15", + }, + searchDate: "2026-04-14", + }), + ).toEqual({ + deliveryDate: "2026-04-16", + deliveryTime: "После обеда", + }); + }); +}); diff --git a/supabase/functions/get-delivery-invitation/index.ts b/supabase/functions/get-delivery-invitation/index.ts index cee6ef6..82a9836 100644 --- a/supabase/functions/get-delivery-invitation/index.ts +++ b/supabase/functions/get-delivery-invitation/index.ts @@ -15,7 +15,7 @@ Deno.serve(async (request) => { return new Response("ok", { headers: corsHeaders }); } - if (request.method !== "GET") { + if (!["GET", "POST"].includes(request.method)) { return new Response(JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: { @@ -27,7 +27,9 @@ Deno.serve(async (request) => { try { const url = new URL(request.url); - const token = url.searchParams.get("token") || ""; + const token = request.method === "POST" + ? ((await request.json()) as { token?: string })?.token || "" + : url.searchParams.get("token") || ""; if (!token) { return new Response(JSON.stringify({ error: "token is required" }), { status: 400, @@ -96,6 +98,8 @@ Deno.serve(async (request) => { customerName: order.customer?.name || invitation.customer_name || null, customerPhone: order.customer?.phone || invitation.customer_phone || null, availableSlots: invitation.available_slots || [], + deliveryDate: invitation.delivery_date || null, + deliveryTime: invitation.delivery_time || null, orderStatus: order.status, deliveryAgreementStatus: order.delivery_agreement_status, }, diff --git a/supabase/seed/stage-1-demo.sql b/supabase/seed/stage-1-demo.sql index 7ada517..6ea49b5 100644 --- a/supabase/seed/stage-1-demo.sql +++ b/supabase/seed/stage-1-demo.sql @@ -564,12 +564,19 @@ insert into public.delivery_slots ( ) select o.id, - '2026-04-14'::date, - 'Первая половина дня', + slot.delivery_date, + slot.delivery_time, (select id from public.users where email = 'mk7029953@yandex.ru' limit 1), 'pending_confirmation', null from public.orders o +cross join ( + values + ((current_date + 1), 'До обеда'), + ((current_date + 1), 'После обеда'), + ((current_date + 2), 'До обеда'), + ((current_date + 2), 'После обеда') +) as slot(delivery_date, delivery_time) where o.order_number = 'CD-240031'; insert into public.delivery_slots ( @@ -607,22 +614,22 @@ insert into public.delivery_invitations ( ) select o.id, - encode(digest('demo-invite-1001', 'sha256'), 'hex'), + encode(digest('client-flow-1001', 'sha256'), 'hex'), 'awaiting_choice', o.order_number, o.customer ->> 'name', o.customer ->> 'phone', o.customer ->> 'messenger', array[ - '14 апреля, первая половина дня', - '14 апреля, вторая половина дня', - '15 апреля, первая половина дня', - '15 апреля, вторая половина дня' + to_char(current_date + 1, 'YYYY-MM-DD') || ', До обеда', + to_char(current_date + 1, 'YYYY-MM-DD') || ', После обеда', + to_char(current_date + 2, 'YYYY-MM-DD') || ', До обеда', + to_char(current_date + 2, 'YYYY-MM-DD') || ', После обеда' ], - '2026-04-14'::date, - 'Первая половина дня', - '2026-04-13T09:00:00Z', - '2026-04-13T09:10:00Z', + (current_date + 1), + 'До обеда', + timezone('utc', now()), + null, null from public.orders o where o.order_number = 'CD-240031' @@ -657,12 +664,12 @@ select 'seed', 'success', jsonb_build_object( - 'token', 'demo-invite-1001', + 'token', 'client-flow-1001', 'availableSlots', array[ - '14 апреля, первая половина дня', - '14 апреля, вторая половина дня', - '15 апреля, первая половина дня', - '15 апреля, вторая половина дня' + to_char(current_date + 1, 'YYYY-MM-DD') || ', До обеда', + to_char(current_date + 1, 'YYYY-MM-DD') || ', После обеда', + to_char(current_date + 2, 'YYYY-MM-DD') || ', До обеда', + to_char(current_date + 2, 'YYYY-MM-DD') || ', После обеда' ] ), null @@ -729,4 +736,4 @@ select 'telegram', 'Подтвержу позже, вернусь после 16:00.', jsonb_build_object('source', 'seed') -from public.orders o where o.order_number = 'CD-240031'; \ No newline at end of file +from public.orders o where o.order_number = 'CD-240031';