/** * useFicheAdapter — B3-M2 * Adaptateur léger Org | StructureV2 → MiniFiche, format commun consommé par * FicheMiniCard / FichesPanel. Aucune mutation des types sources. * * Repli résumé (brief B3, le champ `resume_court` du batch B2b n'existe pas * encore) : première phrase du premier champ non vide parmi une liste de * candidats ordonnée par type, tronquée proprement à ~110 caractères sur un * mot (jamais au milieu). */ import type { Org } from '~/types/org' import type { StructureV2 } from '~/types/structure-v2' export interface MiniFiche { id: string | number nom: string resume: string chips: string[] echelle?: string geoloc: boolean kind: 'org' | 'structure' } const RESUME_MAX = 110 /** Première phrase d'un texte (coupe sur ". "/"! "/"? " ou fin de chaîne). */ function firstSentence(text: string): string { const match = text.match(/^.*?[.!?](?=\s|$)/) return (match ? match[0] : text).trim() } /** Tronque proprement sur un mot, jamais au milieu. */ function truncateOnWord(text: string, max = RESUME_MAX): string { if (text.length <= max) return text const slice = text.slice(0, max) const lastSpace = slice.lastIndexOf(' ') const cut = lastSpace > max * 0.4 ? slice.slice(0, lastSpace) : slice return `${cut.trimEnd()}…` } function buildResume(candidates: Array): string { const source = candidates.find((c) => !!c && c.trim().length > 0) if (!source) return '' return truncateOnWord(firstSentence(source.trim())) } function isStructure(source: Org | StructureV2): source is StructureV2 { return 'famille_principale' in source } function orgFonctions(org: Org): string[] { return (org.tags_fonction ?? '') .split(',') .map((f) => f.trim()) .filter(Boolean) .slice(0, 3) } export function useFicheAdapter() { function toMini(source: Org | StructureV2): MiniFiche { if (isStructure(source)) { return { id: source.id, nom: source.nom, resume: buildResume([source.description_courte, source.description_longue]), chips: (source.hashtags ?? []).slice(0, 3), geoloc: source.latitude != null && source.longitude != null, kind: 'structure', } } return { id: source.Id, nom: source.nom, resume: buildResume([source.description_enrichie, source.description]), chips: orgFonctions(source), echelle: source.echelle, geoloc: source.latitude != null && source.longitude != null, kind: 'org', } } return { toMini } }