From 9e2a7b06a98fcd29da6b978d2076dd7713474b33 Mon Sep 17 00:00:00 2001 From: Marine Heckler Date: Fri, 17 May 2024 11:58:41 +0200 Subject: [PATCH] Ajout d'un annuaire des conseillers sur le site --- packages/site/app/actus/[id]/[slug]/utils.ts | 2 +- packages/site/app/actus/page.tsx | 2 +- packages/site/app/collectivites/utils.ts | 2 +- packages/site/app/faq/page.tsx | 2 +- packages/site/app/programme/Ressources.tsx | 5 + packages/site/app/programme/[uid]/utils.ts | 2 +- .../programme/annuaire/CarteConseiller.tsx | 128 ++++++++++++++++++ packages/site/app/programme/annuaire/page.tsx | 73 ++++++++++ packages/site/app/programme/annuaire/utils.ts | 55 ++++++++ packages/site/src/strapi/strapi.ts | 8 +- .../content-types/conseiller/schema.json | 60 ++++++++ .../api/conseiller/controllers/conseiller.ts | 7 + .../src/api/conseiller/routes/conseiller.ts | 7 + .../src/api/conseiller/services/conseiller.ts | 7 + strapi/types/generated/contentTypes.d.ts | 70 ++++++++++ 15 files changed, 423 insertions(+), 7 deletions(-) create mode 100644 packages/site/app/programme/annuaire/CarteConseiller.tsx create mode 100644 packages/site/app/programme/annuaire/page.tsx create mode 100644 packages/site/app/programme/annuaire/utils.ts create mode 100644 strapi/src/api/conseiller/content-types/conseiller/schema.json create mode 100644 strapi/src/api/conseiller/controllers/conseiller.ts create mode 100644 strapi/src/api/conseiller/routes/conseiller.ts create mode 100644 strapi/src/api/conseiller/services/conseiller.ts diff --git a/packages/site/app/actus/[id]/[slug]/utils.ts b/packages/site/app/actus/[id]/[slug]/utils.ts index e0f551a64f..834d1e9adb 100644 --- a/packages/site/app/actus/[id]/[slug]/utils.ts +++ b/packages/site/app/actus/[id]/[slug]/utils.ts @@ -59,7 +59,7 @@ export const getData = async (id: number) => { ]); if (data) { - const idList = await fetchCollection('actualites', [ + const {data: idList} = await fetchCollection('actualites', [ ['fields[0]', 'DateCreation'], ['fields[1]', 'createdAt'], ['fields[2]', 'Epingle'], diff --git a/packages/site/app/actus/page.tsx b/packages/site/app/actus/page.tsx index 1c17bca1b2..0525c12022 100644 --- a/packages/site/app/actus/page.tsx +++ b/packages/site/app/actus/page.tsx @@ -26,7 +26,7 @@ type ActuCard = { }; const getData = async () => { - const data = await fetchCollection('actualites', [ + const {data} = await fetchCollection('actualites', [ ['populate[0]', 'Couverture'], ['sort[0]', 'createdAt:desc'], ]); diff --git a/packages/site/app/collectivites/utils.ts b/packages/site/app/collectivites/utils.ts index 664714c21d..8fb4935e03 100644 --- a/packages/site/app/collectivites/utils.ts +++ b/packages/site/app/collectivites/utils.ts @@ -70,7 +70,7 @@ export const fetchCollectivite = async (code_siren_insee: string) => { }; export const getStrapiData = async (codeSirenInsee: string) => { - const data = await fetchCollection('collectivites', [ + const {data} = await fetchCollection('collectivites', [ ['filters[code_siren_insee]', `${codeSirenInsee}`], ['populate[0]', 'seo'], ['populate[1]', 'seo.metaImage'], diff --git a/packages/site/app/faq/page.tsx b/packages/site/app/faq/page.tsx index 0bdec99f9c..3626ab0c74 100644 --- a/packages/site/app/faq/page.tsx +++ b/packages/site/app/faq/page.tsx @@ -20,7 +20,7 @@ export type FaqData = { }; const getData = async () => { - const data = await fetchCollection('faqs'); + const {data} = await fetchCollection('faqs'); const formattedData = data ? sortByRank(data).map(d => ({ diff --git a/packages/site/app/programme/Ressources.tsx b/packages/site/app/programme/Ressources.tsx index 158878084e..926587f62d 100644 --- a/packages/site/app/programme/Ressources.tsx +++ b/packages/site/app/programme/Ressources.tsx @@ -14,6 +14,11 @@ const Ressources = ({description}: RessourcesProps) => { href: '/ressources', variant: 'outlined', }, + { + title: 'Annuaire des conseillers', + href: '/programme/annuaire', + variant: 'outlined', + }, { title: 'Lire les questions fréquentes', href: '/faq', diff --git a/packages/site/app/programme/[uid]/utils.ts b/packages/site/app/programme/[uid]/utils.ts index 3ce19d166d..57a8c8c1e4 100644 --- a/packages/site/app/programme/[uid]/utils.ts +++ b/packages/site/app/programme/[uid]/utils.ts @@ -11,7 +11,7 @@ import { import {StrapiItem} from 'src/strapi/StrapiItem'; export const getServiceStrapiData = async (uid: string) => { - const data = await fetchCollection('services', [ + const {data} = await fetchCollection('services', [ ['filters[uid]', `${uid}`], ['populate[0]', 'seo'], ['populate[1]', 'seo.metaImage'], diff --git a/packages/site/app/programme/annuaire/CarteConseiller.tsx b/packages/site/app/programme/annuaire/CarteConseiller.tsx new file mode 100644 index 0000000000..1c794de4e9 --- /dev/null +++ b/packages/site/app/programme/annuaire/CarteConseiller.tsx @@ -0,0 +1,128 @@ +'use client'; + +import {useState} from 'react'; +import {Icon, Tooltip, useCopyToClipboard} from '@tet/ui'; +import {StrapiItem} from 'src/strapi/StrapiItem'; +import {StrapiImage} from '@components/strapiImage/StrapiImage'; + +type CarteConseillerProps = { + prenom: string; + nom: string; + structure: string; + region: string; + ville: string; + email: string; + linkedin?: string; + site?: string; + photo?: StrapiItem; +}; + +const CarteConseiller = ({ + prenom, + nom, + structure, + region, + ville, + email, + linkedin, + site, + photo, +}: CarteConseillerProps) => { + const [displayCopyMsg, setDisplayCopyMsg] = useState(false); + const {copy} = useCopyToClipboard(); + + return ( +
+ {/* Image */} + {photo !== undefined ? ( + + ) : ( +
+ + {prenom.slice(0, 1)} + {nom.slice(0, 1)} + +
+ )} + + {/* Contenu */} +
+ {/* En-tête */} +
+ {/* Nom */} +
+ {prenom} {nom} +
+ + {/* Liens */} +
+ {!!linkedin && ( + + + + )} + {!!site && ( + + + + )} +
+
+ + {/* Détails */} +
+ {/* Structure */} +

{structure}

+ + {/* Région - Ville */} +

+ {region} - {ville} +

+ + {/* Email */} +
+ + { + copy(email); + setDisplayCopyMsg(true); + }} + onMouseLeave={() => setDisplayCopyMsg(false)} + /> + + + {email} + +
+
+
+
+ ); +}; + +export default CarteConseiller; diff --git a/packages/site/app/programme/annuaire/page.tsx b/packages/site/app/programme/annuaire/page.tsx new file mode 100644 index 0000000000..7cae9e832e --- /dev/null +++ b/packages/site/app/programme/annuaire/page.tsx @@ -0,0 +1,73 @@ +'use client'; + +import Section from '@components/sections/Section'; +import CarteConseiller from './CarteConseiller'; +import {Input, Pagination} from '@tet/ui'; +import {ConseillerType, getData} from './utils'; +import {useEffect, useState} from 'react'; + +const PAGINATION_LIMIT = 12; + +const Annuaire = () => { + const [selectedPage, setSelectPage] = useState(1); + const [searchInput, setSearchInput] = useState(''); + const [total, setTotal] = useState(0); + const [data, setData] = useState([]); + + const getConseillersData = async () => { + const {data, pagination} = await getData({ + page: selectedPage, + limit: PAGINATION_LIMIT, + search: searchInput, + }); + setData(data); + setSelectPage(pagination.start / PAGINATION_LIMIT + 1); + setTotal(pagination.total); + }; + + useEffect(() => { + getConseillersData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedPage, searchInput]); + + return ( +
+
+

+ Annuaire des conseillers-conseillères +

+ setSearchInput(evt.target.value)} + onSearch={() => {}} + type="search" + placeholder="Rechercher un nom, prénom, structure ou région" + /> +
+ +
+ {data.map(carte => ( + + ))} +
+ + {total > PAGINATION_LIMIT && ( +
+ { + setSelectPage(selectedPage); + document.getElementById('annuaire-header')?.scrollIntoView(); + }} + /> +
+ )} +
+ ); +}; + +export default Annuaire; diff --git a/packages/site/app/programme/annuaire/utils.ts b/packages/site/app/programme/annuaire/utils.ts new file mode 100644 index 0000000000..b03b1403a2 --- /dev/null +++ b/packages/site/app/programme/annuaire/utils.ts @@ -0,0 +1,55 @@ +import {StrapiItem} from 'src/strapi/StrapiItem'; +import {fetchCollection} from 'src/strapi/strapi'; + +export type ConseillerType = { + id: number; + prenom: string; + nom: string; + structure: string; + region: string; + ville: string; + email: string; + linkedin: string; + site: string; + photo: StrapiItem; +}; + +export const getData = async ({ + page, + limit, + search = '', +}: { + page: number; + limit: number; + search?: string; +}) => { + const conseillers = await fetchCollection('conseillers', [ + ['populate[0]', 'photo'], + ['filters[$or][0][prenom][$startsWithi]', search], + ['filters[$or][1][nom][$startsWithi]', search], + ['filters[$or][2][structure][$startsWithi]', search], + ['filters[$or][3][region][$startsWithi]', search], + ['sort[0]', 'nom:asc'], + ['pagination[start]', `${(page - 1) * limit}`], + ['pagination[limit]', `${limit}`], + ]); + + return { + data: !conseillers.data + ? [] + : (conseillers.data.map(d => ({ + id: d.id, + prenom: d.attributes.prenom as unknown as string, + nom: d.attributes.nom as unknown as string, + structure: d.attributes.structure as unknown as string, + region: d.attributes.region as unknown as string, + ville: d.attributes.ville as unknown as string, + email: d.attributes.email as unknown as string, + linkedin: d.attributes.linkedin as unknown as string, + site: d.attributes.site as unknown as string, + photo: + (d.attributes.photo.data as unknown as StrapiItem) ?? undefined, + })) as ConseillerType[]), + pagination: conseillers.meta.pagination, + }; +}; diff --git a/packages/site/src/strapi/strapi.ts b/packages/site/src/strapi/strapi.ts index 84f3f3b587..09ea5c548a 100644 --- a/packages/site/src/strapi/strapi.ts +++ b/packages/site/src/strapi/strapi.ts @@ -11,6 +11,7 @@ const headers = { type Collection = | 'actualites' | 'collectivites' + | 'conseillers' | 'faqs' | 'services' | 'temoignages'; @@ -26,7 +27,10 @@ type Single = export async function fetchCollection( path: Collection, params: [string, string][] = [['populate', '*']], -): Promise> { +): Promise<{ + data: Array; + meta: {pagination: {start: number; limit: number; total: number}}; +}> { const url = new URL(`${baseURL}/api/${path}`); params.forEach(p => url.searchParams.append(...p)); @@ -36,7 +40,7 @@ export async function fetchCollection( headers, }); const body = await response.json(); - return body['data']; + return body; } export const fetchSingle = async ( diff --git a/strapi/src/api/conseiller/content-types/conseiller/schema.json b/strapi/src/api/conseiller/content-types/conseiller/schema.json new file mode 100644 index 0000000000..4a5fd4ae40 --- /dev/null +++ b/strapi/src/api/conseiller/content-types/conseiller/schema.json @@ -0,0 +1,60 @@ +{ + "kind": "collectionType", + "collectionName": "conseillers", + "info": { + "singularName": "conseiller", + "pluralName": "conseillers", + "displayName": "Conseillers" + }, + "options": { + "draftAndPublish": true + }, + "pluginOptions": {}, + "attributes": { + "prenom": { + "type": "string", + "required": true, + "maxLength": 255 + }, + "nom": { + "type": "string", + "required": true, + "maxLength": 255 + }, + "structure": { + "type": "string", + "required": true, + "maxLength": 255 + }, + "region": { + "type": "string", + "required": true, + "maxLength": 255 + }, + "ville": { + "type": "string", + "required": true, + "maxLength": 255 + }, + "email": { + "type": "email", + "required": true, + "maxLength": 255, + "unique": true + }, + "linkedin": { + "type": "string", + "maxLength": 255 + }, + "site": { + "type": "string", + "required": false, + "maxLength": 255 + }, + "photo": { + "allowedTypes": ["images"], + "type": "media", + "multiple": false + } + } +} diff --git a/strapi/src/api/conseiller/controllers/conseiller.ts b/strapi/src/api/conseiller/controllers/conseiller.ts new file mode 100644 index 0000000000..afc2a7e10b --- /dev/null +++ b/strapi/src/api/conseiller/controllers/conseiller.ts @@ -0,0 +1,7 @@ +/** + * conseiller controller + */ + +import { factories } from '@strapi/strapi' + +export default factories.createCoreController('api::conseiller.conseiller'); diff --git a/strapi/src/api/conseiller/routes/conseiller.ts b/strapi/src/api/conseiller/routes/conseiller.ts new file mode 100644 index 0000000000..efd46ef277 --- /dev/null +++ b/strapi/src/api/conseiller/routes/conseiller.ts @@ -0,0 +1,7 @@ +/** + * conseiller router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::conseiller.conseiller'); diff --git a/strapi/src/api/conseiller/services/conseiller.ts b/strapi/src/api/conseiller/services/conseiller.ts new file mode 100644 index 0000000000..b918869468 --- /dev/null +++ b/strapi/src/api/conseiller/services/conseiller.ts @@ -0,0 +1,7 @@ +/** + * conseiller service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::conseiller.conseiller'); diff --git a/strapi/types/generated/contentTypes.d.ts b/strapi/types/generated/contentTypes.d.ts index 62aa53bf49..987a7891e8 100644 --- a/strapi/types/generated/contentTypes.d.ts +++ b/strapi/types/generated/contentTypes.d.ts @@ -824,6 +824,75 @@ export interface ApiCollectiviteCollectivite extends Schema.CollectionType { }; } +export interface ApiConseillerConseiller extends Schema.CollectionType { + collectionName: 'conseillers'; + info: { + singularName: 'conseiller'; + pluralName: 'conseillers'; + displayName: 'Conseillers'; + }; + options: { + draftAndPublish: true; + }; + attributes: { + prenom: Attribute.String & + Attribute.Required & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + nom: Attribute.String & + Attribute.Required & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + structure: Attribute.String & + Attribute.Required & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + region: Attribute.String & + Attribute.Required & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + ville: Attribute.String & + Attribute.Required & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + email: Attribute.Email & + Attribute.Required & + Attribute.Unique & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + linkedin: Attribute.String & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + site: Attribute.String & + Attribute.SetMinMaxLength<{ + maxLength: 255; + }>; + photo: Attribute.Media; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + publishedAt: Attribute.DateTime; + createdBy: Attribute.Relation< + 'api::conseiller.conseiller', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + updatedBy: Attribute.Relation< + 'api::conseiller.conseiller', + 'oneToOne', + 'admin::user' + > & + Attribute.Private; + }; +} + export interface ApiFaqFaq extends Schema.CollectionType { collectionName: 'faqs'; info: { @@ -1302,6 +1371,7 @@ declare module '@strapi/types' { 'plugin::users-permissions.user': PluginUsersPermissionsUser; 'api::actualite.actualite': ApiActualiteActualite; 'api::collectivite.collectivite': ApiCollectiviteCollectivite; + 'api::conseiller.conseiller': ApiConseillerConseiller; 'api::faq.faq': ApiFaqFaq; 'api::page-accueil.page-accueil': ApiPageAccueilPageAccueil; 'api::page-budget.page-budget': ApiPageBudgetPageBudget;