8a7bed487f
AdminRouter wraps existing switchTab for deep-linking. - frontend/js/admin/router.js (new, 102L): parse/navigate/current/on/off, recursion guard via _navigating flag - admin.html: +1 <script> before admin.js - admin.js: switchTab(btn, opts) + initAdminRouter IIFE for hashchange dispatch Backward compat: all 21 onclick=switchTab(this) callsites continue working. F5 / back / forward / deep-link verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
88 lines
3.8 KiB
Markdown
88 lines
3.8 KiB
Markdown
# Feature Context: Admin Panel Redesign
|
|
|
|
## Current State
|
|
|
|
(будет обновляться после каждой фазы)
|
|
|
|
- ✅ Phase 1 implemented — `window.AdminRouter` обёртывает старый `switchTab` (hash ↔ tab двусторонне). `switchTab` принимает 2-й аргумент `{ fromRouter: true }` для предотвращения рекурсии. Default = `#stats`. Файлы: `frontend/js/admin/router.js` (новый), `frontend/admin.html` (+1 строка), `frontend/js/admin/admin.js` (модификация `switchTab` + IIFE `initAdminRouter`).
|
|
- ⬜ Phase 2 not started — все 13 секций в admin.js монолите
|
|
- ⬜ Phase 3-6 not started
|
|
|
|
## Temporary Workarounds
|
|
|
|
(пусто — заполняется implementer'ом)
|
|
|
|
## Cross-Phase Dependencies
|
|
|
|
- **Phase 2 depends on Phase 1:** sections подписываются на router events, чтобы lazy-init по hashchange
|
|
- **Phases 3, 4, 5 depend on Phase 2:** новые модули будут добавляться в `js/admin/sections/` (структура из фазы 2)
|
|
- **Phase 6 depends on Phase 2:** deep page для user/session — это новые sections в той же структуре
|
|
- **Phase 6 removes** старую `.user-panel` overlay из admin.html — фазы 1-5 НЕ должны её удалять
|
|
|
|
## Router Contract (Phase 1)
|
|
|
|
```js
|
|
// Subscribe in any future module:
|
|
AdminRouter.on('change', ({ route, params, raw }) => { /* ... */ });
|
|
|
|
// Programmatic deep-link without polluting history:
|
|
AdminRouter.navigate('#users/123', { replace: true, silent: true });
|
|
```
|
|
|
|
- Events emitted: `'change'` only (payload: parsed route).
|
|
- Late subscribers do NOT receive replay — call `AdminRouter.current()` on init.
|
|
- `silent: true` suppresses the synchronous emit but native `hashchange` still fires;
|
|
the internal `_navigating` flag in router.js prevents the listener from re-firing.
|
|
- `switchTab(btn, { fromRouter: true })` — call from router handlers to skip the
|
|
reverse-sync write to `location.hash` (avoids redundant `replaceState`).
|
|
|
|
## Implementation Notes
|
|
|
|
### Существующая структура (что менять / что НЕ менять)
|
|
|
|
**Точки входа в admin.js:**
|
|
- `LS.initPage()` — auth + role check
|
|
- `switchTab(btn)` — текущий tab-роутер; будет обёрнут router'ом, но не удалён до фазы 6
|
|
- Per-tab `*Inited` флаги (`usersInited`, `sessionsInited`, ...) — переедут в section modules
|
|
|
|
**Backward compat обязателен:**
|
|
- `goAddQuestion(slug)` и подобные cross-tab onclick handlers должны работать
|
|
- Старые ссылки `<a href="#stats">` (если есть) тоже
|
|
|
|
### Конвенции вновь создаваемых модулей
|
|
|
|
Каждая section (фаза 2):
|
|
```js
|
|
// js/admin/sections/<name>.js
|
|
(function () {
|
|
'use strict';
|
|
let inited = false;
|
|
async function load() { /* ... */ }
|
|
window.AdminSections = window.AdminSections || {};
|
|
window.AdminSections.<name> = {
|
|
init: async () => { if (inited) return; inited = true; await load(); },
|
|
reload: load,
|
|
};
|
|
})();
|
|
```
|
|
|
|
Router (фаза 1):
|
|
```js
|
|
// js/admin/router.js
|
|
window.AdminRouter = {
|
|
navigate(hash) { /* update hash + dispatch */ },
|
|
current() { /* parse current hash */ },
|
|
on(event, fn) { /* subscribe */ },
|
|
};
|
|
```
|
|
|
|
### Какие onclick handlers есть сейчас (выборка)
|
|
|
|
Из admin.html / admin.js:
|
|
- `onclick="switchTab(this)"` — на каждой admin-nav-item
|
|
- `onclick="openUserPanel(event, ${u.id}, '${u.role}')"` — на user row
|
|
- `onclick="changeRole(this)"` — на role-select
|
|
- `onclick="goAddQuestion('${slug}')"` — cross-tab
|
|
|
|
Эти должны работать без изменений до фазы 6.
|