Skip to content

Commit f602537

Browse files
committed
feat: add new type field
1 parent 4daa689 commit f602537

3 files changed

Lines changed: 97 additions & 92 deletions

File tree

backend/src/api/blog/content-types/blog/schema.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@
8686
"type": "relation",
8787
"relation": "oneToOne",
8888
"target": "api::volunteer.volunteer"
89+
},
90+
"resource_type": {
91+
"type": "enumeration",
92+
"enum": [
93+
"podcast",
94+
"article",
95+
"rapport",
96+
"prise_de_position",
97+
"autre"
98+
]
99+
},
100+
"newsletter": {
101+
"type": "enumeration",
102+
"enum": [
103+
"techno_lucides",
104+
"vers_lautonomie"
105+
]
89106
}
90107
}
91108
}

backend/src/api/press-release/content-types/press-release/schema.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@
124124
"videos",
125125
"audios"
126126
]
127+
},
128+
"resource_type": {
129+
"type": "enumeration",
130+
"enum": [
131+
"podcast",
132+
"article",
133+
"rapport",
134+
"prise_de_position",
135+
"autre"
136+
]
137+
},
138+
"newsletter": {
139+
"type": "enumeration",
140+
"enum": [
141+
"techno_lucides",
142+
"vers_lautonomie"
143+
]
127144
}
128145
}
129146
}

frontend/src/app/[locale]/blog/blog.tsx

Lines changed: 63 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,27 @@ function getTagsAsStrings(tags: unknown): string[] {
2222
});
2323
}
2424

25-
function determineType(element: Record<string, any> | null, tagStrings: string[]): string {
26-
// Check tags first
27-
const types = ['podcast', 'article', 'rapport', 'prise de position', 'autre'];
28-
for (const t of tagStrings) {
29-
const lower = t.toLowerCase();
30-
if (types.includes(lower)) return lower;
31-
}
32-
// Check media_name (it sometimes contains the type, e.g. "Rapport")
33-
if (element?.media_name) {
34-
const lower = element.media_name.toLowerCase();
35-
if (types.includes(lower)) return lower;
36-
}
37-
// Check title for clues
38-
if (element?.title) {
39-
const lower = element.title.toLowerCase();
40-
if (lower.startsWith('podcast') || lower.startsWith('vidéo') || lower.startsWith('video')) return 'article';
41-
if (lower.startsWith('rapport')) return 'rapport';
42-
if (lower.startsWith('communiqué')) return 'article';
43-
}
44-
// Default
25+
// Fallback used only while a resource has no `resource_type` set in Strapi.
26+
// Once editors fill the field it becomes the source of truth (see transformBlogsData).
27+
function determineType(element: Record<string, any> | null): string {
28+
const title = (element?.title ?? '').toLowerCase();
29+
const media = (element?.media_name ?? '').toLowerCase();
30+
const url = (element?.article_link ?? '').toLowerCase();
31+
32+
if (media === 'rapport' || title.startsWith('rapport') || url.endsWith('.pdf')) return 'rapport';
33+
if (url.includes('/podcast') || url.includes('radiofrance') || title.startsWith('podcast')) return 'podcast';
34+
if (title.startsWith('vidéo') || title.startsWith('video') || title.startsWith('communiqué')) return 'article';
35+
// A named press outlet (not our own brand, not a newsletter) => article.
36+
if (media && media !== 'data for good' && !media.startsWith('newsletter')) return 'article';
4537
return 'autre';
4638
}
4739

48-
function determineNewsletter(element: Record<string, any> | null, tagStrings: string[]): string | null {
49-
const newsletters = ['Techno-lucides', "Vers l'autonomie et au-delà"];
50-
// Check tags
51-
for (const n of newsletters) {
52-
if (tagStrings.includes(n)) return n;
53-
}
54-
// Check media_name (e.g. "Newsletter - Technolucide", "Newsletter - Vers l'autonomie et au-delà")
55-
if (element?.media_name) {
56-
if (element.media_name === 'Newsletter - Technolucide') return 'Techno-lucides';
57-
if (element.media_name === "Newsletter - Vers l'autonomie et au-delà") return "Vers l'autonomie et au-delà";
58-
}
59-
// Check title
60-
if (element?.title) {
61-
for (const n of newsletters) {
62-
if (element.title.includes(n)) return n;
63-
}
64-
if (element.title.startsWith('Techno-Lucide')) return 'Techno-lucides';
65-
if (element.title.startsWith("Vers l'autonomie")) return "Vers l'autonomie et au-delà";
66-
}
40+
// Fallback used only while a resource has no `newsletter` set in Strapi.
41+
function determineNewsletter(element: Record<string, any> | null): string | null {
42+
const media = element?.media_name ?? '';
43+
const title = element?.title ?? '';
44+
if (media === 'Newsletter - Technolucide' || title.startsWith('Techno-Lucide')) return 'techno_lucides';
45+
if (media === "Newsletter - Vers l'autonomie et au-delà" || title.startsWith("Vers l'autonomie")) return 'vers_lautonomie';
6746
return null;
6847
}
6948

@@ -91,27 +70,26 @@ function transformBlogsData(resources: NonNullable<BlogsPageData>): TransformedR
9170

9271
const publishedDate = element?.published_date ?? '';
9372
const parsedDate = new Date(publishedDate);
73+
const isValidDate = !isNaN(parsedDate.getTime());
9474

9575
return {
9676
id: resource.id,
9777
title: element?.title ?? '',
9878
rawDate: publishedDate,
99-
date: parsedDate instanceof Date && !isNaN(parsedDate.getTime())
100-
? parsedDate.toLocaleString(undefined, {dateStyle: 'medium'})
101-
: '',
79+
date: isValidDate ? parsedDate.toLocaleString(undefined, {dateStyle: 'medium'}) : '',
10280
image: element?.thumbnail?.url ?? '/images/dataforgood.svg',
10381
link: isBlog ? `/blog/${element?.slug ?? ''}` : getPressReleaseLink(element as any),
10482
subInfos: tagStrings,
10583
tags: [
106-
parsedDate instanceof Date && !isNaN(parsedDate.getTime())
107-
? parsedDate.toLocaleDateString(undefined, {dateStyle: 'long'})
108-
: null,
84+
isValidDate ? parsedDate.toLocaleDateString(undefined, {dateStyle: 'long'}) : null,
10985
element?.media_name,
11086
].filter(Boolean),
11187
isBlank: true,
112-
year: parsedDate instanceof Date && !isNaN(parsedDate.getTime()) ? parsedDate.getFullYear() : 0,
113-
type: determineType(element, tagStrings),
114-
newsletter: determineNewsletter(element, tagStrings),
88+
year: isValidDate ? parsedDate.getFullYear() : 0,
89+
// `resource_type`/`newsletter` are the source of truth once set in Strapi;
90+
// fall back to a best-effort guess for rows not yet categorized by editors.
91+
type: element?.resource_type ?? determineType(element),
92+
newsletter: element?.newsletter ?? determineNewsletter(element),
11593
};
11694
});
11795
}
@@ -132,10 +110,17 @@ export default function BlogPage({data, pagination: _pagination}: BlogsPageProps
132110
const searchParams = useSearchParams();
133111
const allResources = useMemo(() => transformBlogsData(data), [data]);
134112

135-
const [searchQuery, setSearchQuery] = useState(searchParams.get('search') ?? '');
136-
const [activeYears, setActiveYears] = useState<string[]>(() => getSearchParamValues(searchParams, 'years'));
137-
const [activeTypes, setActiveTypes] = useState<string[]>(() => getSearchParamValues(searchParams, 'types'));
138-
const [activeNewsletters, setActiveNewsletters] = useState<string[]>(() => getSearchParamValues(searchParams, 'newsletters'));
113+
// The URL query string is the single source of truth for filters/search, so
114+
// that shareable links and browser Back/Forward restore the exact state.
115+
const yearsParam = searchParams.get('years') ?? '';
116+
const typesParam = searchParams.get('types') ?? '';
117+
const newslettersParam = searchParams.get('newsletters') ?? '';
118+
const searchQuery = searchParams.get('search') ?? '';
119+
120+
const activeYears = useMemo(() => (yearsParam ? yearsParam.split(',').filter(Boolean) : []), [yearsParam]);
121+
const activeTypes = useMemo(() => (typesParam ? typesParam.split(',').filter(Boolean) : []), [typesParam]);
122+
const activeNewsletters = useMemo(() => (newslettersParam ? newslettersParam.split(',').filter(Boolean) : []), [newslettersParam]);
123+
139124
const [hideFilters, setHideFilters] = useState(true);
140125

141126
const { currentPage, handlePageChange } = usePagination(1);
@@ -173,53 +158,39 @@ export default function BlogPage({data, pagination: _pagination}: BlogsPageProps
173158

174159
const pageCount = Math.ceil(filteredResources.length / pageSize);
175160

176-
const syncFiltersToUrl = useCallback((years: string[], types: string[], newsletters: string[], search: string, page: number) => {
161+
// Toggle a comma-separated multi-value param, always resetting to page 1.
162+
const toggleFilterParam = useCallback((key: string, value: string) => {
177163
const params = new URLSearchParams(searchParams.toString());
178-
if (years.length > 0) params.set('years', years.join(','));
179-
else params.delete('years');
180-
if (types.length > 0) params.set('types', types.join(','));
181-
else params.delete('types');
182-
if (newsletters.length > 0) params.set('newsletters', newsletters.join(','));
183-
else params.delete('newsletters');
184-
if (search) params.set('search', search);
185-
else params.delete('search');
186-
if (page > 1) params.set('page', String(page));
187-
else params.delete('page');
164+
const current = getSearchParamValues(params, key);
165+
const next = current.includes(value)
166+
? current.filter(v => v !== value)
167+
: [...current, value];
168+
if (next.length > 0) params.set(key, next.join(','));
169+
else params.delete(key);
170+
params.delete('page');
188171
router.push(`?${params.toString()}`, { scroll: false });
189172
}, [router, searchParams]);
190173

191174
const handleSearchChange = (e: any) => {
192175
const query = e.value ?? '';
193-
setSearchQuery(query);
194-
syncFiltersToUrl(activeYears, activeTypes, activeNewsletters, query, 1);
176+
const params = new URLSearchParams(searchParams.toString());
177+
if (query) params.set('search', query);
178+
else params.delete('search');
179+
params.delete('page');
180+
router.push(`?${params.toString()}`, { scroll: false });
181+
};
182+
183+
const paramKeyByFilterType: Record<string, string> = {
184+
year: 'years',
185+
type: 'types',
186+
newsletter: 'newsletters',
195187
};
196188

197189
const handleFilterClick = (e: any) => {
198190
const filterValue = e.value as string;
199-
const filterType = e.getAttribute('data-type');
200-
201-
let newYears = activeYears;
202-
let newTypes = activeTypes;
203-
let newNewsletters = activeNewsletters;
204-
205-
if (filterType === 'year') {
206-
newYears = activeYears.includes(filterValue)
207-
? activeYears.filter(v => v !== filterValue)
208-
: [...activeYears, filterValue];
209-
setActiveYears(newYears);
210-
} else if (filterType === 'type') {
211-
newTypes = activeTypes.includes(filterValue)
212-
? activeTypes.filter(v => v !== filterValue)
213-
: [...activeTypes, filterValue];
214-
setActiveTypes(newTypes);
215-
} else if (filterType === 'newsletter') {
216-
newNewsletters = activeNewsletters.includes(filterValue)
217-
? activeNewsletters.filter(v => v !== filterValue)
218-
: [...activeNewsletters, filterValue];
219-
setActiveNewsletters(newNewsletters);
220-
}
221-
222-
syncFiltersToUrl(newYears, newTypes, newNewsletters, searchQuery, 1);
191+
const filterType = e.getAttribute('data-type') as string;
192+
const key = paramKeyByFilterType[filterType];
193+
if (key) toggleFilterParam(key, filterValue);
223194
};
224195

225196
const yearFilters = [
@@ -236,13 +207,13 @@ export default function BlogPage({data, pagination: _pagination}: BlogsPageProps
236207
{ filterName: 'Podcast', filterValue: 'podcast' },
237208
{ filterName: 'Article', filterValue: 'article' },
238209
{ filterName: 'Rapport', filterValue: 'rapport' },
239-
{ filterName: 'Prise de position', filterValue: 'prise de position' },
210+
{ filterName: 'Prise de position', filterValue: 'prise_de_position' },
240211
{ filterName: 'Autre', filterValue: 'autre' },
241212
];
242213

243214
const newsletterFilters = [
244-
{ filterName: 'Techno-lucides', filterValue: 'Techno-lucides' },
245-
{ filterName: "Vers l'autonomie et au-delà", filterValue: "Vers l'autonomie et au-delà" },
215+
{ filterName: 'Techno-lucides', filterValue: 'techno_lucides' },
216+
{ filterName: "Vers l'autonomie et au-delà", filterValue: 'vers_lautonomie' },
246217
];
247218

248219
const allActiveFilterValues = [...activeYears, ...activeTypes, ...activeNewsletters];

0 commit comments

Comments
 (0)