diff --git a/package-lock.json b/package-lock.json index b99f39edffbedd9cfc41d1f8f814d0e87b72226c..d7fa3e426c02e96b319366f4521d0e6906014d3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6337,51 +6337,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@pgtyped/runtime": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pgtyped/runtime/-/runtime-2.4.2.tgz", - "integrity": "sha512-W1fK475KjmkhleK4Be7swblT5Kqykxc/APe57r8cgmxLzbT/x9Q3BjOLBJhBPxp1gKFZGgQyxtELPOaNl/66jg==", - "license": "MIT", - "dependencies": { - "@pgtyped/parser": "^2.4.2", - "chalk": "^4.1.0", - "debug": "^4.1.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@pgtyped/runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@pgtyped/runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -30975,7 +30930,7 @@ "dependencies": { "@graphql-tools/graphql-file-loader": "^8.0.14", "@graphql-tools/load": "^8.0.14", - "@pgtyped/runtime": "^2.4.2", + "@pgtyped/runtime": "^2.3.0", "@sentry/node": "^9.1.0", "@swc/core": "^1.10.16", "@tus/file-store": "^1.5.1", @@ -31047,6 +31002,51 @@ "vitest": "^3.0.5" } }, + "packages/api/node_modules/@pgtyped/runtime": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@pgtyped/runtime/-/runtime-2.3.0.tgz", + "integrity": "sha512-B8RMUeX+zsaXfKOuR3w3Vku5YLSQ8rw+YUYc2IyAFzlQJZpAQDkkAVM0abgGNQE8bOK1wuE+nHJawWuVy+I8bA==", + "license": "MIT", + "dependencies": { + "@pgtyped/parser": "^2.3.0", + "chalk": "^4.1.0", + "debug": "^4.1.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "packages/api/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/api/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "packages/common": { "name": "camino-common", "version": "1.0.0", diff --git a/packages/api/package.json b/packages/api/package.json index 380213a257a9083461900d1530f3b6c27a9f5a3f..e38eef6ed3c05411bdbb327f2e33cb78ada6f2f4 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -20,8 +20,8 @@ "db:recreate": "dropdb --host=localhost --username=postgres camino && createdb --host=localhost --username=postgres camino", "db:migrate": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/knex/migrate.ts", "db:add-migration": "NODE_OPTIONS='--loader ts-node/esm/transpile-only' knex migrate:make", - "db:watch": "npx --yes --package=@pgtyped/cli pgtyped -w -c pgtyped-config.json", - "db:check": "npx --yes --package=@pgtyped/cli pgtyped -c pgtyped-config.ci.json", + "db:watch": "npx --yes --package=@pgtyped/cli@2.3.0 pgtyped -w -c pgtyped-config.json", + "db:check": "npx --yes --package=@pgtyped/cli@2.3.0 pgtyped -c pgtyped-config.ci.json", "dev": "node --watch --enable-source-maps --loader ts-node/esm/transpile-only --inspect ./src/index.ts", "daily-debug": "node --inspect-brk=3000 --loader ts-node/esm/transpile-only ./src/scripts/daily.ts", "dev:update": "npm-check-updates -u && npm install && npm audit fix", @@ -45,7 +45,7 @@ "dependencies": { "@graphql-tools/graphql-file-loader": "^8.0.14", "@graphql-tools/load": "^8.0.14", - "@pgtyped/runtime": "^2.4.2", + "@pgtyped/runtime": "^2.3.0", "@sentry/node": "^9.1.0", "@swc/core": "^1.10.16", "@tus/file-store": "^1.5.1", diff --git a/packages/api/src/api/rest/titres.test.integration.ts b/packages/api/src/api/rest/titres.test.integration.ts index e2816f7571e3d5860bf8726caeb351bbefade965..9803250238f0373e55f6a6ef42047714fedf7009 100644 --- a/packages/api/src/api/rest/titres.test.integration.ts +++ b/packages/api/src/api/rest/titres.test.integration.ts @@ -8,7 +8,7 @@ import { ADMINISTRATION_IDS } from 'camino-common/src/static/administrations' import { ITitreDemarche, ITitreEtape } from '../../types' import { entreprisesUpsert } from '../../database/queries/entreprises' import { Knex } from 'knex' -import { toCaminoDate } from 'camino-common/src/date' +import { caminoDateValidator, toCaminoDate } from 'camino-common/src/date' import { afterAll, beforeAll, beforeEach, describe, test, expect, vi } from 'vitest' import { newEntrepriseId } from 'camino-common/src/entreprise' import type { Pool } from 'pg' @@ -20,9 +20,13 @@ import { titreSlugValidator } from 'camino-common/src/validators/titres' import TitresDemarches from '../../database/models/titres-demarches' import TitresEtapes from '../../database/models/titres-etapes' import Titres from '../../database/models/titres' -import { ETAPE_IS_NOT_BROUILLON } from 'camino-common/src/etape' +import { ETAPE_IS_BROUILLON, ETAPE_IS_NOT_BROUILLON } from 'camino-common/src/etape' import { insertTitreGraph } from '../../../tests/integration-test-helper' import { DemarcheId } from 'camino-common/src/demarche' +import { testBlankUser } from 'camino-common/src/tests-utils' +import { DEMARCHES_TYPES_IDS } from 'camino-common/src/static/demarchesTypes' +import { ETAPES_TYPES } from 'camino-common/src/static/etapesTypes' +import { ETAPES_STATUTS } from 'camino-common/src/static/etapesStatuts' console.info = vi.fn() console.error = vi.fn() @@ -579,3 +583,99 @@ test('getUtilisateurTitreAbonner', async () => { } `) }) + +describe('titresSuper', () => { + test('ne peut pas appeler si pas super', async () => { + const tested = await restNewCall(dbPool, '/rest/titresSuper', {}, { ...testBlankUser, role: 'defaut' }) + expect(tested.body).toMatchInlineSnapshot(` + { + "message": "Seuls les super peuvent accéder à ces données", + "status": 403, + } + `) + }) + + test('récupère une liste vide', async () => { + const tested = await restNewCall(dbPool, '/rest/titresSuper', {}, userSuper) + expect(tested.body).toMatchInlineSnapshot(` + [] + `) + }) + + test('récupère la liste des titres avec une étape en brouillon', async () => { + const id = newTitreId('titreIdAvecBrouillon') + const demarcheId = newDemarcheId('demarcheIdAvecBrouillon') + const etapeId = newEtapeId('etapeIdEnBrouillon') + const idSansBrouillon = newTitreId('titreIdSansBrouillon') + const demarcheIdSansBrouillon = newDemarcheId('demarcheIdSansBrouillon') + const etapeIdSansBrouillon = newEtapeId('etapeIdNonBrouillon') + await insertTitreGraph({ + id, + nom: 'mon titre avec brouillon', + typeId: 'arm', + slug: titreSlugValidator.parse('slug'), + titreStatutId: 'val', + propsTitreEtapesIds: {}, + demarches: [ + { + id: demarcheId, + typeId: DEMARCHES_TYPES_IDS.Octroi, + titreId: id, + etapes: [ + { + id: etapeId, + typeId: ETAPES_TYPES.demande, + titreDemarcheId: demarcheId, + statutId: ETAPES_STATUTS.FAIT, + isBrouillon: ETAPE_IS_BROUILLON, + date: caminoDateValidator.parse('2025-01-10'), + }, + ], + }, + ], + }) + + await insertTitreGraph({ + id: idSansBrouillon, + nom: 'mon titre sans brouillon', + typeId: 'arm', + slug: titreSlugValidator.parse('slug-sans-brouillon'), + titreStatutId: 'val', + propsTitreEtapesIds: {}, + demarches: [ + { + id: demarcheIdSansBrouillon, + typeId: DEMARCHES_TYPES_IDS.Octroi, + titreId: idSansBrouillon, + etapes: [ + { + id: etapeIdSansBrouillon, + typeId: ETAPES_TYPES.demande, + titreDemarcheId: demarcheIdSansBrouillon, + statutId: ETAPES_STATUTS.FAIT, + isBrouillon: ETAPE_IS_NOT_BROUILLON, + date: caminoDateValidator.parse('2025-02-28'), + }, + ], + }, + ], + }) + + const tested = await restNewCall(dbPool, '/rest/titresSuper', {}, userSuper) + expect(tested.body).toMatchInlineSnapshot(` + [ + { + "demarche_slug": "titreIdAvecBrouillon-oct99", + "demarche_type_id": "oct", + "etape_date": "2025-01-10", + "etape_slug": "demarcheIdAvecBrouillon-mfr99", + "etape_type_id": "mfr", + "titre_nom": "mon titre avec brouillon", + "titre_slug": "slug", + "titre_statut_id": "val", + "titre_type_id": "arm", + }, + ] + `) + }) +}) diff --git a/packages/api/src/api/rest/titres.ts b/packages/api/src/api/rest/titres.ts index 814bab8fadca1d8144c5e23a0461598d2772a470..709ef81dbc7e6f614967c647d291a499ce0d0d33 100644 --- a/packages/api/src/api/rest/titres.ts +++ b/packages/api/src/api/rest/titres.ts @@ -1,6 +1,6 @@ import { titreArchive, titresGet, titreGet, titreUpdate } from '../../database/queries/titres' import { HTTP_STATUS } from 'camino-common/src/http' -import { CommonTitreAdministration, editableTitreValidator, TitreLink, TitreLinks, TitreGet, utilisateurTitreAbonneValidator, titreGetValidator } from 'camino-common/src/titres' +import { CommonTitreAdministration, editableTitreValidator, TitreLink, TitreLinks, TitreGet, utilisateurTitreAbonneValidator, titreGetValidator, SuperTitre } from 'camino-common/src/titres' import { CaminoRequest, CustomResponse } from './express-type' import { userSuper } from '../../database/user-super' import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, onlyUnique } from 'camino-common/src/typescript-tools' @@ -15,14 +15,14 @@ import { titreEtapeForMachineValidator, toMachineEtapes } from '../../business/r import { DemarchesStatutsIds } from 'camino-common/src/static/demarchesStatuts' import { ETAPES_TYPES, EtapeTypeId } from 'camino-common/src/static/etapesTypes' import { CaminoDate, getCurrent } from 'camino-common/src/date' -import { isAdministration, User, UserNotNull } from 'camino-common/src/roles' +import { isAdministration, isSuper, User, UserNotNull } from 'camino-common/src/roles' import { canEditDemarche, canCreateTravaux } from 'camino-common/src/permissions/titres-demarches' import { utilisateurTitreCreate, utilisateurTitreDelete } from '../../database/queries/utilisateurs-titres' import { titreUpdateTask } from '../../business/titre-update' import { getDoublonsByTitreId, getTitre as getTitreDb } from './titres.queries' import type { Pool } from 'pg' import { TitresStatutIds } from 'camino-common/src/static/titresStatuts' -import { getTitreUtilisateur } from '../../database/queries/titres-utilisateurs.queries' +import { accesSuperSeulementError, getTitresWithBrouillons, GetTitresWithBrouillonsErrors, getTitreUtilisateur } from '../../database/queries/titres-utilisateurs.queries' import { titreIdValidator, titreIdOrSlugValidator, TitreId } from 'camino-common/src/validators/titres' import { RestNewGetCall, RestNewPostCall } from '../../server/rest' import { Effect, Match, pipe } from 'effect' @@ -33,6 +33,29 @@ import { demarcheEnregistrementDemandeDateFind } from 'camino-common/src/demarch const etapesAMasquer = [ETAPES_TYPES.classementSansSuite, ETAPES_TYPES.desistementDuDemandeur, ETAPES_TYPES.demandeDeComplements_RecevabiliteDeLaDemande_] +type TitresSuperErrors = GetTitresWithBrouillonsErrors +export const titresSuper: RestNewGetCall<'/rest/titresSuper'> = (rootPipe): Effect.Effect<SuperTitre[], CaminoApiError<TitresSuperErrors>> => + rootPipe.pipe( + Effect.filterOrFail( + ({ user }) => isSuper(user), + () => ({ message: accesSuperSeulementError }) + ), + Effect.flatMap(({ pool, user }) => getTitresWithBrouillons(pool, user)), + 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', () => ({ + ...caminoError, + status: HTTP_STATUS.INTERNAL_SERVER_ERROR, + })), + Match.whenOr('Seuls les super peuvent accéder à ces données', () => ({ + ...caminoError, + status: HTTP_STATUS.FORBIDDEN, + })), + Match.exhaustive + ) + ) + ) + export const titresAdministrations = (_pool: Pool) => async (req: CaminoRequest, res: CustomResponse<CommonTitreAdministration[]>): Promise<void> => { diff --git a/packages/api/src/database/queries/titres-utilisateurs.queries.ts b/packages/api/src/database/queries/titres-utilisateurs.queries.ts index 773f8806665642f865a6185c725e5c7a8bfafd4c..2f8d32faa6ddf4b9c79fcb5e8785112b09e4a0ca 100644 --- a/packages/api/src/database/queries/titres-utilisateurs.queries.ts +++ b/packages/api/src/database/queries/titres-utilisateurs.queries.ts @@ -1,13 +1,50 @@ import { sql } from '@pgtyped/runtime' import { EffectDbQueryAndValidateErrors, Redefine, effectDbQueryAndValidate } from '../../pg-database' -import { IGetTitreUtilisateurDbQuery } from './titres-utilisateurs.queries.types' +import { IGetTitresWithBrouillonsDbQuery, IGetTitreUtilisateurDbQuery } from './titres-utilisateurs.queries.types' import { isNotNullNorUndefinedNorEmpty } from 'camino-common/src/typescript-tools' import { Pool } from 'pg' import { z } from 'zod' import { TitreId, titreIdValidator } from 'camino-common/src/validators/titres' -import { UtilisateurId, utilisateurIdValidator } from 'camino-common/src/roles' +import { isSuper, User, UtilisateurId, utilisateurIdValidator } from 'camino-common/src/roles' import { Effect, pipe } from 'effect' import { CaminoError } from 'camino-common/src/zod-tools' +import { SuperTitre, superTitreValidator } from 'camino-common/src/titres' +export const accesSuperSeulementError = 'Seuls les super peuvent accéder à ces données' as const + +export type GetTitresWithBrouillonsErrors = EffectDbQueryAndValidateErrors | typeof accesSuperSeulementError +export const getTitresWithBrouillons = (pool: Pool, user: User): Effect.Effect<SuperTitre[], CaminoError<GetTitresWithBrouillonsErrors>> => + Effect.Do.pipe( + Effect.filterOrFail( + () => isSuper(user), + () => ({ message: accesSuperSeulementError }) + ), + Effect.flatMap(() => effectDbQueryAndValidate(getTitresWithBrouillonsDb, {}, pool, superTitreValidator)) + ) + +const getTitresWithBrouillonsDb = sql<Redefine<IGetTitresWithBrouillonsDbQuery, {}, z.infer<typeof superTitreValidator>>>` +select + t.nom as titre_nom, + t.slug as titre_slug, + t.type_id as titre_type_id, + t.titre_statut_id, + td.type_id as demarche_type_id, + td.slug as demarche_slug, + te.type_id as etape_type_id, + te.slug as etape_slug, + te.date as etape_date +from + titres t + join titres_demarches td on td.titre_id=t.id + join titres_etapes te on te.titre_demarche_id=td.id + +where + t.archive is false + and td.archive is false + and te.archive is false + and te.is_brouillon + +order by te.date asc +` export const getTitreUtilisateur = (pool: Pool, titreId: TitreId, userId: UtilisateurId): Effect.Effect<boolean, CaminoError<EffectDbQueryAndValidateErrors>> => pipe( diff --git a/packages/api/src/database/queries/titres-utilisateurs.queries.types.ts b/packages/api/src/database/queries/titres-utilisateurs.queries.types.ts index 772aa528ee7ecfd0dee79ebaaacc8047c6f4f7c6..991b82f340b50510e770cb0b6b54e80d741de452 100644 --- a/packages/api/src/database/queries/titres-utilisateurs.queries.types.ts +++ b/packages/api/src/database/queries/titres-utilisateurs.queries.types.ts @@ -1,5 +1,27 @@ /** Types generated for queries found in "src/database/queries/titres-utilisateurs.queries.ts" */ +/** 'GetTitresWithBrouillonsDb' parameters type */ +export type IGetTitresWithBrouillonsDbParams = void; + +/** 'GetTitresWithBrouillonsDb' return type */ +export interface IGetTitresWithBrouillonsDbResult { + demarche_slug: string | null; + demarche_type_id: string; + etape_date: string; + etape_slug: string | null; + etape_type_id: string; + titre_nom: string; + titre_slug: string; + titre_statut_id: string; + titre_type_id: string; +} + +/** 'GetTitresWithBrouillonsDb' query type */ +export interface IGetTitresWithBrouillonsDbQuery { + params: IGetTitresWithBrouillonsDbParams; + result: IGetTitresWithBrouillonsDbResult; +} + /** 'GetTitreUtilisateurDb' parameters type */ export interface IGetTitreUtilisateurDbParams { titreId: string; diff --git a/packages/api/src/server/rest.ts b/packages/api/src/server/rest.ts index a2ade5e807c5141f80455669d8c58dff324249dd..6c46c9ee09f0756b7bbcacc4ac1273446c3aeeda 100644 --- a/packages/api/src/server/rest.ts +++ b/packages/api/src/server/rest.ts @@ -7,7 +7,7 @@ import { inspect } from 'node:util' import { activites, demarches, entreprises, titre, titres, travaux } from '../api/rest/index' import { NewDownload, avisDocumentDownload, etapeDocumentDownload, etapeTelecharger, streamLargeObjectInResponse } from '../api/rest/fichiers' -import { getTitreLiaisons, postTitreLiaisons, removeTitre, titresAdministrations, updateTitre, utilisateurTitreAbonner, getTitre, getUtilisateurTitreAbonner } from '../api/rest/titres' +import { getTitreLiaisons, postTitreLiaisons, removeTitre, titresAdministrations, updateTitre, utilisateurTitreAbonner, getTitre, getUtilisateurTitreAbonner, titresSuper } from '../api/rest/titres' import { creerEntreprise, fiscalite, @@ -195,6 +195,7 @@ const restRouteImplementations: Readonly<{ [key in CaminoRestRoute]: Transform<k '/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'] }, '/rest/titresAdministrations': { getCall: titresAdministrations, ...CaminoRestRoutes['/rest/titresAdministrations'] }, + '/rest/titresSuper': { newGetCall: titresSuper, ...CaminoRestRoutes['/rest/titresSuper'] }, '/rest/statistiques/minerauxMetauxMetropole': { getCall: getMinerauxMetauxMetropolesStats, ...CaminoRestRoutes['/rest/statistiques/minerauxMetauxMetropole'] }, // UNTESTED YET '/rest/statistiques/guyane': { getCall: getGuyaneStats, ...CaminoRestRoutes['/rest/statistiques/guyane'] }, '/rest/statistiques/guyane/:annee': { getCall: getGuyaneStats, ...CaminoRestRoutes['/rest/statistiques/guyane/:annee'] }, diff --git a/packages/common/src/rest.ts b/packages/common/src/rest.ts index 390e55740853e5c2858b414a6e4dfdb25e71e440..4b7ff3fb344973e06110dc6c6fde9a321a297c55 100644 --- a/packages/common/src/rest.ts +++ b/packages/common/src/rest.ts @@ -22,6 +22,7 @@ import { qgisTokenRestValidator, utilisateurToEdit, utilisateursSearchParamsVali import { editableTitreValidator, getDemarcheByIdOrSlugValidator, + superTitreValidator, titreAdministrationValidator, titreDemandeOutputValidator, titreDemandeValidator, @@ -97,6 +98,7 @@ const IDS = [ '/rest/titres/:titreId', '/rest/titres/:titreId/abonne', '/rest/titresAdministrations', + '/rest/titresSuper', '/rest/titres/:id/titreLiaisons', '/rest/demarches', '/rest/demarches/:demarcheIdOrSlug', @@ -171,6 +173,7 @@ export const CaminoRestRoutes = { '/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() } }, '/rest/titresAdministrations': { params: noParamsValidator, get: { output: z.array(titreAdministrationValidator) } }, + '/rest/titresSuper': { params: noParamsValidator, newGet: { output: z.array(superTitreValidator) } }, '/rest/titres/:id/titreLiaisons': { params: z.object({ id: titreIdValidator }), newGet: { output: titreLinksValidator }, newPost: { input: z.array(titreIdValidator), output: titreLinksValidator } }, '/rest/demarches': { params: noParamsValidator, newPost: { input: demarcheCreationInputValidator, output: demarcheCreationOutputValidator } }, '/rest/demarches/:demarcheIdOrSlug': { params: z.object({ demarcheIdOrSlug: demarcheIdOrSlugValidator }), newGet: { output: getDemarcheByIdOrSlugValidator }, delete: true }, diff --git a/packages/common/src/titres.ts b/packages/common/src/titres.ts index 234907f957f8497efa896072a8504c657514eb07..de3e3a45c0b3773034c82d3a16e10587327632bd 100644 --- a/packages/common/src/titres.ts +++ b/packages/common/src/titres.ts @@ -12,7 +12,7 @@ import { TitreId, titreIdValidator, titreSlugValidator } from './validators/titr import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty } from './typescript-tools' import { EntrepriseId, entrepriseIdValidator } from './entreprise' import { isFondamentalesStatutOk } from './static/etapesStatuts' -import { ETAPE_IS_NOT_BROUILLON, etapeIdValidator } from './etape' +import { ETAPE_IS_NOT_BROUILLON, etapeIdValidator, etapeSlugValidator } from './etape' import { isEntrepriseOrBureauDEtude, User } from './roles' const commonTitreValidator = z.object({ @@ -81,6 +81,19 @@ export const editableTitreValidator = commonTitreValidator.pick({ references: true, }) +export const superTitreValidator = z.object({ + titre_nom: z.string(), + titre_slug: titreSlugValidator, + titre_type_id: titreTypeIdValidator, + titre_statut_id: titreStatutIdValidator, + demarche_type_id: demarcheTypeIdValidator, + demarche_slug: demarcheSlugValidator, + etape_type_id: etapeTypeIdValidator, + etape_slug: etapeSlugValidator, + etape_date: caminoDateValidator, +}) +export type SuperTitre = z.infer<typeof superTitreValidator> + export const titreAdministrationValidator = commonTitreValidator.omit({ administrations_locales: true }).extend({ derniereEtape: z.object({ etapeTypeId: etapeTypeIdValidator, date: caminoDateValidator }).nullable(), enAttenteDeAdministration: z.boolean(), diff --git a/packages/ui/src/components/dashboard.tsx b/packages/ui/src/components/dashboard.tsx index f90c78c8f507f5a788791c58fd84fb171989d12c..727b6bbef962b83f4f7848d14940f09289bd3ef4 100644 --- a/packages/ui/src/components/dashboard.tsx +++ b/packages/ui/src/components/dashboard.tsx @@ -1,7 +1,7 @@ import { FunctionalComponent, defineAsyncComponent, defineComponent, inject, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { dashboardApiClient } from './dashboard/dashboard-api-client' -import { User, isAdministration, isEntrepriseOrBureauDEtude } from 'camino-common/src/roles' +import { User, isAdministration, isEntrepriseOrBureauDEtude, isSuper } from 'camino-common/src/roles' import { entreprisesKey, userKey } from '@/moi' import { Entreprise } from 'camino-common/src/entreprise' @@ -13,7 +13,7 @@ export const Dashboard = defineComponent({ const entreprises = inject(entreprisesKey, ref([])) onMounted(async () => { - if (!isEntrepriseOrBureauDEtude(user) && !isAdministration(user)) { + if (!isEntrepriseOrBureauDEtude(user) && !isAdministration(user) && !isSuper(user)) { router.replace({ name: 'titres' }) } }) @@ -40,6 +40,14 @@ const PureDashboard: FunctionalComponent<{ user: User; entreprises: Entreprise[] }) return <PureAdministrationDashboard apiClient={dashboardApiClient} user={props.user} entreprises={props.entreprises} /> + } else if (isSuper(props.user)) { + const PureSuperDashboard = defineAsyncComponent(async () => { + const { PureSuperDashboard } = await import('@/components/dashboard/pure-super-dashboard') + + return PureSuperDashboard + }) + + return <PureSuperDashboard apiClient={dashboardApiClient} user={props.user} /> } return null diff --git a/packages/ui/src/components/dashboard/dashboard-api-client.ts b/packages/ui/src/components/dashboard/dashboard-api-client.ts index a8fb2a8be49a49b4e53ab5f16814c49722073111..e94ba5b7ea2f4cca7776d245ec85735651e59132 100644 --- a/packages/ui/src/components/dashboard/dashboard-api-client.ts +++ b/packages/ui/src/components/dashboard/dashboard-api-client.ts @@ -1,14 +1,16 @@ import { apiGraphQLFetch } from '@/api/_client' -import { getWithJson } from '@/api/client-rest' +import { getWithJson, newGetWithJson } from '@/api/client-rest' import { EntrepriseId, TitreEntreprise } from 'camino-common/src/entreprise' import { StatistiquesDGTM } from 'camino-common/src/statistiques' -import { CommonTitreAdministration } from 'camino-common/src/titres' +import { CommonTitreAdministration, SuperTitre } from 'camino-common/src/titres' +import { CaminoError } from 'camino-common/src/zod-tools' import gql from 'graphql-tag' export interface DashboardApiClient { getAdministrationTitres: () => Promise<CommonTitreAdministration[]> getDgtmStats: () => Promise<StatistiquesDGTM> getEntreprisesTitres: (entreprisesIds: EntrepriseId[]) => Promise<TitreEntreprise[]> + getTitresAvecEtapeEnBrouillon: () => Promise<SuperTitre[] | CaminoError<string>> } const titres = apiGraphQLFetch(gql` query Titres( @@ -77,4 +79,5 @@ export const dashboardApiClient: DashboardApiClient = { getEntreprisesTitres: async (entreprisesIds: EntrepriseId[]) => { return (await titres({ entreprisesIds })).elements }, + getTitresAvecEtapeEnBrouillon: async (): Promise<SuperTitre[] | CaminoError<string>> => newGetWithJson('/rest/titresSuper', {}), } diff --git a/packages/ui/src/components/dashboard/pure-super-dashboard.stories.tsx b/packages/ui/src/components/dashboard/pure-super-dashboard.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b665f212f24485ce061bdff977117b96304e33d4 --- /dev/null +++ b/packages/ui/src/components/dashboard/pure-super-dashboard.stories.tsx @@ -0,0 +1,48 @@ +import { Meta, StoryFn } from '@storybook/vue3' +import { testBlankUser } from 'camino-common/src/tests-utils' +import { titreSlugValidator } from 'camino-common/src/validators/titres' +import { PureSuperDashboard } from './pure-super-dashboard' +import { SuperTitre } from 'camino-common/src/titres' +import { demarcheSlugValidator } from 'camino-common/src/demarche' +import { caminoDateValidator } from 'camino-common/src/date' +import { etapeSlugValidator } from 'camino-common/src/etape' + +const meta: Meta = { + title: 'Components/Dashboard/Super', + // @ts-ignore @storybook/vue3 n'aime pas les composants tsx + component: PureSuperDashboard, +} +export default meta + +const titres: SuperTitre[] = [ + { + titre_nom: 'Aachen', + titre_slug: titreSlugValidator.parse('m-cx-aachen-1810'), + titre_type_id: 'cxm', + titre_statut_id: 'ech', + demarche_slug: demarcheSlugValidator.parse('m-cx-aachen-1810-oct01'), + demarche_type_id: 'oct', + etape_date: caminoDateValidator.parse('1810-01-01'), + etape_slug: etapeSlugValidator.parse('m-cx-aachen-1810-oct01-mfr01'), + etape_type_id: 'mfr', + }, + { + titre_nom: 'Amadis 5', + titre_slug: titreSlugValidator.parse('m-ax-amadis-5-2022'), + titre_type_id: 'pxg', + titre_statut_id: 'val', + demarche_slug: demarcheSlugValidator.parse('m-ax-amadis-5-2022-oct01'), + demarche_type_id: 'oct', + etape_date: caminoDateValidator.parse('2022-01-01'), + etape_slug: etapeSlugValidator.parse('m-ax-amadis-5-2022-oct01-mfr01'), + etape_type_id: 'asc', + }, +] + +export const TableauVide: StoryFn = () => <PureSuperDashboard user={{ role: 'super', ...testBlankUser }} apiClient={{ getTitresAvecEtapeEnBrouillon: () => Promise.resolve([]) }} /> +export const TableauPlein: StoryFn = () => <PureSuperDashboard user={{ role: 'super', ...testBlankUser }} apiClient={{ getTitresAvecEtapeEnBrouillon: () => Promise.resolve(titres) }} /> +export const Loading: StoryFn = () => <PureSuperDashboard user={{ role: 'super', ...testBlankUser }} apiClient={{ getTitresAvecEtapeEnBrouillon: () => new Promise<SuperTitre[]>(_resolve => {}) }} /> + +export const WithError: StoryFn = () => ( + <PureSuperDashboard user={{ role: 'super', ...testBlankUser }} apiClient={{ getTitresAvecEtapeEnBrouillon: () => Promise.resolve({ message: 'Une erreur' }) }} /> +) diff --git a/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_Loading.html b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_Loading.html new file mode 100644 index 0000000000000000000000000000000000000000..f36d291d978bbe436b84eae1a655aeb528bf0d90 --- /dev/null +++ b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_Loading.html @@ -0,0 +1,16 @@ +<div> + <div class="fr-grid-row"> + <div class="fr-col-12 fr-col-md-6"> + <h1>Tableau de bord</h1> + </div> + <div class="fr-col-12 fr-col-md-6" style="display: flex; flex-direction: row; align-items: flex-start; justify-content: flex-end;"> + <!----> + <!----> + </div> + </div> + <div class="_top-level_3306d0" style="display: flex; justify-content: center;"> + <!----> + <!----> + <div class="_spinner_3306d0"></div> + </div> +</div> \ No newline at end of file diff --git a/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_TableauPlein.html b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_TableauPlein.html new file mode 100644 index 0000000000000000000000000000000000000000..810d741511555a9338b3631c05b060457c515810 --- /dev/null +++ b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_TableauPlein.html @@ -0,0 +1,63 @@ +<div> + <div class="fr-grid-row"> + <div class="fr-col-12 fr-col-md-6"> + <h1>Tableau de bord</h1> + </div> + <div class="fr-col-12 fr-col-md-6" style="display: flex; flex-direction: row; align-items: flex-start; justify-content: flex-end;"> + <!----> + <!----> + </div> + </div> + <div> + <div class="fr-table fr-table--no-scroll" style="overflow: auto;"> + <div class="fr-table__wrapper" style="width: auto;"> + <div class="fr-table__container"> + <div class="fr-table__content"> + <table style="display: table; width: 100%;"> + <caption>Titres avec une étape en brouillon</caption> + <thead> + <tr> + <th scope="col">Nom</th> + <th scope="col">Type de démarche</th> + <th scope="col">Type de titre</th> + <th scope="col">-</th> + <th scope="col">Statut du titre</th> + <th scope="col">Étape en brouillon</th> + <th scope="col">Date de l'étape</th> + </tr> + </thead> + <tbody> + <tr> + <td><a class="fr-link" to="{name:titre,params:{id:m-cx-aachen-1810}}" type="primary"><span class="bold">Aachen</span></a></td> + <td><a href="/mocked-href" title="octroi" aria-label="octroi">Octroi</a></td> + <td><span class="small bold">Concession</span></td> + <td> + <p class="fr-tag fr-tag--md mono" title="Domaine minéraux et métaux" aria-label="Domaine minéraux et métaux" style="min-width: 2rem; background-color: var(--background-contrast-blue-ecume); color: black;">M</p> + </td> + <td> + <p style="z-index: unset; margin-bottom: 0px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="fr-badge fr-badge--md fr-badge--beige-gris-galet" title="échu" aria-label="échu">échu</p> + </td> + <td><span class="">Demande</span></td> + <td><span class="">1810-01-01</span></td> + </tr> + <tr> + <td><a class="fr-link" to="{name:titre,params:{id:m-ax-amadis-5-2022}}" type="primary"><span class="bold">Amadis 5</span></a></td> + <td><a href="/mocked-href" title="octroi" aria-label="octroi">Octroi</a></td> + <td><span class="small bold">Permis d'exploitation</span></td> + <td> + <p class="fr-tag fr-tag--md mono" title="Domaine géothermie" aria-label="Domaine géothermie" style="min-width: 2rem; background-color: var(--background-contrast-pink-tuile); color: black;">G</p> + </td> + <td> + <p style="z-index: unset; margin-bottom: 0px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;" class="fr-badge fr-badge--md fr-badge--green-bourgeon" title="valide" aria-label="valide">valide</p> + </td> + <td><span class="">Avis des services et commissions consultatives</span></td> + <td><span class="">2022-01-01</span></td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </div> + </div> +</div> \ No newline at end of file diff --git a/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_TableauVide.html b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_TableauVide.html new file mode 100644 index 0000000000000000000000000000000000000000..13237f4b92c5f0d9c21f93c0f0ce4756a01716ec --- /dev/null +++ b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_TableauVide.html @@ -0,0 +1,12 @@ +<div> + <div class="fr-grid-row"> + <div class="fr-col-12 fr-col-md-6"> + <h1>Tableau de bord</h1> + </div> + <div class="fr-col-12 fr-col-md-6" style="display: flex; flex-direction: row; align-items: flex-start; justify-content: flex-end;"> + <!----> + <!----> + </div> + </div> + <p>Aucune étape en brouillon</p> +</div> \ No newline at end of file diff --git a/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_WithError.html b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_WithError.html new file mode 100644 index 0000000000000000000000000000000000000000..55095b1725007d0a9d0f866836f48eb671adb69f --- /dev/null +++ b/packages/ui/src/components/dashboard/pure-super-dashboard.stories_snapshots_WithError.html @@ -0,0 +1,18 @@ +<div> + <div class="fr-grid-row"> + <div class="fr-col-12 fr-col-md-6"> + <h1>Tableau de bord</h1> + </div> + <div class="fr-col-12 fr-col-md-6" style="display: flex; flex-direction: row; align-items: flex-start; justify-content: flex-end;"> + <!----> + <!----> + </div> + </div> + <div class="" style="display: flex; justify-content: center;"> + <!----> + <div class="fr-alert fr-alert--error fr-alert--sm" role="alert"> + <p>Une erreur</p> + </div> + <!----> + </div> +</div> \ No newline at end of file diff --git a/packages/ui/src/components/dashboard/pure-super-dashboard.tsx b/packages/ui/src/components/dashboard/pure-super-dashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..34ded30e865874917e3eef9edb519d570adf4296 --- /dev/null +++ b/packages/ui/src/components/dashboard/pure-super-dashboard.tsx @@ -0,0 +1,101 @@ +import { defineComponent, onMounted, ref } from 'vue' +import { Column, TableSimple } from '../_ui/table-simple' +import { nomColumn, nomCell, statutCell, typeCell, domaineColumn, domaineCell } from '@/components/titres/table-utils' +import { SuperTitre } from 'camino-common/src/titres' +import { LoadingElement } from '@/components/_ui/functional-loader' +import { AsyncData } from '@/api/client-rest' +import { ComponentColumnData, JSXElementColumnData, TableRow, TextColumnData } from '../_ui/table' +import { DashboardApiClient } from './dashboard-api-client' +import { User } from 'camino-common/src/roles' +import { PageContentHeader } from '../_common/page-header-content' +import { isNotNullNorUndefinedNorEmpty } from 'camino-common/src/typescript-tools' +import { CaminoRouterLink } from '@/router/camino-router-link' +import { DemarchesTypes } from 'camino-common/src/static/demarchesTypes' +import { capitalize } from 'camino-common/src/strings' +import { getDomaineId } from 'camino-common/src/static/titresTypes' +import { EtapesTypes } from 'camino-common/src/static/etapesTypes' + +interface Props { + apiClient: Pick<DashboardApiClient, 'getTitresAvecEtapeEnBrouillon'> + user: User +} +const columns = [ + nomColumn, + { id: 'demarche_type', contentTitle: 'Type de démarche' }, + { id: 'titre_type', contentTitle: 'Type de titre' }, + domaineColumn, + { id: 'titre_statut', contentTitle: 'Statut du titre' }, + { id: 'etape_brouillon', contentTitle: 'Étape en brouillon' }, + { id: 'etape_date', contentTitle: "Date de l'étape" }, +] as const satisfies Column[] +type ColumnId = (typeof columns)[number]['id'] + +export const PureSuperDashboard = defineComponent<Props>(props => { + const data = ref<AsyncData<TableRow<ColumnId>[]>>({ status: 'LOADING' }) + + type Columns = (typeof columns)[number]['id'] + + const titresLignesBuild = (titres: SuperTitre[]): TableRow<Columns>[] => { + return titres.map(titre => { + const columns: { + [key in Columns]: JSXElementColumnData | ComponentColumnData | TextColumnData + } = { + nom: nomCell({ nom: titre.titre_nom }), + titre_type: typeCell(titre.titre_type_id), + demarche_type: { + type: 'jsx', + jsxElement: ( + <CaminoRouterLink to={{ name: 'demarche', params: { demarcheId: titre.demarche_slug } }} isDisabled={false} title={DemarchesTypes[titre.demarche_type_id].nom}> + {capitalize(DemarchesTypes[titre.demarche_type_id].nom)} + </CaminoRouterLink> + ), + value: titre.demarche_type_id, + }, + domaine: domaineCell({ domaineId: getDomaineId(titre.titre_type_id) }), + titre_statut: statutCell(titre), + etape_brouillon: { type: 'text', value: capitalize(EtapesTypes[titre.etape_type_id].nom) }, + etape_date: { type: 'text', value: titre.etape_date }, + } + + return { + id: titre.titre_slug, + link: { name: 'titre', params: { id: titre.titre_slug } }, + columns, + } + }) + } + + onMounted(async () => { + const titres = await props.apiClient.getTitresAvecEtapeEnBrouillon() + if ('message' in titres) { + data.value = { + status: 'NEW_ERROR', + error: titres, + } + } else { + data.value = { + status: 'LOADED', + value: titresLignesBuild(titres), + } + } + }) + + return () => ( + <div> + <PageContentHeader nom="Tableau de bord" download={null} renderButton={null} /> + <LoadingElement + data={data.value} + renderItem={item => { + if (isNotNullNorUndefinedNorEmpty(item)) { + return <TableSimple caption={{ value: 'Titres avec une étape en brouillon', visible: true }} columns={columns} rows={item} /> + } + + return <p>Aucune étape en brouillon</p> + }} + /> + </div> + ) +}) + +// @ts-ignore waiting for https://github.com/vuejs/core/issues/7833 +PureSuperDashboard.props = ['apiClient', 'user'] diff --git a/packages/ui/src/components/page/header.stories_snapshots_CanOpenAnnuaire.html b/packages/ui/src/components/page/header.stories_snapshots_CanOpenAnnuaire.html index 3c4e6a98a9974ba41551902ee45033eb5edf8617..14bd9b26248259e6ea86ad62b65568aa875fdac0 100644 --- a/packages/ui/src/components/page/header.stories_snapshots_CanOpenAnnuaire.html +++ b/packages/ui/src/components/page/header.stories_snapshots_CanOpenAnnuaire.html @@ -40,17 +40,18 @@ </div> <nav class="fr-nav" id="headerNavigationId" role="navigation" aria-label="Menu principal"> <ul class="fr-nav__list"> + <li class="fr-nav__item"><a class="fr-nav__link" to="{name:dashboard}" target="_self" type="primary">Tableau de bord</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:titres}" target="_self" type="primary">Titres et autorisations</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:demarches}" target="_self" type="primary">Démarches</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:travaux}" target="_self" type="primary">Travaux</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:activites}" target="_self" type="primary">Activités</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:statistiques}" target="_self" type="primary">Statistiques</a></li> - <li class="fr-nav__item"><button class="fr-nav__btn" aria-expanded="false" aria-controls="collapse-5" aria-current="true">Annuaire</button> - <div class="fr-collapse fr-menu" id="collapse-5"> + <li class="fr-nav__item"><button class="fr-nav__btn" aria-expanded="false" aria-controls="collapse-6" aria-current="true">Annuaire</button> + <div class="fr-collapse fr-menu" id="collapse-6"> <ul class="fr-menu__list"> - <li><a class="fr-nav__link" to="{name:entreprises}" target="_self" id="nav-5-0" type="primary">Entreprises</a></li> - <li><a class="fr-nav__link" to="{name:administrations}" target="_self" id="nav-5-1" type="primary">Administrations</a></li> - <li><a class="fr-nav__link" to="{name:utilisateurs}" target="_self" id="nav-5-2" type="primary">Utilisateurs</a></li> + <li><a class="fr-nav__link" to="{name:entreprises}" target="_self" id="nav-6-0" type="primary">Entreprises</a></li> + <li><a class="fr-nav__link" to="{name:administrations}" target="_self" id="nav-6-1" type="primary">Administrations</a></li> + <li><a class="fr-nav__link" to="{name:utilisateurs}" target="_self" id="nav-6-2" type="primary">Utilisateurs</a></li> </ul> </div> </li> diff --git a/packages/ui/src/components/page/header.stories_snapshots_Super.html b/packages/ui/src/components/page/header.stories_snapshots_Super.html index 3c4e6a98a9974ba41551902ee45033eb5edf8617..14bd9b26248259e6ea86ad62b65568aa875fdac0 100644 --- a/packages/ui/src/components/page/header.stories_snapshots_Super.html +++ b/packages/ui/src/components/page/header.stories_snapshots_Super.html @@ -40,17 +40,18 @@ </div> <nav class="fr-nav" id="headerNavigationId" role="navigation" aria-label="Menu principal"> <ul class="fr-nav__list"> + <li class="fr-nav__item"><a class="fr-nav__link" to="{name:dashboard}" target="_self" type="primary">Tableau de bord</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:titres}" target="_self" type="primary">Titres et autorisations</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:demarches}" target="_self" type="primary">Démarches</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:travaux}" target="_self" type="primary">Travaux</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:activites}" target="_self" type="primary">Activités</a></li> <li class="fr-nav__item"><a class="fr-nav__link" to="{name:statistiques}" target="_self" type="primary">Statistiques</a></li> - <li class="fr-nav__item"><button class="fr-nav__btn" aria-expanded="false" aria-controls="collapse-5" aria-current="true">Annuaire</button> - <div class="fr-collapse fr-menu" id="collapse-5"> + <li class="fr-nav__item"><button class="fr-nav__btn" aria-expanded="false" aria-controls="collapse-6" aria-current="true">Annuaire</button> + <div class="fr-collapse fr-menu" id="collapse-6"> <ul class="fr-menu__list"> - <li><a class="fr-nav__link" to="{name:entreprises}" target="_self" id="nav-5-0" type="primary">Entreprises</a></li> - <li><a class="fr-nav__link" to="{name:administrations}" target="_self" id="nav-5-1" type="primary">Administrations</a></li> - <li><a class="fr-nav__link" to="{name:utilisateurs}" target="_self" id="nav-5-2" type="primary">Utilisateurs</a></li> + <li><a class="fr-nav__link" to="{name:entreprises}" target="_self" id="nav-6-0" type="primary">Entreprises</a></li> + <li><a class="fr-nav__link" to="{name:administrations}" target="_self" id="nav-6-1" type="primary">Administrations</a></li> + <li><a class="fr-nav__link" to="{name:utilisateurs}" target="_self" id="nav-6-2" type="primary">Utilisateurs</a></li> </ul> </div> </li> diff --git a/packages/ui/src/components/page/menu.ts b/packages/ui/src/components/page/menu.ts index c064da0b1fb015fbb39e870a444e0c5364e865bc..9f26161f324acda459b7e8687db3539b9181a95f 100644 --- a/packages/ui/src/components/page/menu.ts +++ b/packages/ui/src/components/page/menu.ts @@ -24,7 +24,7 @@ export const linksByRole = (user: User): Record<Role, (Link | Annuaire)[]> => { const linkActivites: Link[] = canReadActivites(user) ? [links.ACTIVITES] : [] return { - super: [links.TITRES_ET_AUTORISATIONS, links.DEMARCHES, links.TRAVAUX, ...linkActivites, links.STATISTIQUES, ANNUAIRE, links.JOURNAUX], + super: [links.TABLEAU_DE_BORD, links.TITRES_ET_AUTORISATIONS, links.DEMARCHES, links.TRAVAUX, ...linkActivites, links.STATISTIQUES, ANNUAIRE, links.JOURNAUX], admin: [links.TABLEAU_DE_BORD, links.TITRES_ET_AUTORISATIONS, links.DEMARCHES, links.TRAVAUX, ...linkActivites, links.STATISTIQUES, ANNUAIRE], editeur: [links.TABLEAU_DE_BORD, links.TITRES_ET_AUTORISATIONS, links.DEMARCHES, links.TRAVAUX, ...linkActivites, links.STATISTIQUES, ANNUAIRE], lecteur: [links.TITRES_ET_AUTORISATIONS, links.DEMARCHES, links.TRAVAUX, links.STATISTIQUES, ANNUAIRE], diff --git a/packages/ui/src/components/page/plan.stories_snapshots_Super.html b/packages/ui/src/components/page/plan.stories_snapshots_Super.html index 5bfa93a1044fce7a3bdf2dd3608ce01341391397..0d3b91c0c0236152affee523b263b6de9de767f5 100644 --- a/packages/ui/src/components/page/plan.stories_snapshots_Super.html +++ b/packages/ui/src/components/page/plan.stories_snapshots_Super.html @@ -1,6 +1,7 @@ <div> <h1>Plan du site</h1> <ul> + <li><a href="/mocked-href" title="Tableau de bord" aria-label="Tableau de bord">Tableau de bord</a></li> <li><a href="/mocked-href" title="Titres et autorisations" aria-label="Titres et autorisations">Titres et autorisations</a></li> <li><a href="/mocked-href" title="Démarches" aria-label="Démarches">Démarches</a></li> <li><a href="/mocked-href" title="Travaux" aria-label="Travaux">Travaux</a></li> diff --git a/packages/ui/src/components/titres/table-utils.ts b/packages/ui/src/components/titres/table-utils.ts index 9ee8af93698b73bdfa83c68e9945248dd0c0044c..2e90854a280721eb3b73b7f9045ce0f250fae552 100644 --- a/packages/ui/src/components/titres/table-utils.ts +++ b/packages/ui/src/components/titres/table-utils.ts @@ -23,7 +23,7 @@ export const nomColumn: Column<'nom'> = { id: 'nom', contentTitle: 'Nom', } -const domaineColumn: Column<'domaine'> = { +export const domaineColumn: Column<'domaine'> = { id: 'domaine', contentTitle: '', } @@ -123,7 +123,7 @@ export const titulairesCell = (titre: { titulaireIds?: EntrepriseId[] }, entrepr value: titre.titulaireIds?.map(id => entreprisesIndex[id] ?? '').join(', '), } } -const domaineCell = (titre: { domaineId: DomaineId }): ComponentColumnData => ({ +export const domaineCell = (titre: { domaineId: DomaineId }): ComponentColumnData => ({ type: 'component', component: markRaw(CaminoDomaine), props: { domaineId: titre.domaineId },