From 64af42167b2f40f5f727ccacbe479a53ae980a80 Mon Sep 17 00:00:00 2001 From: nns Date: Sat, 30 May 2026 11:52:00 +0500 Subject: [PATCH] =?UTF-8?q?docs(sprint7):=20=D0=BF=D1=83=D0=BD=D0=BA=D1=82?= =?UTF-8?q?=D1=8B=206-7=20=E2=9C=93=20+=20=D0=B8=D1=82=D0=BE=D0=B3=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Все 7 пунктов закрыты. Stage прошёл smoke-тест 5/5 после последнего деплоя. Co-Authored-By: Claude Opus 4.7 --- docs/sprint7-progress.md | 31 ++++++++++++++- tests/e2e/scripts/screenshot-shortcuts.ts | 46 +++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/scripts/screenshot-shortcuts.ts diff --git a/docs/sprint7-progress.md b/docs/sprint7-progress.md index ec92db5..bb61a35 100644 --- a/docs/sprint7-progress.md +++ b/docs/sprint7-progress.md @@ -17,8 +17,8 @@ - [x] **3. Toast-система ошибок** — собственная `lib/toast.ts` + ``. Axios interceptor: 4xx/5xx → error toast (humanizeError() читает ProblemDetails: errors.X[0] / detail / message / title). 401 — refresh-flow, без toast. Success — глобальный mutation onSuccess (через `meta.successMessage`): useCatalogMutations + 36 мутаций на doc-edit pages. Top-right, autoclose 5s, дедуп, ручное закрытие X. Скриншот: `tests/e2e/reports/toast-error-*.png`. - [x] **4. Loading skeletons** — `Skeleton.tsx` экспортирует ``, ``, ``. DataTable рендерит TableSkeleton при isLoading. 9 doc-edit pages + OrganizationSettingsPage показывают FormSkeleton пока тащат документ. DashboardPage график → Skeleton block. Скриншот: `tests/e2e/reports/skeleton-table-*.png`. - [x] **5. Empty states с CTA** — `EmptyState.tsx` (icon + title + description + action/secondary). Применён к 14 list-страницам (Products/Counterparties/Enters/Losses/Transfers/Inventories/Demands/SupplierReturns/Supplies/RetailSales + 4 отчёта). Показывается только когда нет поиска/фильтров. Скриншот: `tests/e2e/reports/empty-state-products-*.png`. -- [ ] **6. Breadcrumbs** — на edit-страницах Reusable ``. -- [ ] **7. Keyboard shortcuts** — edit: Ctrl+S = save, Esc = cancel/back; list: `/` = focus search, `n` = create. Hint в footer / `?` overlay. +- [x] **6. Breadcrumbs** — Reusable `` (ChevronRight разделитель, последний item — semibold). Применён к 9 edit-страницам (Product + Supply/Enter/Loss/Transfer/Inventory/SupplierReturn/Demand/RetailSale). Дублирующая subtitle убрана. Скриншот: `tests/e2e/reports/breadcrumbs-product-*.png`. +- [x] **7. Keyboard shortcuts** — `useShortcuts(map, enabled?)` хук + `` (открывается клавишей `?`). Поддерживает `mod+s`, одиночные клавиши скипают input/textarea. Edit (9 страниц): mod+s=save (через `canSave`), Esc=back-to-list. Гард `!dialogProps.open` чтобы Esc не воровал у ConfirmDialog. List (10 страниц): `/`=focus search (SearchBar теперь forwardRef), `n`=create new. ShortcutsOverlay смонтирован в AppLayout, `?` всегда доступен. ## Журнал @@ -69,3 +69,30 @@ - Гард: показываем только когда `!search && filters.empty`. На фильтрованном результате — обычный «Нет данных». - Скриншот: `tests/e2e/reports/empty-state-products-1780122048413.png`. - Коммит: `8d53292 feat(web): Empty states с CTA`. + +### 2026-05-30 — пункт 6 ✓ + +- `Breadcrumbs.tsx`: ChevronRight разделитель, последний item — semibold/не кликабельный. +- 9 edit-страниц: Product (Каталог / Товары / ), Supply (Закупки / Приёмки / ), Enter/Loss/Transfer/Inventory (Остатки / ...), SupplierReturn (Закупки / Возвраты поставщикам / ), Demand (Продажи / Оптовые отгрузки / ), RetailSale (Продажи / Чеки / ). +- Дублирующая subtitle «Черновик — товар не списан…» убрана; зелёный бейдж «Проведён <дата>» остаётся. +- Скриншот: `tests/e2e/reports/breadcrumbs-product-*.png`. +- Коммит: `821bc4e feat(web): Breadcrumbs на edit-страницах`. + +### 2026-05-30 — пункт 7 ✓ + +- `useShortcuts(map, enabled?)`: 'mod+s' (Ctrl/Cmd), бэр-клавиши скипают input/textarea/contenteditable, preventDefault автоматический. + - Фикс: бэр-клавиши матчатся только по `e.key`, без проверки модификаторов — это нужно для '?' (вводится через Shift+/). +- 9 edit-pages: mod+s = save (через canSave + !save.isPending), Escape = navigate(list); guard `!dialogProps.open` чтобы не конфликтовать с ConfirmDialog. +- 10 list-pages: '/' = focus search (SearchBar теперь forwardRef), 'n' = create new. +- `` смонтирован в AppLayout — '?' открывает модал со списком всех шорткатов. +- Скриншот overlay: `tests/e2e/reports/shortcuts-overlay-1780123856118.png`. +- Коммиты: `76cbe78 feat(web): keyboard shortcuts`, `c2ebbcc fix(web): useShortcuts — бэр-клавиши`. + +## Итог + +Все 7 пунктов ✓. После последнего деплоя: +- `tests/e2e/run.sh stage-smoke --api-only` → 5/5 ✓ +- `tests/e2e/run.sh stage-demo-seed --api-only` → 5/5 ✓ +- Скриншоты подтверждают визуально: ConfirmDialog, Toaster, Skeleton, EmptyState, Breadcrumbs, ShortcutsOverlay. + +Stage URL: https://test.admin.food-market.kz. Свежий signup → нажать «Заполнить демо-данными» в Настройках орги → все модули наполнены. diff --git a/tests/e2e/scripts/screenshot-shortcuts.ts b/tests/e2e/scripts/screenshot-shortcuts.ts new file mode 100644 index 0000000..309d0a8 --- /dev/null +++ b/tests/e2e/scripts/screenshot-shortcuts.ts @@ -0,0 +1,46 @@ +/** + * Sprint 7 item 7 — визуально проверяем «?» overlay шорткатов. + */ +import { chromium } from 'playwright' +import { makeClient, login } from '../lib/api.js' + +const BASE = process.env.E2E_ADMIN_URL ?? 'https://test.admin.food-market.kz' +const TS = Date.now() +const EMAIL = `shrt-shot-${TS}@food-market.local` +const PASS = 'ShrtShot12345!' + +async function ensureSession() { + const api = makeClient() + const r = await api.post('/api/auth/signup', { + email: EMAIL, password: PASS, + organizationName: `ShrtShot ${TS}`, phone: '+77011190001', plan: 'start', + }) + if (r.status !== 200) throw new Error(`signup ${r.status}: ${JSON.stringify(r.data)}`) + return login(EMAIL, PASS) +} + +async function main() { + const sess = await ensureSession() + const browser = await chromium.launch({ headless: true }) + const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1280, height: 800 } }) + const page = await ctx.newPage() + await page.goto(`${BASE}/`) + await page.evaluate(({ token }) => localStorage.setItem('fm.access_token', token), { token: sess.accessToken }) + + await page.goto(`${BASE}/dashboard`, { waitUntil: 'domcontentloaded' }) + await page.waitForLoadState('networkidle') + // Жмём «?» — должен открыться overlay. + await page.keyboard.press('Shift+Slash') // = `?` без AZERTY + await page.waitForSelector('[aria-labelledby="shortcuts-title"]', { timeout: 5000 }) + await page.screenshot({ path: `reports/shortcuts-overlay-${TS}.png` }) + console.log(`[shot] saved → reports/shortcuts-overlay-${TS}.png`) + + // Esc закрывает + await page.keyboard.press('Escape') + await page.waitForTimeout(300) + const open = await page.locator('[aria-labelledby="shortcuts-title"]').count() + console.log(`[shot] Esc closes overlay: ${open === 0 ? '✓' : '✗ STILL OPEN'}`) + await browser.close() +} + +main().catch(err => { console.error(err); process.exit(1) })