feat(vue-fiches): fiche= dans l'URL, fiche à fiche, historique push/replace/back (B12-M2/M3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGNSEFZdoKvbdsGyfSnu5S
This commit is contained in:
Jules Neny
2026-10-03 08:57:16 +02:00
co-authored by Claude Opus 5.5
parent 55027ba482
commit fe1043e321
3 changed files with 217 additions and 3 deletions
+71 -1
View File
@@ -121,7 +121,8 @@ export function lireQuery(query: Query, cfg: ConfigVueFiches): EtatFiches {
/**
* Query à écrire pour un état. Les paramètres que la vue fiches ne gère pas
* (`territoire`, `random`, `fiche` de B12…) sont recopiés depuis `base`.
* (`territoire`, `random`…) sont recopiés depuis `base`, `fiche` aussi : un
* filtre changé garde la fiche ouverte (elle peut passer « hors de vos filtres »).
*/
export function ecrireQuery(etat: EtatFiches, cfg: ConfigVueFiches, base: Query = {}): Record<string, string> {
const geres = new Set(['vue', 'q', 'adresse', 'tri', 'groupe', ...cfg.dimensions.map((d) => d.cle)])
@@ -295,3 +296,72 @@ export function regrouper(fiches: MiniFiche[], def: GroupeDef | null, cfg: Confi
export function ordreFiches(groupes: GroupeRendu[]): MiniFiche[] {
return groupes.flatMap((g) => g.fiches)
}
// ── Fiche ouverte (B12) ────────────────────────────────────────────────────
/** `fiche=` de l'URL ; null si absent ou vide. L'id reste une chaîne (Entraide : nombre, Réseaux : `f1-rotor`). */
export function lireFiche(query: Query): string | null {
const v = premiere(query.fiche).trim()
return v || null
}
/** Query avec `fiche=` posé (id) ou retiré (null), tout le reste recopié tel quel. */
export function queryAvecFiche(query: Query, id: string | number | null): Record<string, string> {
const out: Record<string, string> = {}
for (const [k, v] of Object.entries(query)) {
if (k !== 'fiche' && premiere(v) !== '') out[k] = premiere(v)
}
if (id != null && String(id) !== '') out.fiche = String(id)
return out
}
export interface PositionFiche {
/** Rang dans l'ordre courant, de 1 à total. */
rang: number
total: number
precedente: MiniFiche | null
suivante: MiniFiche | null
}
/**
* Place de la fiche dans l'ordre courant (filtres + tri + regroupement).
* null = fiche hors de l'ordre courant (« hors de vos filtres ») : pas de
* précédente ni de suivante. Pas de bouclage aux extrémités.
*/
export function positionFiche(ordre: MiniFiche[], id: string | number | null): PositionFiche | null {
if (id == null) return null
const i = ordre.findIndex((f) => String(f.id) === String(id))
if (i === -1) return null
return {
rang: i + 1,
total: ordre.length,
precedente: i > 0 ? ordre[i - 1] : null,
suivante: i < ordre.length - 1 ? ordre[i + 1] : null,
}
}
/**
* Historique (SPEC §5) : OUVRIR une fiche depuis la liste ajoute une entrée
* (le retour du téléphone la referme) ; passer d'une fiche à l'autre la
* remplace (le retour ne rejoue pas les vingt fiches parcourues).
*/
export function modeOuverture(ficheCourante: string | null): 'push' | 'replace' {
return ficheCourante == null ? 'push' : 'replace'
}
/**
* FERMER : si l'entrée a été ajoutée par cette page ET que la liste derrière
* n'a pas changé depuis (mêmes filtres, tri, vue), on revient en arrière :
* l'historique reste propre. Sinon (lien partagé, rechargement, filtre changé
* fiche ouverte), on retire `fiche=` sur place : un retour arrière aurait
* défait les filtres choisis entre-temps.
*/
export function modeFermeture(ouverteParLaPage: boolean, listeInchangee: boolean): 'back' | 'replace' {
return ouverteParLaPage && listeInchangee ? 'back' : 'replace'
}
/** Empreinte de la liste derrière la fiche : la query sans `fiche=`, clés triées. */
export function empreinteListe(query: Query): string {
const q = queryAvecFiche(query, null)
return Object.keys(q).sort().map((k) => `${k}=${q[k]}`).join('&')
}