Files
Learn_System/plans/admin-redesign/phase-4-palette.md
T
Maxim Dolgolyov 76e376ee04 chore(plan): admin-redesign 6-phase plan
PLAN.md + 6 subplans + CONTEXT.md

Strategy: Incremental | Mode: Automated | Execution: Orchestrator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:47:55 +03:00

114 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 4: Cmd+K command palette
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
**Parallelizable with:** Phase 3, Phase 5
## Objective
Глобальный палеттный поиск по Ctrl+K (Cmd+K на Mac) — нахоит entities (users, tests, classes, sessions) + actions ("выдать монеты ученику", "разбанить", "создать класс", deep-link routes). Радикально сокращает количество кликов для частых сценариев.
## Tasks
- [ ] Backend: новый endpoint `GET /api/admin/search?q=X&limit=8`:
- Возвращает смешанный результат:
```js
{
users: [{ id, name, email, role }], // top 5 по name LIKE / email LIKE
tests: [{ id, name, subject_slug }], // top 3
classes: [{ id, name, code }], // top 3
sessions: [] // skip пока, добавим если нужно
}
```
- Контроллер: новая функция `globalSearch` в `adminController.js`
- Route: `router.get('/search', requireAdmin, globalSearch)`
- Каждая sub-query SELECT отдельно с LIMIT, общий ответ — простой json
- Auth: admin only (teachers видят только своих учеников; для упрощения — admin)
- [ ] Frontend: `frontend/js/admin/palette.js` — palette модуль:
- Не section, а глобальный widget — подключается в admin.js init
- Слушает `keydown` на `Ctrl+K` / `Cmd+K` (preventDefault)
- Открывает modal через `LS.modal()`:
- Header: search input (autofocus)
- Body: список результатов с keyboard nav (↑↓ Enter Esc)
- Иконка типа справа от каждого результата (User, Test, Class, Action)
- Дебаунс поиска ~150ms
- Min длина query: 2 символа
- При query='' → показать "Recent Actions" hardcoded list
- [ ] Actions index (hardcoded в palette.js):
```js
const ACTIONS = [
{ id: 'award_coins', name: 'Выдать монеты', icon: 'coins', handler: () => AdminRouter.navigate('#shop') },
{ id: 'award_xp', name: 'Выдать XP', icon: 'zap', handler: () => AdminRouter.navigate('#gam') },
{ id: 'new_class', name: 'Создать класс', icon: 'plus-circle', handler: () => window.location.href = '/classes' },
{ id: 'new_test', name: 'Создать тест', icon: 'file-plus', handler: () => AdminRouter.navigate('#tests') },
{ id: 'view_users', name: 'Все пользователи', icon: 'users', handler: () => AdminRouter.navigate('#users') },
{ id: 'view_sessions', name: 'Все сессии', icon: 'history', handler: () => AdminRouter.navigate('#sessions') },
{ id: 'view_audit', name: 'Audit log', icon: 'shield', handler: () => AdminRouter.navigate('#sublog') },
// …добавлять по мере надобности
];
```
- Fuzzy-match в JS (substring match по name) при query
- [ ] Открытие результата:
- User → `AdminRouter.navigate('#users/' + id)` (Phase 6 будет рендерить deep page; пока fallback на `#users` + opening user-panel через имеющийся `openUserPanel`)
- Test → `AdminRouter.navigate('#tests')` + scroll к row (если поддерживается, иначе просто tab)
- Class → `window.location.href = '/classes#' + id`
- Action → выполнить handler
- [ ] Стили palette: глассморфизм/blur, центрировано, max-width 600px, dark/light theme-friendly. Использовать существующие токены `--surface`, `--border`, `--text-2`.
- [ ] Подсказка в UI: footer dialog'а "↑↓ — навигация · ↵ — выбрать · esc — закрыть"
## Files to Modify/Create
- `backend/src/controllers/adminController.js` — добавить `globalSearch` (~60L)
- `backend/src/routes/admin.js` — добавить `/search` route
- `js/api.js` — добавить `LS.adminGlobalSearch(q)` helper (~5L)
- `frontend/js/admin/palette.js` — новый, ~300-400L
- `frontend/admin.html` — добавить `<script src="/js/admin/palette.js"></script>`
## Acceptance Criteria
- Ctrl+K (Cmd+K) открывает palette из любого таба admin
- Esc закрывает
- Печать "иван" → top users с именем "Иван..."
- Печать "монеты" → action "Выдать монеты"
- ↑↓ навигация работает, Enter выполняет
- Поиск отрабатывает <100ms для 8 результатов на тестовой БД
- Click outside / Esc закрывают
- LS.modal используется (не reinventing wheel)
- Auth: только admin может открыть (teachers — палетту не открывают)
## Notes
### Почему Ctrl+K а не /
Ctrl+K — индустри-стандарт (GitHub, Linear, Vercel, Slack). `/` конфликтует с input'ами.
### Дебаунсинг
Простой setTimeout/clearTimeout. Без библиотек.
### LS.modal compat
LS.modal сейчас принимает `{ title, body, footer, onOk, onClose, size }`. Для palette нужен focus management — autofocus input при открытии. Можно использовать через колбэк `onMount` если он есть, либо `setTimeout(() => input.focus(), 0)` после открытия.
### Что НЕ делать в этой фазе
- Не делать ML/fuzzy-search в backend (LIKE достаточно)
- Не делать historic recents (Cmd+K recents) — это уже после merge
- Не делать collaboration ("кто-то ещё печатает")
## Review Checklist
- [ ] Ctrl+K не конфликтует с системными shortcut'ами браузера
- [ ] Palette не открывается если фокус в textarea / input (если требует ввод)... опционально, можно открывать всегда
- [ ] No SQL injection в /admin/search
- [ ] Эскейпинг через LS.esc для рендеринга имён пользователей
- [ ] No N+1 queries (один SELECT на тип сущности)
- [ ] Build passes
## Handoff to Next Phase
<!-- Implementer: записать, какой формат ответа /admin/search,
как palette вызывает navigate (важно для Phase 6 — deep user page будет ловить #users/N),
какие actions zarejestrowano (Phase 6 может добавить ещё). -->