feat(tickets): persistance des filtres tickets entre redémarrages (#29)
Introduit le port UiPreferencesGateway et son adapter uiPreferences, avec le module ticketFilterPersistence qui sauvegarde/restaure les filtres (recherche, statut, sprint, agents) via useTickets, useTicketSearch et useProjectAgents. Couvert par tests unitaires et un test d'intégration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
65
frontend/src/adapters/uiPreferences.ts
Normal file
65
frontend/src/adapters/uiPreferences.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* `localStorage`-backed {@link UiPreferencesGateway} (ticket #29).
|
||||
*
|
||||
* The only place that touches `window.localStorage`; features go through the
|
||||
* port via DI. Everything is best-effort: a missing / private-mode / quota-full
|
||||
* storage, or a corrupt JSON entry, degrades silently to "no preference" rather
|
||||
* than throwing into the UI (persisted UI filters must never break the app).
|
||||
*/
|
||||
|
||||
import type { UiPreferencesGateway } from "@/ports";
|
||||
|
||||
/** Returns the `Storage` if usable in this environment, else `null`. */
|
||||
function storage(): Storage | null {
|
||||
try {
|
||||
return typeof window !== "undefined" ? window.localStorage : null;
|
||||
} catch {
|
||||
// Accessing `localStorage` can throw (sandboxed / disabled cookies).
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalStorageUiPreferencesGateway implements UiPreferencesGateway {
|
||||
read(key: string): unknown {
|
||||
const store = storage();
|
||||
if (!store) return null;
|
||||
let raw: string | null;
|
||||
try {
|
||||
raw = store.getItem(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (raw === null) return null;
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
// Corrupt entry — drop it so we stop tripping over it, return default.
|
||||
try {
|
||||
store.removeItem(key);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
write(key: string, value: unknown): void {
|
||||
const store = storage();
|
||||
if (!store) return;
|
||||
try {
|
||||
store.setItem(key, JSON.stringify(value));
|
||||
} catch {
|
||||
/* quota / serialisation failure — persistence is best-effort */
|
||||
}
|
||||
}
|
||||
|
||||
remove(key: string): void {
|
||||
const store = storage();
|
||||
if (!store) return;
|
||||
try {
|
||||
store.removeItem(key);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user