feat(tickets): filtres multi-critères par cases à cocher (backend + frontend)
Passe les filtres de la liste des tickets en sélection multiple (#12). Backend Rust : - IssueListFilter : statuses/priorities en Vec, filter_matches en OR intra-champ et AND inter-champs - TicketListRequestDto en tableaux + from_request (parse/déduplication) - MCP idea_ticket_list aligné sur le nouveau contrat Frontend : - TicketListQuery.statuses/priorities - UI de cases à cocher, toggle/clear des filtres Tests verts : issue_store (7), app-tauri --lib (59), mcp_server (24), issue_usecases (6), sprint_usecases (6), frontend vitest (505), npm run build (exit 0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -20,6 +20,8 @@ import {
|
||||
type Sprint,
|
||||
type Ticket,
|
||||
type TicketList,
|
||||
type TicketPriority,
|
||||
type TicketStatus,
|
||||
} from "@/domain";
|
||||
import type { CreateTicketInput, TicketListQuery } from "@/ports";
|
||||
import { useGateways } from "@/app/di";
|
||||
@ -32,6 +34,16 @@ export interface TicketsViewModel {
|
||||
busy: boolean;
|
||||
query: TicketListQuery;
|
||||
setQuery: (next: TicketListQuery) => void;
|
||||
/**
|
||||
* Toggles a status in the multi-select filter (ticket #12): adds it when
|
||||
* absent, removes it when present. An empty set ⇒ no status constraint (all).
|
||||
* The list re-fetches automatically as the query changes.
|
||||
*/
|
||||
toggleStatus: (status: TicketStatus) => void;
|
||||
/** Toggles a priority in the multi-select filter (ticket #12). */
|
||||
togglePriority: (priority: TicketPriority) => void;
|
||||
/** Clears both facet filters (status + priority), leaving other criteria. */
|
||||
clearFacets: () => void;
|
||||
refresh: () => Promise<void>;
|
||||
create: (input: CreateTicketInput) => Promise<Ticket | null>;
|
||||
/**
|
||||
@ -89,6 +101,35 @@ export function useTickets(projectId: string): TicketsViewModel {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId, ticket, queryKey]);
|
||||
|
||||
// Toggle a value in one facet set, dropping the key entirely when it empties
|
||||
// so the query stays clean ("no constraint"). Setting a fresh object identity
|
||||
// re-fetches via the `queryKey` effect (ticket #12).
|
||||
const toggleStatus = useCallback((status: TicketStatus) => {
|
||||
setQuery((prev) => {
|
||||
const current = prev.statuses ?? [];
|
||||
const next = current.includes(status)
|
||||
? current.filter((s) => s !== status)
|
||||
: [...current, status];
|
||||
const { statuses: _drop, ...rest } = prev;
|
||||
return next.length > 0 ? { ...rest, statuses: next } : rest;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const togglePriority = useCallback((priority: TicketPriority) => {
|
||||
setQuery((prev) => {
|
||||
const current = prev.priorities ?? [];
|
||||
const next = current.includes(priority)
|
||||
? current.filter((p) => p !== priority)
|
||||
: [...current, priority];
|
||||
const { priorities: _drop, ...rest } = prev;
|
||||
return next.length > 0 ? { ...rest, priorities: next } : rest;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearFacets = useCallback(() => {
|
||||
setQuery(({ statuses: _s, priorities: _p, ...rest }) => rest);
|
||||
}, []);
|
||||
|
||||
const refreshSprints = useCallback(async () => {
|
||||
try {
|
||||
setSprints(await ticket.listSprints(projectId));
|
||||
@ -242,6 +283,9 @@ export function useTickets(projectId: string): TicketsViewModel {
|
||||
busy,
|
||||
query,
|
||||
setQuery,
|
||||
toggleStatus,
|
||||
togglePriority,
|
||||
clearFacets,
|
||||
refresh,
|
||||
create,
|
||||
assignSprint,
|
||||
|
||||
Reference in New Issue
Block a user