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