From a52434ca2bc3dcc6ac10926c6440c2068ff28216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com> Date: Wed, 2 Apr 2025 11:42:05 +0200 Subject: [PATCH 1/3] quick access titre --- packages/ui/src/components/page/quick-access-titre.tsx | 10 ++++++---- packages/ui/src/components/titre/titre-api-client.ts | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/page/quick-access-titre.tsx b/packages/ui/src/components/page/quick-access-titre.tsx index bd35f8f31..99f112d57 100644 --- a/packages/ui/src/components/page/quick-access-titre.tsx +++ b/packages/ui/src/components/page/quick-access-titre.tsx @@ -9,10 +9,11 @@ import { capitalize } from 'camino-common/src/strings' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' import { CaminoAnnee, getAnnee } from 'camino-common/src/date' import { TypeAheadSingle } from '../_ui/typeahead-single' +import { useState } from '@/utils/vue-tsx-utils' export const QuickAccessTitre = defineComponent<{ id: string; onSelectTitre: () => void }>(props => { const router = useRouter() - const titres = ref<TitreForTitresRerchercherByNom[]>([]) + const [titres, setTitres] = useState<TitreForTitresRerchercherByNom[]>([]) const search = async (searchTerm: string): Promise<void> => { const intervalle = 10 @@ -25,7 +26,7 @@ export const QuickAccessTitre = defineComponent<{ id: string; onSelectTitre: () references: searchTerm, }) } - titres.value.splice(0, titres.value.length, ...searchTitres.elements) + setTitres(searchTitres.elements) } const onSelectedTitre = (titre: TitreForTitresRerchercherByNom | undefined) => { @@ -53,8 +54,9 @@ interface DisplayTitreProps { } export const DisplayTitre: FunctionalComponent<DisplayTitreProps> = props => { let annee: CaminoAnnee | null = null - if (isNotNullNorUndefined(props.titre.demarches?.[0]?.demarcheDateDebut)) { - annee = getAnnee(props.titre.demarches?.[0]?.demarcheDateDebut) + const firstDemarche = props.titre.demarches.find(({ordre}) => ordre === 1) + if (isNotNullNorUndefined(firstDemarche?.demarcheDateDebut)) { + annee = getAnnee(firstDemarche.demarcheDateDebut) } return ( diff --git a/packages/ui/src/components/titre/titre-api-client.ts b/packages/ui/src/components/titre/titre-api-client.ts index 465118577..a6a1b4b9e 100644 --- a/packages/ui/src/components/titre/titre-api-client.ts +++ b/packages/ui/src/components/titre/titre-api-client.ts @@ -37,7 +37,7 @@ export type TitreForTitresRerchercherByNom = { id: TitreId nom: string typeId: TitreTypeId - demarches: { demarcheDateDebut: CaminoDate | null }[] + demarches: { demarcheDateDebut: CaminoDate | null; ordre: number }[] } export interface TitreApiClient { removeTitre: (titreId: TitreId) => Promise<void> @@ -287,6 +287,10 @@ export const titreApiClient: TitreApiClient = { id nom typeId + demarches { + ordre + demarcheDateDebut + } } } } @@ -304,6 +308,7 @@ export const titreApiClient: TitreApiClient = { nom typeId demarches { + ordre demarcheDateDebut } } -- GitLab From b9e90043666d3511c2dcf48d2281b75a25428c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com> Date: Wed, 2 Apr 2025 15:29:07 +0200 Subject: [PATCH 2/3] use dedicated rest route --- .../api/src/api/rest/quick-access.queries.ts | 50 ++++++++ .../api/rest/quick-access.queries.types.ts | 42 +++++++ .../api/rest/quick-access.test.integration.ts | 109 ++++++++++++++++++ packages/api/src/api/rest/quick-access.ts | 61 ++++++++++ .../migrations/20250402125154_add-unaccent.ts | 19 +++ packages/api/src/server/rest.ts | 2 + packages/common/src/rest.ts | 6 + packages/common/src/titres.ts | 9 ++ .../page/quick-access-titre.stories.tsx | 12 +- .../components/page/quick-access-titre.tsx | 48 ++++---- .../src/components/titre/titre-api-client.ts | 7 +- 11 files changed, 335 insertions(+), 30 deletions(-) create mode 100644 packages/api/src/api/rest/quick-access.queries.ts create mode 100644 packages/api/src/api/rest/quick-access.queries.types.ts create mode 100644 packages/api/src/api/rest/quick-access.test.integration.ts create mode 100644 packages/api/src/api/rest/quick-access.ts create mode 100644 packages/api/src/knex/migrations/20250402125154_add-unaccent.ts diff --git a/packages/api/src/api/rest/quick-access.queries.ts b/packages/api/src/api/rest/quick-access.queries.ts new file mode 100644 index 000000000..8f9721776 --- /dev/null +++ b/packages/api/src/api/rest/quick-access.queries.ts @@ -0,0 +1,50 @@ +import { sql } from '@pgtyped/runtime' +import { EffectDbQueryAndValidateErrors, Redefine, effectDbQueryAndValidate } from '../../pg-database.js' +import { z } from 'zod' +import { Pool } from 'pg' +import { titreIdValidator } from 'camino-common/src/validators/titres.js' +import { Effect } from 'effect' +import { CaminoError } from 'camino-common/src/zod-tools.js' +import { IGetTitresByNomDbQuery, IGetTitresByReferenceDbQuery } from './quick-access.queries.types.js' +import { titreTypeIdValidator } from 'camino-common/src/static/titresTypes.js' +import { caminoDateValidator } from 'camino-common/src/date.js' + +const titresByNomDbValidator = z.object({ + id: titreIdValidator, + type_id: titreTypeIdValidator, + nom: z.string(), + public_lecture: z.boolean(), + demarche_date_debut: caminoDateValidator.nullable(), +}) +type TitreByNomDb = z.infer<typeof titresByNomDbValidator> + +type GetTitresByNomErrors = EffectDbQueryAndValidateErrors +export const getTitresByReference = (pool: Pool, reference: string): Effect.Effect<TitreByNomDb[], CaminoError<GetTitresByNomErrors>> => + effectDbQueryAndValidate(getTitresByReferenceDb, { reference: `%${reference}%` }, pool, titresByNomDbValidator) + +const getTitresByReferenceDb = sql<Redefine<IGetTitresByReferenceDbQuery, { reference: string }, TitreByNomDb>>` +SELECT + t.id, + t.type_id, + t.nom, + t.public_lecture, + td.demarche_date_debut +FROM titres t +LEFT JOIN titres_demarches td ON td.titre_id = t.id AND td.ordre = 1 AND td.archive IS FALSE +WHERE t.archive IS FALSE AND EXISTS (SELECT 1 FROM jsonb_array_elements(t.references) titreRefs WHERE LOWER(titreRefs->>'nom') LIKE LOWER($reference!)) LIMIT 10 +` + +export const getTitresByNom = (pool: Pool, nom: string): Effect.Effect<TitreByNomDb[], CaminoError<GetTitresByNomErrors>> => + effectDbQueryAndValidate(getTitresByNomDb, { nom: `%${nom}%` }, pool, titresByNomDbValidator) + +const getTitresByNomDb = sql<Redefine<IGetTitresByNomDbQuery, { nom: string }, TitreByNomDb>>` +SELECT + t.id, + t.type_id, + t.nom, + t.public_lecture, + td.demarche_date_debut +FROM titres t +LEFT JOIN titres_demarches td ON td.titre_id = t.id and td.ordre = 1 AND td.archive IS FALSE +WHERE t.archive IS FALSE AND LOWER(unaccent(t.nom)) LIKE LOWER(unaccent($nom)) LIMIT 10 +` diff --git a/packages/api/src/api/rest/quick-access.queries.types.ts b/packages/api/src/api/rest/quick-access.queries.types.ts new file mode 100644 index 000000000..763778101 --- /dev/null +++ b/packages/api/src/api/rest/quick-access.queries.types.ts @@ -0,0 +1,42 @@ +/** Types generated for queries found in "src/api/rest/quick-access.queries.ts" */ + +/** 'GetTitresByReferenceDb' parameters type */ +export interface IGetTitresByReferenceDbParams { + reference: string; +} + +/** 'GetTitresByReferenceDb' return type */ +export interface IGetTitresByReferenceDbResult { + demarche_date_debut: string | null; + id: string; + nom: string; + public_lecture: boolean; + type_id: string; +} + +/** 'GetTitresByReferenceDb' query type */ +export interface IGetTitresByReferenceDbQuery { + params: IGetTitresByReferenceDbParams; + result: IGetTitresByReferenceDbResult; +} + +/** 'GetTitresByNomDb' parameters type */ +export interface IGetTitresByNomDbParams { + nom?: string | null | void; +} + +/** 'GetTitresByNomDb' return type */ +export interface IGetTitresByNomDbResult { + demarche_date_debut: string | null; + id: string; + nom: string; + public_lecture: boolean; + type_id: string; +} + +/** 'GetTitresByNomDb' query type */ +export interface IGetTitresByNomDbQuery { + params: IGetTitresByNomDbParams; + result: IGetTitresByNomDbResult; +} + diff --git a/packages/api/src/api/rest/quick-access.test.integration.ts b/packages/api/src/api/rest/quick-access.test.integration.ts new file mode 100644 index 000000000..8adc12bee --- /dev/null +++ b/packages/api/src/api/rest/quick-access.test.integration.ts @@ -0,0 +1,109 @@ +import { dbManager } from '../../../tests/db-manager' +import { restNewCall } from '../../../tests/_utils/index' +import { ADMINISTRATION_IDS } from 'camino-common/src/static/administrations' +import { afterAll, beforeAll, describe, test, expect, vi } from 'vitest' +import type { Pool } from 'pg' +import { newTitreId } from '../../database/models/_format/id-create' +import { insertTitreGraph } from '../../../tests/integration-test-helper' + +console.info = vi.fn() +console.error = vi.fn() + +let dbPool: Pool +beforeAll(async () => { + const { pool } = await dbManager.populateDb() + dbPool = pool +}) + +afterAll(async () => { + await dbManager.closeKnex() +}) + +describe('quickAccess', () => { + test('par référence', async () => { + const reference = 'refTest' + await insertTitreGraph({ + id: newTitreId('id-par-reference'), + nom: 'Nom-par-référence', + typeId: 'arm', + titreStatutId: 'val', + propsTitreEtapesIds: {}, + publicLecture: true, + references: [{ nom: reference, referenceTypeId: 'brg' }], + }) + const tested = await restNewCall( + dbPool, + '/rest/quickAccess', + {}, + { + role: 'admin', + administrationId: ADMINISTRATION_IDS['DGTM - GUYANE'], + }, + { search: reference } + ) + + expect(tested.body).toMatchInlineSnapshot(` + [ + { + "id": "id-par-reference", + "nom": "Nom-par-référence", + "titreDateDebut": null, + "typeId": "arm", + }, + ] + `) + }) + + test('par nom', async () => { + await insertTitreGraph({ + id: newTitreId('id-titre-avec-accents'), + nom: 'Titre avÉc des Accènts', + typeId: 'arm', + titreStatutId: 'val', + propsTitreEtapesIds: {}, + publicLecture: true, + }) + const tested = await restNewCall( + dbPool, + '/rest/quickAccess', + {}, + { + role: 'admin', + administrationId: ADMINISTRATION_IDS['DGTM - GUYANE'], + }, + { search: 'avec des acc' } + ) + + expect(tested.body).toMatchInlineSnapshot(` + [ + { + "id": "id-titre-avec-accents", + "nom": "Titre avÉc des Accènts", + "titreDateDebut": null, + "typeId": "arm", + }, + ] + `) + }) + test('droits insuffisants', async () => { + await insertTitreGraph({ + id: newTitreId('id-titre-filtré'), + nom: 'titre filtré', + typeId: 'arm', + titreStatutId: 'val', + propsTitreEtapesIds: {}, + publicLecture: false, + }) + const tested = await restNewCall( + dbPool, + '/rest/quickAccess', + {}, + { + role: 'defaut', + }, + { search: 'filtre' } + ) + + expect(tested.body).toMatchInlineSnapshot(`[]`) + }) +}) diff --git a/packages/api/src/api/rest/quick-access.ts b/packages/api/src/api/rest/quick-access.ts new file mode 100644 index 000000000..476d789e1 --- /dev/null +++ b/packages/api/src/api/rest/quick-access.ts @@ -0,0 +1,61 @@ +import { canReadTitre } from 'camino-common/src/permissions/titres' +import { QuickAccessResult } from 'camino-common/src/titres' +import { HTTP_STATUS } from 'camino-common/src/http' +import { Effect, Match } from 'effect' +import { CaminoApiError } from '../../types' +import { RestNewGetCall } from '../../server/rest' +import { EffectDbQueryAndValidateErrors } from '../../pg-database' +import { getTitresByNom, getTitresByReference } from './quick-access.queries' +import { getAdministrationsLocalesByTitreId, getTitulairesAmodiatairesByTitreId } from './titres.queries' +import { isNullOrUndefinedOrEmpty } from 'camino-common/src/typescript-tools' + +const errorCanReadTitre = "Erreur lors de l'accès aux permissions" as const +type QuickAccessSearchErrors = EffectDbQueryAndValidateErrors | typeof errorCanReadTitre + +export const quickAccessSearch: RestNewGetCall<'/rest/quickAccess'> = (rootPipe): Effect.Effect<QuickAccessResult[], CaminoApiError<QuickAccessSearchErrors>> => { + return rootPipe.pipe( + Effect.bind('unfilteredTitres', ({ searchParams, pool }) => + getTitresByNom(pool, searchParams.search).pipe( + Effect.flatMap(result => { + if (isNullOrUndefinedOrEmpty(result)) { + return getTitresByReference(pool, searchParams.search) + } else { + return Effect.succeed(result) + } + }) + ) + ), + Effect.bind('filteredTitres', ({ unfilteredTitres, user, pool }) => + Effect.tryPromise({ + try: async () => { + const titres = [] + for (const titre of unfilteredTitres) { + if ( + await canReadTitre( + user, + () => Promise.resolve(titre.type_id), + () => getAdministrationsLocalesByTitreId(pool, titre.id), + () => getTitulairesAmodiatairesByTitreId(pool, titre.id), + titre + ) + ) { + titres.push(titre) + } + } + return titres + }, + catch: e => ({ message: errorCanReadTitre, extra: e }), + }) + ), + Effect.map(({ filteredTitres }) => filteredTitres.map<QuickAccessResult>(titre => ({ id: titre.id, nom: titre.nom, typeId: titre.type_id, titreDateDebut: titre.demarche_date_debut }))), + Effect.mapError(caminoError => + Match.value(caminoError.message).pipe( + Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Les données en base ne correspondent pas à ce qui est attendu', "Erreur lors de l'accès aux permissions", () => ({ + ...caminoError, + status: HTTP_STATUS.INTERNAL_SERVER_ERROR, + })), + Match.exhaustive + ) + ) + ) +} diff --git a/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts b/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts new file mode 100644 index 000000000..f8e521a8a --- /dev/null +++ b/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts @@ -0,0 +1,19 @@ +import { Knex } from 'knex' + +export const up = async (knex: Knex): Promise<void> => { + await knex.raw('CREATE EXTENSION unaccent') + await knex.raw(`CREATE OR REPLACE FUNCTION my_unaccent(some_time varchar) + RETURNS text + AS + $BODY$ + select unaccent($1); + $BODY$ + LANGUAGE sql + IMMUTABLE;`) + await knex.raw('CREATE INDEX ON titres (lower(my_unaccent(nom)));') +} + +export const down = (): void => {} + +// export const config = { transaction: false }; +// export default config diff --git a/packages/api/src/server/rest.ts b/packages/api/src/server/rest.ts index 40264ccf3..6b8f2c1ed 100644 --- a/packages/api/src/server/rest.ts +++ b/packages/api/src/server/rest.ts @@ -58,6 +58,7 @@ import { addLog } from '../api/rest/logs.queries' import { HTTP_STATUS } from 'camino-common/src/http' import { zodParseEffectTyped } from '../tools/fp-tools' import { Cause, Effect, Exit, Option, pipe } from 'effect' +import { quickAccessSearch } from '../api/rest/quick-access' interface IRestResolverResult { nom: string @@ -191,6 +192,7 @@ const restRouteImplementations: Readonly<{ [key in CaminoRestRoute]: Transform<k '/config': { newGetCall: getConfig, ...CaminoRestRoutes['/config'] }, '/rest/titres/:id/titreLiaisons': { newGetCall: getTitreLiaisons, newPostCall: postTitreLiaisons, ...CaminoRestRoutes['/rest/titres/:id/titreLiaisons'] }, '/rest/etapesTypes/:demarcheId/:date': { newGetCall: getEtapesTypesEtapesStatusWithMainStep, ...CaminoRestRoutes['/rest/etapesTypes/:demarcheId/:date'] }, + '/rest/quickAccess': { newGetCall: quickAccessSearch, ...CaminoRestRoutes['/rest/quickAccess'] }, '/rest/titres': { newPostCall: titreDemandeCreer, ...CaminoRestRoutes['/rest/titres'] }, '/rest/titres/:titreId': { deleteCall: removeTitre, postCall: updateTitre, getCall: getTitre, ...CaminoRestRoutes['/rest/titres/:titreId'] }, '/rest/titres/:titreId/abonne': { postCall: utilisateurTitreAbonner, newGetCall: getUtilisateurTitreAbonner, ...CaminoRestRoutes['/rest/titres/:titreId/abonne'] }, diff --git a/packages/common/src/rest.ts b/packages/common/src/rest.ts index 193ce0d15..96a20d901 100644 --- a/packages/common/src/rest.ts +++ b/packages/common/src/rest.ts @@ -22,6 +22,7 @@ import { qgisTokenRestValidator, utilisateurToEdit, utilisateursSearchParamsVali import { editableTitreValidator, getDemarcheByIdOrSlugValidator, + quickAccessResultValidator, superTitreValidator, titreAdministrationValidator, titreDemandeOutputValidator, @@ -98,6 +99,7 @@ const IDS = [ '/rest/titres/:titreId', '/rest/titres/:titreId/abonne', '/rest/titresAdministrations', + '/rest/quickAccess', '/rest/titresSuper', '/rest/titres/:id/titreLiaisons', '/rest/demarches', @@ -155,6 +157,9 @@ const entrepriseIdParamsValidator = z.object({ entrepriseId: entrepriseIdValidat const etapeIdParamsValidator = z.object({ etapeId: etapeIdValidator }) const administrationIdParamsValidator = z.object({ administrationId: administrationIdValidator }) const geoSystemIdParamsValidator = z.object({ geoSystemeId: geoSystemeIdValidator }) +const quickAccessSearchParamsValidator = z.object({ search: z.string() }) + +const quickAccessArrayResultValidator = z.array(quickAccessResultValidator) export const CaminoRestRoutes = { '/config': { params: noParamsValidator, newGet: { output: caminoConfigValidator } }, '/moi': { params: noParamsValidator, newGet: { output: userValidator } }, @@ -169,6 +174,7 @@ export const CaminoRestRoutes = { '/rest/statistiques/granulatsMarins': { params: noParamsValidator, get: { output: statistiquesGranulatsMarinsValidator } }, '/rest/statistiques/granulatsMarins/:annee': { params: z.object({ annee: caminoAnneeValidator }), get: { output: statistiquesGranulatsMarinsValidator } }, '/rest/statistiques/datagouv': { params: noParamsValidator, get: { output: z.array(statistiquesDataGouvValidator) } }, + '/rest/quickAccess': { params: noParamsValidator, newGet: { searchParams: quickAccessSearchParamsValidator, output: quickAccessArrayResultValidator } }, '/rest/titres': { params: noParamsValidator, newPost: { input: titreDemandeValidator, output: titreDemandeOutputValidator } }, '/rest/titres/:titreId': { params: z.object({ titreId: titreIdOrSlugValidator }), get: { output: titreGetValidator }, delete: true, post: { output: z.void(), input: editableTitreValidator } }, '/rest/titres/:titreId/abonne': { params: z.object({ titreId: titreIdValidator }), post: { input: utilisateurTitreAbonneValidator, output: z.void() }, newGet: { output: z.boolean() } }, diff --git a/packages/common/src/titres.ts b/packages/common/src/titres.ts index de3e3a45c..fa593498f 100644 --- a/packages/common/src/titres.ts +++ b/packages/common/src/titres.ts @@ -182,3 +182,12 @@ export const titreDemandeOutputValidator = z.object({ etapeId: etapeIdValidator. export type TitreDemandeOutput = z.infer<typeof titreDemandeOutputValidator> export const createAutomaticallyEtapeWhenCreatingTitre = (user: User): boolean => isEntrepriseOrBureauDEtude(user) + +export const quickAccessResultValidator = z.object({ + id: titreIdValidator, + nom: z.string(), + typeId: titreTypeIdValidator, + titreDateDebut: caminoDateValidator.nullable(), +}) + +export type QuickAccessResult = z.infer<typeof quickAccessResultValidator> diff --git a/packages/ui/src/components/page/quick-access-titre.stories.tsx b/packages/ui/src/components/page/quick-access-titre.stories.tsx index 4b24445c3..a0b98a734 100644 --- a/packages/ui/src/components/page/quick-access-titre.stories.tsx +++ b/packages/ui/src/components/page/quick-access-titre.stories.tsx @@ -21,13 +21,13 @@ export const Simple: StoryFn = () => ( id: titreIdValidator.parse('1'), nom: 'monTitre', typeId: 'arm', - demarches: [], + titreDateDebut: null, }, { id: titreIdValidator.parse('1'), nom: 'monSecondTitre', typeId: 'arg', - demarches: [], + titreDateDebut: null, }, ]} onSearch={onSearch} @@ -42,7 +42,7 @@ export const Full: StoryFn = () => ( id: titreIdValidator.parse(`${index}`), nom: `Nom du titre ${index}`, typeId: index % 3 === 0 ? 'arg' : index % 2 === 0 ? 'cxh' : 'axm', - demarches: [{ demarcheDateDebut: toCaminoDate(`2023-01-0${(index % 9) + 1}`) }], + titreDateDebut: toCaminoDate(`2023-01-0${(index % 9) + 1}`), }))} onSearch={onSearch} onSelectedTitre={onSelectedTitre} @@ -56,7 +56,7 @@ export const FullAlwaysOpen: StoryFn = () => ( id: titreIdValidator.parse(`${index}`), nom: `Nom du titre ${index}`, typeId: index % 3 === 0 ? 'arg' : index % 2 === 0 ? 'cxh' : 'axm', - demarches: [{ demarcheDateDebut: toCaminoDate(`2023-01-0${(index % 9) + 1}`) }], + titreDateDebut: toCaminoDate(`2023-01-0${(index % 9) + 1}`), }))} onSearch={onSearch} onSelectedTitre={onSelectedTitre} @@ -71,7 +71,7 @@ export const DisplayTitreSeulSansDate: StoryFn = () => ( titre={{ nom: 'monTitre', typeId: 'arm', - demarches: [], + titreDateDebut: null, }} /> ) @@ -81,7 +81,7 @@ export const DisplayTitreSeulAvecDate: StoryFn = () => ( titre={{ nom: 'monTitre', typeId: 'arm', - demarches: [{ demarcheDateDebut: toCaminoDate('2023-09-26') }], + titreDateDebut: toCaminoDate('2023-09-26'), }} /> ) diff --git a/packages/ui/src/components/page/quick-access-titre.tsx b/packages/ui/src/components/page/quick-access-titre.tsx index 99f112d57..fa05b35f9 100644 --- a/packages/ui/src/components/page/quick-access-titre.tsx +++ b/packages/ui/src/components/page/quick-access-titre.tsx @@ -4,32 +4,37 @@ import { getDomaineId, getTitreTypeType } from 'camino-common/src/static/titresT import { createDebounce } from '@/utils/debounce' import { useRouter } from 'vue-router' import { ref, FunctionalComponent, defineComponent } from 'vue' -import { titreApiClient, TitreForTitresRerchercherByNom } from '../titre/titre-api-client' +import { titreApiClient } from '../titre/titre-api-client' import { capitalize } from 'camino-common/src/strings' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' import { CaminoAnnee, getAnnee } from 'camino-common/src/date' import { TypeAheadSingle } from '../_ui/typeahead-single' import { useState } from '@/utils/vue-tsx-utils' +import { QuickAccessResult } from 'camino-common/src/titres' export const QuickAccessTitre = defineComponent<{ id: string; onSelectTitre: () => void }>(props => { const router = useRouter() - const [titres, setTitres] = useState<TitreForTitresRerchercherByNom[]>([]) + const [titres, setTitres] = useState<QuickAccessResult[]>([]) const search = async (searchTerm: string): Promise<void> => { - const intervalle = 10 - - let searchTitres = await titreApiClient.titresRechercherByNom(searchTerm) - - if (searchTitres.elements.length === 0) { - searchTitres = await titreApiClient.titresRechercherByReferences({ - intervalle, - references: searchTerm, - }) + const searchTitres = await titreApiClient.quickAccess(searchTerm) + if ('message' in searchTitres) { + console.error(searchTitres) + setTitres([]) + } else { + setTitres(searchTitres) } - setTitres(searchTitres.elements) + + // if (searchTitres.elements.length === 0) { + // searchTitres = await titreApiClient.titresRechercherByReferences({ + // intervalle, + // references: searchTerm, + // }) + // } + // setTitres(searchTitres.elements) } - const onSelectedTitre = (titre: TitreForTitresRerchercherByNom | undefined) => { + const onSelectedTitre = (titre: QuickAccessResult | undefined) => { if (titre) { router.push({ name: 'titre', params: { id: titre.id } }) props.onSelectTitre() @@ -44,19 +49,18 @@ QuickAccessTitre.props = ['id', 'onSelectTitre'] interface Props { id: string - titres: TitreForTitresRerchercherByNom[] - onSelectedTitre: (titre: TitreForTitresRerchercherByNom | undefined) => void + titres: QuickAccessResult[] + onSelectedTitre: (titre: QuickAccessResult | undefined) => void alwaysOpen?: boolean onSearch: (searchTerm: string) => void } interface DisplayTitreProps { - titre: Pick<TitreForTitresRerchercherByNom, 'nom' | 'typeId' | 'demarches'> + titre: Pick<QuickAccessResult, 'nom' | 'typeId' | 'titreDateDebut'> } export const DisplayTitre: FunctionalComponent<DisplayTitreProps> = props => { let annee: CaminoAnnee | null = null - const firstDemarche = props.titre.demarches.find(({ordre}) => ordre === 1) - if (isNotNullNorUndefined(firstDemarche?.demarcheDateDebut)) { - annee = getAnnee(firstDemarche.demarcheDateDebut) + if (isNotNullNorUndefined(props.titre.titreDateDebut)) { + annee = getAnnee(props.titre.titreDateDebut) } return ( @@ -74,12 +78,12 @@ export const DisplayTitre: FunctionalComponent<DisplayTitreProps> = props => { } export const PureQuickAccessTitre = defineComponent<Props>(props => { - const display = (titre: TitreForTitresRerchercherByNom) => { + const display = (titre: QuickAccessResult) => { return <DisplayTitre titre={titre} /> } - const overrideItem = ref<TitreForTitresRerchercherByNom | null>(null) - const selectItem = (item: TitreForTitresRerchercherByNom | undefined) => { + const overrideItem = ref<QuickAccessResult | null>(null) + const selectItem = (item: QuickAccessResult | undefined) => { overrideItem.value = null props.onSelectedTitre(item) } diff --git a/packages/ui/src/components/titre/titre-api-client.ts b/packages/ui/src/components/titre/titre-api-client.ts index a6a1b4b9e..1595e1910 100644 --- a/packages/ui/src/components/titre/titre-api-client.ts +++ b/packages/ui/src/components/titre/titre-api-client.ts @@ -1,4 +1,4 @@ -import { EditableTitre, TitreDemande, TitreDemandeOutput, TitreGet } from 'camino-common/src/titres' +import { EditableTitre, QuickAccessResult, TitreDemande, TitreDemandeOutput, TitreGet } from 'camino-common/src/titres' import { TitreId, TitreIdOrSlug } from 'camino-common/src/validators/titres' import { deleteWithJson, getWithJson, newGetWithJson, newPostWithJson, postWithJson } from '../../api/client-rest' import { CaminoDate } from 'camino-common/src/date' @@ -33,12 +33,13 @@ export type TitreForTable = { references?: { referenceTypeId: ReferenceTypeId; nom: string }[] } -export type TitreForTitresRerchercherByNom = { +type TitreForTitresRerchercherByNom = { id: TitreId nom: string typeId: TitreTypeId demarches: { demarcheDateDebut: CaminoDate | null; ordre: number }[] } + export interface TitreApiClient { removeTitre: (titreId: TitreId) => Promise<void> titreUtilisateurAbonne: (titreId: TitreId, abonne: boolean) => Promise<void> @@ -98,6 +99,7 @@ export interface TitreApiClient { facadesMaritimes: FacadesMaritimes[] perimetre?: [number, number, number, number] }) => Promise<{ elements: TitreWithPerimetre[]; total: number }> + quickAccess: (nom: string) => Promise<QuickAccessResult[] | CaminoError<string>> titresRechercherByNom: (nom: string) => Promise<{ elements: TitreForTitresRerchercherByNom[] }> titresRechercherByReferences: (params: { intervalle: number; references: string }) => Promise<{ elements: TitreForTitresRerchercherByNom[] }> getTitresByIds: (titreIds: TitreId[], cacheKey: string) => Promise<{ elements: Pick<TitreForTable, 'id' | 'nom'>[] }> @@ -105,6 +107,7 @@ export interface TitreApiClient { } export const titreApiClient: TitreApiClient = { + quickAccess: nom => newGetWithJson('/rest/quickAccess', {}, { search: nom }), removeTitre: async (titreId: TitreId): Promise<void> => { return deleteWithJson('/rest/titres/:titreId', { titreId }) }, -- GitLab From 6fa775de7af1fa4f7953e4c3d0fcbf9cb1bd9855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com> Date: Wed, 2 Apr 2025 15:41:19 +0200 Subject: [PATCH 3/3] pr review --- .../src/knex/migrations/20250402125154_add-unaccent.ts | 3 --- packages/ui/src/components/page/quick-access-titre.tsx | 8 -------- 2 files changed, 11 deletions(-) diff --git a/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts b/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts index f8e521a8a..eea299502 100644 --- a/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts +++ b/packages/api/src/knex/migrations/20250402125154_add-unaccent.ts @@ -14,6 +14,3 @@ export const up = async (knex: Knex): Promise<void> => { } export const down = (): void => {} - -// export const config = { transaction: false }; -// export default config diff --git a/packages/ui/src/components/page/quick-access-titre.tsx b/packages/ui/src/components/page/quick-access-titre.tsx index fa05b35f9..1767b7b60 100644 --- a/packages/ui/src/components/page/quick-access-titre.tsx +++ b/packages/ui/src/components/page/quick-access-titre.tsx @@ -24,14 +24,6 @@ export const QuickAccessTitre = defineComponent<{ id: string; onSelectTitre: () } else { setTitres(searchTitres) } - - // if (searchTitres.elements.length === 0) { - // searchTitres = await titreApiClient.titresRechercherByReferences({ - // intervalle, - // references: searchTerm, - // }) - // } - // setTitres(searchTitres.elements) } const onSelectedTitre = (titre: QuickAccessResult | undefined) => { -- GitLab