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:
@ -11,7 +11,7 @@
|
||||
* or out of a sprint. Sprint lifecycle events refresh the sprint list.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
isSprintEvent,
|
||||
@ -25,6 +25,10 @@ import {
|
||||
} from "@/domain";
|
||||
import type { CreateTicketInput, TicketListQuery } from "@/ports";
|
||||
import { useGateways } from "@/app/di";
|
||||
import {
|
||||
hydrateListFilters,
|
||||
persistListFilters,
|
||||
} from "./ticketFilterPersistence";
|
||||
|
||||
export interface TicketsViewModel {
|
||||
list: TicketList | null;
|
||||
@ -76,12 +80,33 @@ function describe(e: unknown): string {
|
||||
}
|
||||
|
||||
export function useTickets(projectId: string): TicketsViewModel {
|
||||
const { ticket, system } = useGateways();
|
||||
const { ticket, system, uiPreferences } = useGateways();
|
||||
const [list, setList] = useState<TicketList | null>(null);
|
||||
const [sprints, setSprints] = useState<Sprint[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [query, setQuery] = useState<TicketListQuery>({});
|
||||
// Hydrate the filters persisted for this project (ticket #29) on first render
|
||||
// so reopening the window restores them without a flash of defaults. The
|
||||
// assignee, if any, is validated for shape here and reconciled against the
|
||||
// live agent roster by the panel (an obsolete assignee is then cleared).
|
||||
const [query, setQuery] = useState<TicketListQuery>(() =>
|
||||
hydrateListFilters(uiPreferences, projectId),
|
||||
);
|
||||
|
||||
// Re-hydrate when the hosting project changes (the mounted hook is normally
|
||||
// pinned to one project, but stay correct if `projectId` is swapped in place).
|
||||
const hydratedProject = useRef(projectId);
|
||||
useEffect(() => {
|
||||
if (hydratedProject.current === projectId) return;
|
||||
hydratedProject.current = projectId;
|
||||
setQuery(hydrateListFilters(uiPreferences, projectId));
|
||||
}, [projectId, uiPreferences]);
|
||||
|
||||
// Persist the filters (minus the opaque cursor) whenever they change, scoped
|
||||
// to this project's list key.
|
||||
useEffect(() => {
|
||||
persistListFilters(uiPreferences, projectId, query);
|
||||
}, [uiPreferences, projectId, query]);
|
||||
|
||||
// Stable dependency key so `refresh` only changes when a filter actually does.
|
||||
const queryKey = useMemo(() => JSON.stringify(query), [query]);
|
||||
|
||||
Reference in New Issue
Block a user