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
|
||||
|
||||
Reference in New Issue
Block a user