fix(admin-categories): key create-draft localStorage recovery on a stable id, not the ephemeral generated one

Bug: startCreate() generated a fresh id via category-${Date.now()}
every call and wrote/read the autosave draft under
admin-category-draft:<that id>. Since the id changes every time
startCreate() runs, a draft saved during one "create category" visit
can never be found by a later visit (even seconds later, same tab) -
draft recovery for new (unsaved) categories was completely dead, and
every abandoned attempt left an orphaned, never-cleaned localStorage
entry.

Fix: create-mode drafts now persist under a fixed key
(admin-category-draft:new) tracked via a new draftStorageKey field,
independent of the draft's own id. Edit-mode drafts are unaffected -
they already keyed on the real, stable category id.

Verified live via window.ng.getComponent() on
/ru/backoffice/categories/create (devBypassAdmin=true):
- Before fix: updateDraft({title}) -> localStorage key
  admin-category-draft:category-<ts1>; calling startCreate() again
  (simulating navigate-away/back) generated category-<ts2> and never
  recovered - draft.title reset to '', dirty=false, old key orphaned.
- After fix: same sequence recovers title/dirty correctly under
  admin-category-draft:new; saveDraft() clears that key as expected.
This commit is contained in:
sdarbinyan
2026-07-17 22:11:53 +04:00
parent a8a5de5392
commit 1916153e5b

View File

@@ -6,6 +6,7 @@ import { AdminCategoriesLocalGateway } from '../services/admin-categories-local.
import { LocalStorageService } from '../../../../core/storage/local-storage.service';
const DRAFT_KEY_PREFIX = 'admin-category-draft:';
const NEW_CATEGORY_DRAFT_KEY = `${DRAFT_KEY_PREFIX}new`;
@Injectable({ providedIn: 'root' })
export class AdminCategoriesFacade {
@@ -21,6 +22,7 @@ export class AdminCategoriesFacade {
readonly dirty = signal(false);
readonly slugTaken = signal(false);
private savedSnapshot: string | null = null;
private draftStorageKey: string | null = null;
readonly rootCategories = computed(() => this.categories().filter(category => !category.parentId));
readonly childrenByParent = computed(() => {
@@ -69,22 +71,23 @@ export class AdminCategoriesFacade {
startCreate(): void {
this.editorMode.set('create');
const empty = this.formFactory.createEmpty();
const recovered = this.localStorage.getJSON<AdminCategory>(`${DRAFT_KEY_PREFIX}${empty.id}`);
this.draft.set(recovered ?? empty);
this.draftStorageKey = NEW_CATEGORY_DRAFT_KEY;
const recovered = this.localStorage.getJSON<AdminCategory>(this.draftStorageKey);
this.draft.set(recovered ?? this.formFactory.createEmpty());
this.savedSnapshot = null;
this.dirty.set(!!recovered);
}
loadForEdit(id: string): void {
this.editorMode.set('edit');
this.draftStorageKey = `${DRAFT_KEY_PREFIX}${id}`;
this.gateway.loadCategory(id).pipe(take(1)).subscribe({
next: category => {
if (!category) {
this.draft.set(null);
return;
}
const recovered = this.localStorage.getJSON<AdminCategory>(`${DRAFT_KEY_PREFIX}${id}`);
const recovered = this.localStorage.getJSON<AdminCategory>(this.draftStorageKey!);
this.draft.set(recovered ?? { ...category });
this.savedSnapshot = JSON.stringify(category);
this.dirty.set(!!recovered && JSON.stringify(recovered) !== this.savedSnapshot);
@@ -96,7 +99,9 @@ export class AdminCategoriesFacade {
this.draft.update(current => {
if (!current) return current;
const updated = { ...current, ...patch, updatedAt: new Date().toISOString() };
this.localStorage.setJSON(`${DRAFT_KEY_PREFIX}${updated.id}`, updated);
if (this.draftStorageKey) {
this.localStorage.setJSON(this.draftStorageKey, updated);
}
this.dirty.set(this.savedSnapshot !== JSON.stringify(updated));
return updated;
});
@@ -124,7 +129,10 @@ export class AdminCategoriesFacade {
request.pipe(take(1)).subscribe({
next: saved => {
this.localStorage.removeItem(`${DRAFT_KEY_PREFIX}${saved.id}`);
if (this.draftStorageKey) {
this.localStorage.removeItem(this.draftStorageKey);
}
this.draftStorageKey = `${DRAFT_KEY_PREFIX}${saved.id}`;
this.savedSnapshot = JSON.stringify(saved);
this.dirty.set(false);
this.loadList();
@@ -133,9 +141,8 @@ export class AdminCategoriesFacade {
}
discardDraftRecovery(): void {
const draft = this.draft();
if (!draft) return;
this.localStorage.removeItem(`${DRAFT_KEY_PREFIX}${draft.id}`);
if (!this.draft() || !this.draftStorageKey) return;
this.localStorage.removeItem(this.draftStorageKey);
}
canDelete(id: string): boolean {