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:
2026-07-13 07:45:50 +02:00
parent 9ee7290cde
commit 79f06c26d9
13 changed files with 793 additions and 20 deletions

View File

@ -31,6 +31,7 @@ import type { TicketRef } from "@/domain";
import type { TicketListQuery } from "@/ports";
import { Button, Spinner, cn, zIndex } from "@/shared";
import { TicketFacetsBar } from "./TicketFacetsBar";
import { pickerFiltersKey } from "./ticketFilterPersistence";
import { PriorityBadge, StatusBadge, TicketRef as TicketRefBadge } from "./ticketMeta";
import {
useTicketSearch,
@ -55,6 +56,13 @@ export interface TicketPickerProps {
excludeRefs?: TicketRef[];
/** Seed filter/search query. */
initialQuery?: TicketListQuery;
/**
* Whether the picker persists its filters across openings (ticket #29,
* default `true`). `initialQuery` still wins per field, so a caller imposing a
* business filter keeps priority; a caller that must never inherit stored
* preferences can opt out entirely with `false`.
*/
persistFilters?: boolean;
/**
* Selection callback. In `"single"` mode it receives one
* {@link TicketPickerResult}; in `"multi"` it receives the array.
@ -76,6 +84,7 @@ export function TicketPicker({
selectionMode = "single",
excludeRefs,
initialQuery,
persistFilters = true,
onSelect,
onClose,
}: TicketPickerProps) {
@ -88,6 +97,7 @@ export function TicketPicker({
selectionMode={selectionMode}
excludeRefs={excludeRefs}
initialQuery={initialQuery}
persistFilters={persistFilters}
onSelect={onSelect}
onClose={onClose}
/>
@ -106,16 +116,27 @@ function TicketPickerBody({
selectionMode,
excludeRefs,
initialQuery,
persistFilters,
onSelect,
onClose,
}: Required<
Pick<
TicketPickerProps,
"projectId" | "title" | "confirmLabel" | "selectionMode" | "onSelect" | "onClose"
| "projectId"
| "title"
| "confirmLabel"
| "selectionMode"
| "persistFilters"
| "onSelect"
| "onClose"
>
> &
Pick<TicketPickerProps, "excludeRefs" | "initialQuery">) {
const vm = useTicketSearch(projectId, { initialQuery, excludeRefs });
const vm = useTicketSearch(projectId, {
initialQuery,
excludeRefs,
persistenceKey: persistFilters ? pickerFiltersKey(projectId) : undefined,
});
const dialogRef = useRef<HTMLDivElement>(null);
const [selecting, setSelecting] = useState(false);
const [resolveError, setResolveError] = useState<string | null>(null);