Skip to content

Commit 82aff8f

Browse files
yvalentindolemoine
andauthored
feat: add projects catalog (#915)
Co-authored-by: dlemoine <contact@dorianlemoine.fr>
1 parent 818b957 commit 82aff8f

32 files changed

+713
-161
lines changed

apps/web/plugin/SEO/sitemap.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import dotenv from 'dotenv'
33
import { readFileSync, writeFileSync } from 'fs'
44
import { resolve } from 'path'
55
import { ChangeFreq, type PathSettings, Priority } from './type'
6-
import { ProgramType } from '@tee/data'
6+
import { ProgramType, Project } from '@tee/data'
7+
import { projects } from '@tee/data/static'
78

89
const specificPathSettings: PathSettings[] = [
910
{ path: '/', changeFreq: ChangeFreq.Weekly, priority: Priority.Highest },
@@ -80,12 +81,21 @@ function generateProgramSitemap(): string | undefined {
8081
.join('\n')
8182
}
8283

84+
function generateProjectSitemap(): string | undefined {
85+
return projects
86+
.map((project: Project) => {
87+
return generateOnePathXML('/projets-entreprise/' + project.slug, ChangeFreq.Monthly, Priority.MidHigh)
88+
})
89+
.join('\n')
90+
}
91+
8392
function generateSitemapXML(): string {
8493
const staticElements = generateStaticSitemap()
8594
const programElements = generateProgramSitemap()
95+
const projectElements = generateProjectSitemap()
8696

8797
return `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
88-
${staticElements + '\n' + programElements}
98+
${staticElements + '\n' + programElements + '\n' + projectElements}
8999
</urlset>`
90100
}
91101

apps/web/src/assets/main.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
@import "scss/grid";
1111
@import "scss/highlight";
1212
@import "scss/input";
13+
@import "scss/nav";
1314
@import "scss/radius";
1415
@import "scss/router";
1516
@import "scss/searchBar";

apps/web/src/assets/scss/nav.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@use 'setting';
2+
3+
.tee-header-menu-sub-navigation {
4+
button.fr-nav__btn {
5+
color: setting.$blue-france;
6+
font-weight: 500;
7+
}
8+
}

apps/web/src/components.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ export {}
77
/* prettier-ignore */
88
declare module 'vue' {
99
export interface GlobalComponents {
10+
CatalogBanner: typeof import('./components/catalog/element/CatalogBanner.vue')['default']
1011
CatalogList: typeof import('./components/catalog/CatalogList.vue')['default']
12+
CatalogPrograms: typeof import('./components/catalog/CatalogPrograms.vue')['default']
13+
CatalogProjects: typeof import('./components/catalog/CatalogProjects.vue')['default']
1114
ContactButton: typeof import('./components/contact/ContactButton.vue')['default']
1215
ContactHelp: typeof import('./components/contact/ContactHelp.vue')['default']
1316
ContactMail: typeof import('./components/contact/ContactMail.vue')['default']
@@ -18,6 +21,7 @@ declare module 'vue' {
1821
DsfrButton: typeof import('@gouvminint/vue-dsfr')['DsfrButton']
1922
DsfrCheckboxSet: typeof import('@gouvminint/vue-dsfr')['DsfrCheckboxSet']
2023
DsfrHighlight: typeof import('@gouvminint/vue-dsfr')['DsfrHighlight']
24+
DsfrNavigation: typeof import('@gouvminint/vue-dsfr')['DsfrNavigation']
2125
DsfrSelect: typeof import('@gouvminint/vue-dsfr')['DsfrSelect']
2226
DsfrSideMenu: typeof import('@gouvminint/vue-dsfr')['DsfrSideMenu']
2327
DsfrTabContent: typeof import('@gouvminint/vue-dsfr')['DsfrTabContent']
@@ -62,10 +66,14 @@ declare module 'vue' {
6266
TeeButtonExternalLink: typeof import('./components/element/button/TeeButtonExternalLink.vue')['default']
6367
TeeButtonLink: typeof import('./components/element/button/TeeButtonLink.vue')['default']
6468
TeeCallout: typeof import('./components/element/TeeCallout.vue')['default']
69+
TeeCounterResult: typeof import('./components/element/TeeCounterResult.vue')['default']
6570
TeeCredits: typeof import('./components/TeeCredits.vue')['default']
6671
TeeCta: typeof import('./components/home/TeeCta.vue')['default']
6772
TeeDsfrBreadcrumb: typeof import('./components/element/TeeDsfrBreadcrumb.vue')['default']
6873
TeeDsfrButton: typeof import('./components/element/button/TeeDsfrButton.vue')['default']
74+
TeeDsfrHeader: typeof import('./components/header/TeeDsfrHeader.vue')['default']
75+
TeeDsfrHeaderMenuLinks: typeof import('./components/header/TeeDsfrHeaderMenuLinks.vue')['default']
76+
TeeDsfrNavigationMenu: typeof import('./components/header/TeeDsfrNavigationMenu.vue')['default']
6977
TeeDsfrSearchBar: typeof import('./components/element/TeeDsfrSearchBar.vue')['default']
7078
TeeDsfrTag: typeof import('./components/element/tag/TeeDsfrTag.vue')['default']
7179
TeeDsfrTags: typeof import('./components/element/tag/TeeDsfrTags.vue')['default']
@@ -78,13 +86,16 @@ declare module 'vue' {
7886
TeeHomeInfos: typeof import('./components/home/TeeHomeInfos.vue')['default']
7987
TeeHomeSteps: typeof import('./components/home/TeeHomeSteps.vue')['default']
8088
TeeMatomo: typeof import('./components/TeeMatomo.vue')['default']
89+
TeeNoResult: typeof import('./components/element/TeeNoResult.vue')['default']
8190
TeeObjectiveCard: typeof import('./components/element/TeeObjectiveCard.vue')['default']
8291
TeeQuestionnaire: typeof import('./components/questionnaire/TeeQuestionnaire.vue')['default']
8392
TeeQuestionnaireResult: typeof import('./components/questionnaire/TeeQuestionnaireResult.vue')['default']
8493
TeeSpinner: typeof import('./components/element/TeeSpinner.vue')['default']
8594
TeeTabs: typeof import('./components/element/TeeTabs.vue')['default']
8695
ThemeCard: typeof import('./components/objective/ThemeCard.vue')['default']
96+
ThemeFilter: typeof import('./components/theme/ThemeFilter.vue')['default']
8797
ThemeFiltersAndCard: typeof import('./components/questionnaire/result/list/ThemeFiltersAndCard.vue')['default']
98+
ThemeHeaderCard: typeof import('./components/theme/ThemeHeaderCard.vue')['default']
8899
ThemeProjectTag: typeof import('./components/objective/ThemeProjectTag.vue')['default']
89100
ThemeSelect: typeof import('./components/objective/ThemeSelect.vue')['default']
90101
TrackButton: typeof import('./components/track/form/TrackButton.vue')['default']

apps/web/src/components/TeeHeader.vue

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<DsfrHeader
2+
<TeeDsfrHeader
33
id="tee-header"
44
service-title="Transition écologique des entreprises"
55
service-description="Allier écologie avec économies !"
@@ -17,22 +17,32 @@
1717
// CONSOLE LOG TEMPLATE
1818
// console.log(`TeeHeader > FUNCTION_NAME > MSG_OR_VALUE :`)
1919
20-
import { DsfrHeader } from '@gouvminint/vue-dsfr'
2120
import { RouteName } from '@/types/routeType'
22-
import type { DsfrHeaderMenuLinkProps } from '@gouvminint/vue-dsfr/types/components/DsfrHeader/DsfrHeaderMenuLink.vue'
21+
import { TeeDsfrHeaderMenuLinkProps } from '@/components/header/TeeDsfrHeaderMenuLinks.vue'
2322
24-
const quickLinks: DsfrHeaderMenuLinkProps[] = [
23+
const quickLinks: TeeDsfrHeaderMenuLinkProps[] = [
2524
{
2625
label: 'Accueil',
2726
to: {
2827
name: RouteName.Homepage
2928
}
3029
},
3130
{
32-
label: 'Annuaire',
33-
to: {
34-
name: RouteName.CatalogPrograms
35-
}
31+
label: 'Catalogue',
32+
links: [
33+
{
34+
text: 'Catalogue des aides',
35+
to: {
36+
name: RouteName.CatalogPrograms
37+
}
38+
},
39+
{
40+
text: 'Catalogue des projets',
41+
to: {
42+
name: RouteName.CatalogProjects
43+
}
44+
}
45+
]
3646
}
3747
]
3848
</script>

apps/web/src/components/catalog/CatalogList.vue renamed to apps/web/src/components/catalog/CatalogPrograms.vue

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
11
<template>
2-
<!-- PAGE BANNER -->
3-
<TeeBanner
4-
class="fr-pt-4v fr-py-md-6w fr-text-center"
5-
:bg-color="Color.blueLight"
6-
>
7-
<template #title>
8-
<div class="fr-col-10 fr-col-lg-8">
9-
<h1 class="fr-text--blue-france">L'annuaire des aides publiques à la transition écologique</h1>
10-
</div>
11-
</template>
2+
<CatalogBanner>
3+
<template #title> Le catalogue des aides publiques à la transition écologique </template>
124
<template #description>
13-
<div class="fr-col-12 fr-col-lg-10 fr-col-xl-9 fr-px-1v">
14-
<p class="fr-text--md">
15-
Réalisez une recherche parmi les aides à la transition écologique des entreprises, proposées par l’ensemble des partenaires
16-
publics : ADEME, Bpifrance, CCI, CMA, etc.
17-
</p>
18-
</div>
5+
Réalisez une recherche parmi les aides à la transition écologique des entreprises, proposées par l’ensemble des partenaires publics :
6+
ADEME, Bpifrance, CCI, CMA, etc.
197
</template>
20-
</TeeBanner>
8+
</CatalogBanner>
219

22-
<!-- PROGRAMS AS LIST OF CARDS -->
2310
<div class="fr-container--fluid fr-container--fluid--no-overflow fr-mt-6v">
2411
<div class="fr-grid-row fr-grid-row--center">
2512
<TeeSpinner
@@ -37,7 +24,7 @@
3724
<div class="fr-grid-row fr-grid-row--center">
3825
<div class="fr-container fr-m-0 fr-p-0 fr-pl-md-2v">
3926
<div class="fr-col-12 fr-col-md-10 fr-col-offset-md-2 fr-col-justify--left fr-my-3v">
40-
<ProgramFilterByTheme v-if="showThemeFilterComponent" />
27+
<ThemeFilter v-if="hasThemeFilter" />
4128
</div>
4229
</div>
4330
</div>
@@ -47,7 +34,7 @@
4734
>
4835
<div class="fr-container fr-m-0 fr-p-0 fr-px-md-2v fr-mt-3v">
4936
<div class="fr-col-12 fr-col-md-10 fr-col-offset-md-2">
50-
<TeeObjectiveCard
37+
<ThemeHeaderCard
5138
:objective="objective as Objective"
5239
radius-corner="tr"
5340
radius-size="2-5v"
@@ -77,10 +64,8 @@
7764
</template>
7865

7966
<script setup lang="ts">
80-
import ProgramFiltersAccordion from '@/components/program/list/filters/ProgramFiltersAccordion.vue'
81-
import ProgramFilterByTheme from '@/components/program/list/filters/ProgramFilterByTheme.vue'
8267
import { useProgramStore } from '@/stores/program'
83-
import { Color, Objective, type ProgramData, TrackId } from '@/types'
68+
import { Objective, type ProgramData, TrackId } from '@/types'
8469
import Matomo from '@/utils/matomo'
8570
import { Theme } from '@/utils/theme'
8671
import UsedTrack from '@/utils/track/usedTrack'
@@ -127,7 +112,7 @@ const showNoResultsComponent = computed(() => {
127112
return hasSpinner.value || hasError.value || !countPrograms.value
128113
})
129114
130-
const showThemeFilterComponent = computed(() => {
115+
const hasThemeFilter = computed(() => {
131116
return havePrograms.value && countPrograms.value > 1
132117
})
133118
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<template>
2+
<CatalogBanner>
3+
<template #title> Le catalogue des projets de transition écologique </template>
4+
<template #description> Accédez à la liste des projets de transition écologique destinées aux entreprises. </template>
5+
</CatalogBanner>
6+
7+
<div class="fr-container fr-mt-6v">
8+
<div
9+
class="fr-grid-row"
10+
:class="{ 'fr-grid-row--center': hasSpinner }"
11+
>
12+
<TeeSpinner
13+
v-if="hasSpinner"
14+
class="fr-grid-row--center"
15+
scale="6"
16+
/>
17+
<TeeError
18+
v-else-if="hasError"
19+
:mailto="Contact.email"
20+
:email="Contact.email"
21+
/>
22+
<div v-else>
23+
<div class="fr-col-12 fr-col-justify--left fr-mt-3v">
24+
<ThemeFilter />
25+
</div>
26+
<ThemeHeaderCard
27+
v-if="hasThemeCard"
28+
class="fr-col-12 fr-mt-3v"
29+
:objective="objective as Objective"
30+
radius-corner="tr"
31+
radius-size="2-5v"
32+
/>
33+
<div v-if="hasFilteredProjects">
34+
<div class="fr-col-12 fr-mt-3v">
35+
<h2 class="fr-text--bold fr-mb-0">Quel est votre projet ?</h2>
36+
</div>
37+
<div class="fr-col-12 fr-text--blue-france tee-font-style--italic fr-mt-3v">
38+
<TeeCounterResult :to-count="filteredProjects" />
39+
</div>
40+
<div class="fr-grid-row fr-grid-row--gutters fr-grid-row--left fr-mt-3v">
41+
<router-link
42+
v-for="project in filteredProjects"
43+
:id="project.slug"
44+
:key="project.id"
45+
:to="getRouteToProjectDetail(project)"
46+
class="fr-col-12 fr-col-sm-6 fr-col-md-6 fr-col-lg-4 no-outline"
47+
>
48+
<ProjectCard
49+
:project="project"
50+
class="fr-radius-a--1v fr-card--shadow"
51+
/>
52+
</router-link>
53+
</div>
54+
</div>
55+
<TeeNoResult
56+
v-else
57+
message="Aucun projet n'a pu être identifiée avec les critères choisis..."
58+
/>
59+
</div>
60+
</div>
61+
</div>
62+
</template>
63+
64+
<script setup lang="ts">
65+
import { useNavigationStore } from '@/stores/navigation'
66+
import { useProgramStore } from '@/stores/program'
67+
import { useProjectStore } from '@/stores/project'
68+
import { Objective, type ProgramData, Project as ProjectType, RouteName, TrackId } from '@/types'
69+
import Contact from '@/utils/contact'
70+
import Matomo from '@/utils/matomo'
71+
import { Project } from '@/utils/project/project'
72+
import { computed, onBeforeMount } from 'vue'
73+
import type { RouteLocationRaw } from 'vue-router'
74+
75+
const projectStore = useProjectStore()
76+
const programStore = useProgramStore()
77+
const navigationStore = useNavigationStore()
78+
79+
const projects = ref<ProjectType[]>()
80+
const programs = ref<ProgramData[]>()
81+
const hasError = ref<boolean>(false)
82+
83+
const objective = computed(() => {
84+
return programStore.hasObjectiveTypeSelected() ? (programStore.programFilters.objectiveTypeSelected as Objective) : ''
85+
})
86+
87+
const filteredProjects = Project.filter(projects, programs, objective)
88+
89+
const hasSpinner = computed(() => {
90+
return navigationStore.hasSpinner
91+
})
92+
93+
const hasThemeCard = computed(() => {
94+
return programStore.hasObjectiveTypeSelected() && !hasSpinner.value
95+
})
96+
97+
const hasFilteredProjects = computed(() => {
98+
return filteredProjects.value?.length
99+
})
100+
101+
const getRouteToProjectDetail = (project: ProjectType): RouteLocationRaw => {
102+
return {
103+
name: RouteName.CatalogProjectDetail,
104+
params: { projectSlug: project.slug }
105+
}
106+
}
107+
108+
onBeforeMount(async () => {
109+
navigationStore.hasSpinner = true
110+
const programResult = await programStore.programs
111+
const projectResult = await projectStore.projects
112+
if (programResult.isOk && projectResult.isOk) {
113+
programs.value = programResult.value
114+
projects.value = projectResult.value
115+
} else {
116+
hasError.value = true
117+
}
118+
119+
navigationStore.hasSpinner = false
120+
121+
// analytics / send event
122+
Matomo.sendEvent(TrackId.Results, 'show_results_catalog_projects')
123+
})
124+
</script>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<template>
2+
<TeeBanner
3+
class="fr-pt-4v fr-py-md-6w fr-text-center"
4+
:bg-color="bgColor ?? Color.blueLight"
5+
>
6+
<template #title>
7+
<div class="fr-col-10 fr-col-lg-8">
8+
<h1 class="fr-text--blue-france">
9+
<slot name="title" />
10+
</h1>
11+
</div>
12+
</template>
13+
<template #description>
14+
<div class="fr-col-12 fr-col-lg-10 fr-col-xl-9 fr-px-1v">
15+
<p class="fr-text--md">
16+
<slot name="description" />
17+
</p>
18+
</div>
19+
</template>
20+
</TeeBanner>
21+
</template>
22+
23+
<script lang="ts" setup>
24+
import { Color } from '@/types'
25+
26+
interface Props {
27+
bgColor?: Color
28+
}
29+
defineProps<Props>()
30+
</script>

apps/web/src/components/contact/ContactHelp.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,3 @@
2020
</div>
2121
</div>
2222
</template>
23-
24-
<script setup lang="ts">
25-
import ContactButton from '@/components/contact/ContactButton.vue'
26-
</script>

0 commit comments

Comments
 (0)