From 5fd850719520f7afd54a08388084993e22fa2c44 Mon Sep 17 00:00:00 2001 From: Fanny Cheung Date: Tue, 5 Nov 2024 11:25:28 +0100 Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20de=20la=20recherche=20par?= =?UTF-8?q?=20texte=20sur=20le=20tableau=20de=20suivi=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 19 ++ package.json | 3 + scripts/commun/manipulationStrings.js | 12 +- .../components/BarreRecherche.svelte | 51 +++++ .../components/FiltreParmiOptions.svelte | 1 + .../front-end/components/FiltreTexte.svelte | 62 ------ .../screens/SuiviInstruction.svelte | 187 +++++++----------- scripts/front-end/rechercherDansDossier.js | 90 +++++++++ scripts/server/database/dossier.js | 4 +- scripts/types.js | 6 +- scripts/types/customTypes.d.ts | 2 + 11 files changed, 251 insertions(+), 186 deletions(-) create mode 100644 scripts/front-end/components/BarreRecherche.svelte delete mode 100644 scripts/front-end/components/FiltreTexte.svelte create mode 100644 scripts/front-end/rechercherDansDossier.js diff --git a/package-lock.json b/package-lock.json index 99a4662a..c13610c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "fastify": "^4.26.2", "knex": "^3.1.0", "ky": "^1.7.2", + "lunr": "^2.3.9", + "lunr-languages": "^1.14.0", "minimist": "^1.2.8", "ods-xlsx": "github:DavidBruant/ods-xlsx#v0.8.0", "page": "^1.11.6", @@ -31,6 +33,7 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "@types/d3-fetch": "^3.0.7", + "@types/lunr": "^2.3.7", "@types/minimist": "^1.2.5", "@types/node": "^22.3.0", "@types/page": "^1.11.9", @@ -565,6 +568,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lunr": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.7.tgz", + "integrity": "sha512-Tb/kUm38e8gmjahQzdCKhbdsvQ9/ppzHFfsJ0dMs3ckqQsRj+P5IkSAwFTBrBxdyr3E/LoMUUrZngjDYAjiE3A==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -3216,6 +3225,16 @@ "es5-ext": "~0.10.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, + "node_modules/lunr-languages": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz", + "integrity": "sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==" + }, "node_modules/magic-string": { "version": "0.30.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", diff --git a/package.json b/package.json index 8d97442c..fa647621 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "@types/d3-fetch": "^3.0.7", + "@types/lunr": "^2.3.7", "@types/minimist": "^1.2.5", "@types/node": "^22.3.0", "@types/page": "^1.11.9", @@ -54,6 +55,8 @@ "fastify": "^4.26.2", "knex": "^3.1.0", "ky": "^1.7.2", + "lunr": "^2.3.9", + "lunr-languages": "^1.14.0", "minimist": "^1.2.8", "ods-xlsx": "github:DavidBruant/ods-xlsx#v0.8.0", "page": "^1.11.6", diff --git a/scripts/commun/manipulationStrings.js b/scripts/commun/manipulationStrings.js index dc9d9154..5935e588 100644 --- a/scripts/commun/manipulationStrings.js +++ b/scripts/commun/manipulationStrings.js @@ -38,4 +38,14 @@ export function normalizeTexteEspèce(texte){ .replace(/[\u0300-\u036f]/g, '') // remove accents .replaceAll("’", "'") .toLowerCase() -} \ No newline at end of file +} + +/** + * + * @param {string} texte + * @returns {string} + */ +export function retirerAccents(texte) { + return texte.normalize("NFD").replace(/[\u0300-\u036f]/g, "") +} + \ No newline at end of file diff --git a/scripts/front-end/components/BarreRecherche.svelte b/scripts/front-end/components/BarreRecherche.svelte new file mode 100644 index 00000000..09d5fc5a --- /dev/null +++ b/scripts/front-end/components/BarreRecherche.svelte @@ -0,0 +1,51 @@ + + +
+
+ + + +
+ +

Vous pouvez rechercher par départements, communes, nom de projet, porteur de projet, activité principale, numéro de dossier, numéro Onagre.

+
+ + diff --git a/scripts/front-end/components/FiltreParmiOptions.svelte b/scripts/front-end/components/FiltreParmiOptions.svelte index fccdec95..8f677947 100644 --- a/scripts/front-end/components/FiltreParmiOptions.svelte +++ b/scripts/front-end/components/FiltreParmiOptions.svelte @@ -79,6 +79,7 @@ diff --git a/scripts/front-end/components/screens/SuiviInstruction.svelte b/scripts/front-end/components/screens/SuiviInstruction.svelte index 4e119e08..b6239ff3 100644 --- a/scripts/front-end/components/screens/SuiviInstruction.svelte +++ b/scripts/front-end/components/screens/SuiviInstruction.svelte @@ -2,8 +2,10 @@ //@ts-check import Squelette from '../Squelette.svelte' import FiltreParmiOptions from '../FiltreParmiOptions.svelte' - import FiltreTexte from '../FiltreTexte.svelte' + import BarreRecherche from '../BarreRecherche.svelte' import {formatLocalisation, formatDéposant, phases, prochaineActionAttenduePar} from '../../affichageDossier.js' + import {trouverDossiersIdCorrespondantsÀTexte} from '../../rechercherDansDossier.js' + import {retirerAccents} from '../../../commun/manipulationStrings.js' /** @import {DossierComplet, DossierPhase, DossierProchaineActionAttenduePar} from '../../../types.js' */ /** @import {PitchouState} from '../../store.js' */ @@ -105,87 +107,45 @@ filtrerDossiers() } - $: communeFiltrée = "" - - /** - * @param {{detail: string}} _ - */ - function filtrerParCommune({detail: communeSélectionnée}){ - tousLesFiltres.set('commune', dossier => { - if (!dossier.communes) return false - - return !!dossier.communes.find(({name, postalCode}) => { - return name.includes(communeSélectionnée) || postalCode.includes(communeSélectionnée) - }) - }) - - communeFiltrée = communeSélectionnée - - filtrerDossiers() - } - - /** - * - * @param {Event} e - */ - function onSupprimerFiltreCommune(e) { - e.preventDefault() - - tousLesFiltres.delete('commune') - communeFiltrée = "" - filtrerDossiers() - } - - $: départementFiltré = "" - - /** - * @param {{detail: string}} _ - */ - function filtrerParDépartement({detail: départementSélectionné}){ - tousLesFiltres.set('département', dossier => { - if (!dossier.départements) return false - - return !!dossier.départements.find((département) => { - return département.includes(départementSélectionné) - }) - }) - - départementFiltré = départementSélectionné - - filtrerDossiers() - } - - /** - * - * @param {Event} e - */ - function onSupprimerFiltreDépartement(e) { - e.preventDefault() - - tousLesFiltres.delete('département') - départementFiltré = "" - filtrerDossiers() - } - $: texteÀChercher = '' /** * @param {{detail: string}} _ */ function filtrerParTexte({detail: _texteÀChercher}){ - tousLesFiltres.set('texte', dossier => { - return Boolean( - dossier.commentaire_enjeu && dossier.commentaire_enjeu.includes(_texteÀChercher) || - dossier.commentaire_libre && dossier.commentaire_libre.includes(_texteÀChercher) || - dossier.demandeur_personne_morale_raison_sociale && dossier.demandeur_personne_morale_raison_sociale.includes(_texteÀChercher) || - dossier.demandeur_personne_physique_nom && dossier.demandeur_personne_physique_nom.includes(_texteÀChercher) || - dossier.demandeur_personne_physique_prénoms && dossier.demandeur_personne_physique_prénoms.includes(_texteÀChercher) || - dossier.number_demarches_simplifiées && dossier.number_demarches_simplifiées.includes(_texteÀChercher) || - String(dossier.id || '').includes(_texteÀChercher) || - dossier.nom && dossier.nom.includes(_texteÀChercher) || - dossier.nom_dossier && dossier.nom_dossier.includes(_texteÀChercher) - ) - }) + // cf. https://github.com/MihaiValentin/lunr-languages/issues/66 + // lunr.fr n'indexe pas les chiffres. On gère donc la recherche sur + // les nombres avec une fonction séparée. + if (_texteÀChercher.match(/\d[\dA-Za-z\-]*/)) { + tousLesFiltres.set('texte', dossier => { + const { + id, + départements, + communes, + number_demarches_simplifiées, + historique_identifiant_demande_onagre, + } = dossier + const communesCodes = communes?.map(({postalCode}) => postalCode).filter(c => c) || [] + + return String(id) === _texteÀChercher || + départements?.includes(_texteÀChercher) || + communesCodes?.includes(_texteÀChercher) || + number_demarches_simplifiées === _texteÀChercher || + historique_identifiant_demande_onagre === _texteÀChercher + }) + } else { + const texteSansAccents = retirerAccents(_texteÀChercher) + // Pour chercher les communes qui contiennent des tirets avec lunr, + // on a besoin de passer la chaîne de caractères entre "". + const aRechercher = texteSansAccents.match(/(\w-)+/) ? + `"${texteSansAccents}"` : + texteSansAccents + const dossiersIdCorrespondantsÀTexte = trouverDossiersIdCorrespondantsÀTexte(aRechercher, dossiers) + + tousLesFiltres.set('texte', dossier => { + return dossiersIdCorrespondantsÀTexte.has(dossier.id) + }) + } texteÀChercher = _texteÀChercher; @@ -263,15 +223,13 @@

Suivi instruction DDEP

+ + +
- - - {#if instructeursOptions && instructeursOptions.size >= 2} {/if} {#if dossiersIdSuivisParInstructeurActuel && dossiersIdSuivisParInstructeurActuel.size >= 1} -
+
{/if} - -
- {#if communeFiltrée} -
- Commune : {communeFiltrée} - -
- {/if} - {#if départementFiltré} -
- Département : {départementFiltré} - -
- {/if} - {#if phasesFiltrées.size >= 1} - Phases : {[...phasesFiltrées].join(", ")} - {/if} - {#if prochainesActionsAttenduesParFiltrées.size >= 1} - Prochaine action attendue par : {[...prochainesActionsAttenduesParFiltrées].join(", ")} - {/if} - {#if texteÀChercher} - Texte cherché : {texteÀChercher} - - {/if} - {#if instructeursSélectionnés.size >= 1} - Instructeurs : {[...instructeursSélectionnés].join(", ")} - {/if} -
+ +
+ {#if phasesFiltrées.size >= 1} + Phases : {[...phasesFiltrées].join(", ")} + {/if} + {#if prochainesActionsAttenduesParFiltrées.size >= 1} + Prochaine action attendue par : {[...prochainesActionsAttenduesParFiltrées].join(", ")} + {/if} + {#if texteÀChercher} + Texte cherché : {texteÀChercher} + + {/if} + {#if instructeursSélectionnés.size >= 1} + Instructeurs : {[...instructeursSélectionnés].join(", ")} + {/if} +
-

{dossiersSelectionnés.length} dossiers affichés

+

{dossiersSelectionnés.length} dossiers affichés

@@ -403,4 +345,13 @@ white-space: nowrap; } + .filtres { + display: flex; + align-items: center; + margin-bottom: 0.5rem; + } + + .filtres-actifs { + margin-bottom: 0.5rem; + } diff --git a/scripts/front-end/rechercherDansDossier.js b/scripts/front-end/rechercherDansDossier.js new file mode 100644 index 00000000..14360f70 --- /dev/null +++ b/scripts/front-end/rechercherDansDossier.js @@ -0,0 +1,90 @@ +import lunr from "lunr" +import stemmerSupport from "lunr-languages/lunr.stemmer.support" +import lunrfr from "lunr-languages/lunr.fr" + +import { retirerAccents } from "../commun/manipulationStrings.js" + +/** @import {DossierComplet, StringValues} from "../types.js" */ + +stemmerSupport(lunr) +lunrfr(lunr) + +/** + * @param {DossierComplet} dossier + * @returns {StringValues>} + */ +const créerDossierIndexable = dossier => { + const { + id, + nom_dossier, + number_demarches_simplifiées, + communes, + nom, + déposant_nom, + déposant_prénoms, + demandeur_personne_physique_prénoms, + demandeur_personne_physique_nom, + demandeur_personne_morale_raison_sociale, + activité_principale, + } = dossier + + return { + id: id.toString(), + number_demarches_simplifiées: number_demarches_simplifiées?.toString(), + nom_dossier: retirerAccents(nom_dossier), + communes: communes?.map(({name}) => retirerAccents(name || "")).join(" ") || "", + nom: retirerAccents(nom || ""), + déposant_nom: retirerAccents(déposant_nom || ""), + déposant_prénoms: retirerAccents(déposant_prénoms || ""), + demandeur_personne_physique_prénoms: + retirerAccents(demandeur_personne_physique_prénoms || ""), + demandeur_personne_physique_nom: + retirerAccents(demandeur_personne_physique_nom || ""), + demandeur_personne_morale_raison_sociale: + retirerAccents(demandeur_personne_morale_raison_sociale || ""), + activité_principale: + retirerAccents(activité_principale || ""), + } +} + +/** + * + * @param {DossierComplet[]} dossiers + * @returns {lunr.Index} + */ +export const créerIndexDossiers = dossiers => { + return lunr(function() { + // @ts-expect-error TS ne comprends pas qu'on a ajouté lunrfr + this.use(lunr.fr) + + this.ref("id") + this.field("nom_dossier") + this.field("number_demarches_simplifiées") + this.field("communes") + this.field("nom") + this.field("déposant_nom") + this.field("déposant_prénoms") + this.field("demandeur_personne_physique_prénoms") + this.field("demandeur_personne_physique_nom") + this.field("demandeur_personne_morale_raison_sociale") + this.field("activité_principale") + + for (const dossier of dossiers) { + this.add(créerDossierIndexable(dossier)) + } + }) +} + +/** + * + * @param {string} texteÀChercher + * @param {DossierComplet[]} dossiers + * @returns {Set} + */ +export const trouverDossiersIdCorrespondantsÀTexte = (texteÀChercher, dossiers) => { + const index = créerIndexDossiers(dossiers) + const lunrRésultats = index.search(texteÀChercher) + + // @ts-expect-error TS ne sait pas que la `ref` correspond à l'`id` du dossier + return new Set(lunrRésultats.map(({ref}) => Number(ref))) +} diff --git a/scripts/server/database/dossier.js b/scripts/server/database/dossier.js index 02b4c93f..0bc0ef63 100644 --- a/scripts/server/database/dossier.js +++ b/scripts/server/database/dossier.js @@ -275,10 +275,10 @@ const colonnesDossierComplet = [ "enjeu_politique", "commentaire_enjeu", "commentaire_libre", + "historique_identifiant_demande_onagre", /* "historique_date_réception_ddep", "historique_date_envoi_dernière_contribution", - "historique_identifiant_demande_onagre", "historique_date_saisine_csrpn", "historique_date_saisine_cnpn", "date_avis_csrpn", @@ -350,4 +350,4 @@ export function updateDossier(id, dossierParams) { return directDatabaseConnection('dossier') .where({ id }) .update(dossierParams) -} \ No newline at end of file +} diff --git a/scripts/types.js b/scripts/types.js index 3a7e0048..b2b8fa1f 100644 --- a/scripts/types.js +++ b/scripts/types.js @@ -62,8 +62,8 @@ * * @typedef {Object} DossierDémarcheSimplifiée88444Communes * @property {string} name - * @property {string} code - * @property {string} postalCode + * @property {string} code // Code INSEE (identifiant de commune pour l'administration publique) + * @property {string} postalCode // Code postal (zone de desserte postale) * * @typedef {Object} DossierLocalisation * @property {DossierDémarcheSimplifiée88444Communes[]} communes @@ -74,4 +74,4 @@ /** @typedef {Dossier & DossierComplémentPersonnesImpliquées & DossierPhaseEtProchaineAction & DossierLocalisation} DossierComplet */ -export default 'TS needs a module' \ No newline at end of file +export default 'TS needs a module' diff --git a/scripts/types/customTypes.d.ts b/scripts/types/customTypes.d.ts index 99abd56b..2b78d23a 100644 --- a/scripts/types/customTypes.d.ts +++ b/scripts/types/customTypes.d.ts @@ -1,3 +1,5 @@ declare module 'simple-svelte-autocomplete' declare module 'ods-xlsx' declare module 'minimist' +declare module 'lunr-languages/lunr.stemmer.support' +declare module 'lunr-languages/lunr.fr'