@@ -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