/** * Paramètres d’affichage qui doivent survivre au passage par le détail d’une * facture BAP. Seules ces clés sont sérialisées afin d’éviter de propager des * paramètres de navigation arbitraires. */ export type BapFilters = { searchQuery: string; statusFilter: string; selectedYear: string; selectedMonth: string; recipientFilter: string; entityFilter: string; ventilationFilter: string; sortField: "invoiceDate" | "createdAt"; sortDir: "asc" | "desc"; }; const BAP_STATUSES = new Set([ "all", "exported", "not_exported", "export_error", "bap_validated", "bap_pending", "to_complete", ]); function asSearchParams(search: string): URLSearchParams { return new URLSearchParams(search.startsWith("?") ? search.slice(1) : search); } function isMonth(value: string | null): value is string { return value !== null && /^(0[1-9]|1[0-2])$/.test(value); } function isYear(value: string | null): value is string { return value === "all" || (value !== null && /^\d{4}$/.test(value)); } function isSafeFilterValue(value: string | null): value is string { return value !== null && value.length > 0 && value.length <= 200; } /** Lit et valide les filtres BAP transmis dans l’URL. */ export function parseBapFilters(search: string, defaultYear: string): BapFilters { const params = asSearchParams(search); const status = params.get("status"); const year = params.get("year"); const month = params.get("month"); const sort = params.get("sort"); const direction = params.get("dir"); return { searchQuery: params.get("q") || "", statusFilter: status && BAP_STATUSES.has(status) ? status : "all", selectedYear: isYear(year) ? year : defaultYear, selectedMonth: month === "all" || isMonth(month) ? month : "all", recipientFilter: isSafeFilterValue(params.get("recipient")) ? params.get("recipient")! : "all", entityFilter: ["all", "santinova", "itinova"].includes(params.get("entity") || "") ? params.get("entity")! : "all", ventilationFilter: isSafeFilterValue(params.get("ventilation")) ? params.get("ventilation")! : "all", sortField: sort === "invoiceDate" ? "invoiceDate" : "createdAt", sortDir: direction === "asc" ? "asc" : "desc", }; } /** Construit une URL de détail qui conserve explicitement le contexte BAP. */ export function buildBapDetailLocation(invoiceId: number, filters: BapFilters): string { const params = new URLSearchParams({ returnTo: "bap", q: filters.searchQuery, status: filters.statusFilter, year: filters.selectedYear, month: filters.selectedMonth, recipient: filters.recipientFilter, entity: filters.entityFilter, ventilation: filters.ventilationFilter, sort: filters.sortField, dir: filters.sortDir, }); return `/invoices/${invoiceId}?${params.toString()}`; } /** Retourne la liste d’origine. Sans contexte BAP, le comportement historique est conservé. */ export function getBapReturnLocation(search: string): string { const params = asSearchParams(search); if (params.get("returnTo") !== "bap") return "/invoices"; const allowed = new URLSearchParams(); for (const key of ["q", "status", "year", "month", "recipient", "entity", "ventilation", "sort", "dir"]) { const value = params.get(key); if (value) allowed.set(key, value); } const query = allowed.toString(); return query ? `/invoices-bap?${query}` : "/invoices-bap"; }