feat(admin): complete category management
Sprint 20. Adds features/admin/categories/ (model, gateway interface + local gateway, facade, list/editor pages), mirroring the admin/products container/facade/service split. - indented hierarchy view + native HTML5 drag-and-drop reorder - visibility toggle, item counter, empty state, include-deleted filter - editor: slug uniqueness validation, translations, SEO fields, breadcrumb preview, image via existing MediaPickerComponent - soft delete/restore, blocked when a category has children or items - draft/publish status + localStorage draft recovery (mirrors Project Editor autosave) + CanDeactivate unsaved-changes guard - wired into app.routes.ts (replaces the categories coming-soon placeholder) - docs/ADMIN.md + docs/BACKEND.md updated with the new gap detail Not yet done: admin/products' category dropdown still reads from its own AdminProductsGateway.loadCategories() rather than this gateway (Sprint 21). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,9 @@ by the existing `adminAuthGuard` (`core/admin-auth/admin-auth.guard.ts`):
|
||||
/:lang/backoffice/products/create -> AdminProductEditorPageComponent
|
||||
/:lang/backoffice/products/:id/edit -> AdminProductEditorPageComponent
|
||||
/:lang/backoffice/products/:id/duplicate -> AdminProductEditorPageComponent
|
||||
/:lang/backoffice/categories -> BackofficeComingSoonPageComponent
|
||||
/:lang/backoffice/categories -> AdminCategoriesListPageComponent
|
||||
/:lang/backoffice/categories/create -> AdminCategoryEditorPageComponent
|
||||
/:lang/backoffice/categories/:id/edit -> AdminCategoryEditorPageComponent
|
||||
/:lang/backoffice/static-pages -> BackofficeComingSoonPageComponent
|
||||
/:lang/backoffice/transactions -> BackofficeComingSoonPageComponent
|
||||
/:lang/backoffice/orders -> BackofficeComingSoonPageComponent
|
||||
@@ -145,6 +147,56 @@ save" and "last publish" were indistinguishable after a publish. Added
|
||||
`ProjectEditorFacade`, set only inside `publish()`. `lastSavedAt` behavior is
|
||||
unchanged (still updated by both `save()` and `publish()`).
|
||||
|
||||
## Sprint 20 - Category Management
|
||||
|
||||
`features/admin/categories/` (model/gateway/facade/pages/components), same
|
||||
container/facade/service split as `admin/products` and `admin/dashboard`:
|
||||
|
||||
```text
|
||||
src/app/features/admin/categories/
|
||||
models/ admin-category.model.ts
|
||||
services/ admin-categories-gateway.interface.ts
|
||||
admin-categories-local.gateway.ts
|
||||
admin-categories-form.factory.ts
|
||||
facade/ admin-categories.facade.ts
|
||||
guards/ admin-category-dirty.guard.ts
|
||||
components/ admin-categories-list.component.*
|
||||
admin-category-form.component.*
|
||||
pages/ admin-categories-list-page.component.ts
|
||||
admin-category-editor-page.component.ts
|
||||
```
|
||||
|
||||
- **Hierarchy**: `AdminCategory.parentId` (nullable). List page renders a
|
||||
flattened, indented tree (`AdminCategoriesFacade.rootCategories()` /
|
||||
`childrenOf(id)`); the editor's parent `<select>` excludes the category
|
||||
itself and its descendants to prevent cycles.
|
||||
- **Reordering**: native HTML5 drag-and-drop in
|
||||
`admin-categories-list.component.ts` (`draggable`, `dragstart`/`drop`),
|
||||
persists via `AdminCategoriesFacade.reorder()` which just rewrites `order`.
|
||||
- **Delete/restore**: soft delete (`deletedAt` timestamp). Blocked
|
||||
client-side (`facade.canDelete()`) if the category has children or
|
||||
`itemsCount > 0`; list has an "include deleted" filter with a Restore
|
||||
action for soft-deleted rows.
|
||||
- **Draft/publish**: `status: 'draft' | 'published'`, set by the editor's
|
||||
"Save Draft" vs "Publish" buttons (`AdminCategoriesFacade.saveDraft(publish)`).
|
||||
- **Local draft recovery + unsaved-changes guard**: every `updateDraft()`
|
||||
call persists the in-progress category to `localStorage` under
|
||||
`admin-category-draft:<id>` (via the existing `LocalStorageService`,
|
||||
same pattern as Project Editor autosave); the editor reloads that draft
|
||||
ahead of the saved value if present, and is cleared on save.
|
||||
`adminCategoryDirtyGuard` (mirrors `projectEditorDirtyGuard`) blocks
|
||||
navigation away from an unsaved edit with `window.confirm`.
|
||||
- **Image**: reuses the existing `MediaPickerComponent` (same one used by
|
||||
Media Manager) rather than a free-text URL field.
|
||||
- **Seed data**: `AdminCategoriesLocalGateway` seeds its in-memory cache from
|
||||
`BackofficeDataService.loadCategories()` (`CategoryCardConfig`, currently
|
||||
flat/no hierarchy) - same swappable-provider pattern as
|
||||
`AdminProductsLocalGateway`.
|
||||
- **Not yet wired**: `admin/products`' category `<select>` still uses
|
||||
`AdminProductsGateway.loadCategories()` (its own `AdminProductCategoryOption`
|
||||
seed), not `AdminCategoriesGateway` - unifying them is Sprint 21 scope
|
||||
(`docs/SPRINT-PLAN.md`).
|
||||
|
||||
## Known gaps / backend needs
|
||||
|
||||
- **Dashboard metrics endpoint.** Categories/Products counts are computed
|
||||
|
||||
@@ -75,13 +75,13 @@ Backend must also support content moderation/validation on publish (disallow dan
|
||||
|
||||
## 5. Categories
|
||||
|
||||
**Current behavior:** `GET /category` (existing) mapped through `CategoryDto -> CategoryMapper -> Category` domain model, exposed via `CategoryFacade`. No editor/CRUD surface — `admin/categories` is a routing placeholder (`BackofficeComingSoonPageComponent`).
|
||||
**Current behavior (Sprint 20):** `GET /category` (existing) still backs the public storefront via `CategoryDto -> CategoryMapper -> Category -> CategoryFacade`, unchanged. A full admin editor now exists at `features/admin/categories/` (list + create/edit, hierarchy, drag-and-drop reorder, soft delete/restore, draft/publish, SEO/translations — see `docs/ADMIN.md` "Sprint 20") but it runs entirely against `AdminCategoriesLocalGateway`, an in-memory cache seeded once from `BackofficeDataService.loadCategories()` (`CategoryCardConfig`, no hierarchy) — nothing persists across a page reload.
|
||||
|
||||
**Gap:** no admin write path (create/update/delete/reorder categories) exists anywhere in this codebase.
|
||||
**Gap:** no admin write path (create/update/delete/reorder categories) exists on the backend. `AdminCategory` also carries fields the current `CategoryDto`/`CategoryCardConfig` don't have yet: `parentId` (hierarchy), `slug`, `icon`, `imageUrl`, `status` (draft/published), `deletedAt` (soft delete), `seo`, per-locale `translations`.
|
||||
|
||||
**Needed:** category CRUD endpoints and, ideally, a bulk reorder/visibility endpoint (category `priority`/`visible` are already read fields — see `docs/BOOTSTRAP.md`/domain report history).
|
||||
**Needed:** category CRUD endpoints matching the `AdminCategory` shape (`src/app/features/admin/categories/models/admin-category.model.ts`) plus a bulk reorder endpoint (`order` field) and a slug-uniqueness check (`GET /admin/categories/slug-taken?slug=...`, mirrors `AdminCategoriesGateway.isSlugTaken`).
|
||||
|
||||
**Frontend files:** would need a new `features/admin/categories/` module mirroring `features/admin/products/` (gateway interface + local/API gateway + facade + pages), plus wiring `/:lang/backoffice/categories` off the current coming-soon placeholder in `app.routes.ts`.
|
||||
**Frontend files:** implement `AdminCategoriesApiGateway` against `AdminCategoriesGateway` (`services/admin-categories-gateway.interface.ts`) and rebind via an injection token (same swap pattern as `AdminDashboardMetricsGateway`/`BACKOFFICE_DATA_PROVIDER`) — the facade and pages don't change. Also still open: wire `admin/products`' category dropdown to `AdminCategoriesGateway` instead of its own `AdminProductsGateway.loadCategories()` (Sprint 21).
|
||||
|
||||
## 6. Products
|
||||
|
||||
|
||||
82
docs/SPRINT-PLAN.md
Normal file
82
docs/SPRINT-PLAN.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Sprint Plan (working doc, delete after release)
|
||||
|
||||
Repo already past old "Sprint 19" (dashboard). Renumbering user's Sprint7+ roadmap to continue actual sequence: **Sprint 20 = old "Sprint 7" (Categories)** ... **Sprint 30 = old "Sprint 17" (Final Release)**.
|
||||
|
||||
Pattern to follow (mirrors `admin/products`): `models/`, `services/<x>-gateway.interface.ts` + `<x>-local.gateway.ts` (IndexedDB via existing local-storage service), `facade/`, `pages/`, `components/`. Local-only until backend endpoint exists — log gap in `docs/BACKEND.md`.
|
||||
|
||||
Autonomy: no stop-and-ask except missing API contract / business decision / secrets / external config / legal / multi-valid-design-fork. Commit after each sprint, conventional commits, no push. Quality gate before marking sprint done: build, typecheck, lint (if configured), responsive check, a11y pass, docs updated, no dead code, arch:check green.
|
||||
|
||||
Notify user: **from Sprint 20 (Categories) once product↔category link + admin categories CRUD exist, catalog can start receiving real items** (currently admin-products already has categoryId field but no category source of truth — that's the point where "adding items to catalog" becomes real, not mocked dropdown).
|
||||
|
||||
---
|
||||
|
||||
## Sprint 20 — Category Management ✅ done
|
||||
- [x] AdminCategory model (id, parentId, title, translations, slug, icon, image, seo, visible, order, draft/published status, soft-delete)
|
||||
- [x] AdminCategoriesGateway interface + local (in-memory, seeded from BackofficeDataService) gateway, mirrors products gateway
|
||||
- [x] AdminCategoriesFacade (signals, CRUD, tree ops, slug validation, draft recovery)
|
||||
- [x] Categories list page: indented tree, native HTML5 DnD reorder, visibility toggle, item counter, empty state, include-deleted filter + restore
|
||||
- [x] Category editor: name/slug (+ uniqueness validation), translations editor, icon field, image via MediaPickerComponent, SEO fields, breadcrumb preview
|
||||
- [x] Delete validation (blocked if has children or itemsCount>0) + soft-delete/restore
|
||||
- [x] Draft/publish workflow + localStorage draft recovery (mirrors Project Editor autosave) + unsaved-changes CanDeactivate guard (mirrors projectEditorDirtyGuard)
|
||||
- [x] Wired into admin routing (`app.routes.ts`, replaced coming-soon placeholder)
|
||||
- [x] Updated `docs/ADMIN.md` (new Sprint 20 section), `docs/BACKEND.md` (categories gap rewritten with real field list)
|
||||
- [~] Responsive/a11y: reuses existing FormField/Input/Table/Button/EmptyState a11y wiring; live-browser click-through blocked by safety classifier on the guarded admin route (see below) — verified via tsc/build/arch:check only
|
||||
- Commit: `feat(admin): complete category management`
|
||||
- Note: `admin/products`' category dropdown still uses its own `AdminProductsGateway.loadCategories()`, not this new gateway — unification deferred to Sprint 21 (documented in BACKEND.md).
|
||||
|
||||
## Sprint 21 — Product Management completion
|
||||
- [ ] Audit gaps vs list: duplicate/archive already exist? verify; add missing (archive state, restore draft, related products, variant editor, price/currency/discount editor, inventory/SKU/barcode fields already present—confirm)
|
||||
- [ ] Wire products to real AdminCategoriesGateway (replace ad-hoc category options)
|
||||
- [ ] Gallery via MediaPickerComponent, translation editor, preview, infinite-scroll option on list
|
||||
- [ ] Update `docs/ADMIN.md`, `docs/BACKEND.md`
|
||||
- Commit: `feat(admin): complete product management`
|
||||
|
||||
## Sprint 22 — Media System hardening
|
||||
- [ ] Folder support, tags, search in Media Manager
|
||||
- [ ] Crop/resize/compression on upload, SVG + file-type validation
|
||||
- [ ] Confirm reuse across category/product/logo/hero/static-page selectors
|
||||
- [ ] Storage abstraction doc note (swap IndexedDB mock -> CDN later)
|
||||
- Commit: `feat(media): reusable media management`
|
||||
|
||||
## Sprint 23 — Orders (mock/local, flag backend gap)
|
||||
- [ ] Orders model + local gateway (seed mock data)
|
||||
- [ ] List: filters, search, statuses, timeline, export
|
||||
- [ ] Detail: customer/payment/shipping info, notes/internal notes, refund request, cancel, print invoice
|
||||
- Commit: `feat(admin): order management`
|
||||
|
||||
## Sprint 24 — Transactions (mock/local)
|
||||
- [ ] Payments/refunds/QR list, status, history, export, filters, search, retry, fraud flags, audit log view
|
||||
- Commit: `feat(admin): transaction management`
|
||||
|
||||
## Sprint 25 — Users & Roles
|
||||
- [ ] Users/roles/permissions models (local), marketplace vs office admin distinction
|
||||
- [ ] Invitations, passwordless login (reuse existing Telegram QR pattern), session/device manager, audit trail
|
||||
- Commit: `feat(admin): users and permissions`
|
||||
|
||||
## Sprint 26 — Monitoring
|
||||
- [ ] Audit/security/login logs views (local), API/error/warning feed, queue/webhook placeholders, health page
|
||||
- Commit: `feat(admin): monitoring center`
|
||||
|
||||
## Sprint 27 — Analytics
|
||||
- [ ] Dashboard extensions: sales/revenue/orders/visitors/products/categories charts, date ranges, export
|
||||
- Commit: `feat(admin): analytics dashboard`
|
||||
|
||||
## Sprint 28 — Marketplace Polish
|
||||
- [ ] Lighthouse + a11y sweep, animations, skeletons/empty/error states, responsive fixes, SEO/meta/social preview/robots/sitemap
|
||||
- Commit: `refactor: marketplace release polish`
|
||||
|
||||
## Sprint 29 — Release Candidate
|
||||
- [ ] Dead code cleanup, remove TODOs/console logs/debug code, optimize imports/bundle/images/CSS, translation validation, build/type/lint validation, doc validation, arch review, CHANGELOG + release notes
|
||||
- Commit: `chore: release candidate`
|
||||
|
||||
## Sprint 30 — Final Release
|
||||
- [ ] Full verify pass (build, routing, responsive, translations, admin, marketplace, editor, media, login, bootstrap, APIs, security assumptions), final report
|
||||
- Then (only after explicit go-ahead, push is a confirm-required action): `git push`
|
||||
|
||||
---
|
||||
### Stop conditions (ask user)
|
||||
- Real backend API contract needed (orders/transactions/users are local-mock by design per above — flagged, not blocking)
|
||||
- Ambiguous business rule (e.g. category deletion policy specifics) not inferable from existing product-deletion pattern
|
||||
- Credentials/external service config
|
||||
- Legal/product-only decision
|
||||
- Two equally valid architectures for same feature
|
||||
Reference in New Issue
Block a user