|
1 | 1 | import { fr } from "@codegouvfr/react-dsfr"; |
2 | | -import { Card } from "@codegouvfr/react-dsfr/Card"; |
3 | | -import { Tag } from "@codegouvfr/react-dsfr/Tag"; |
4 | | -import { FC } from "react"; |
| 2 | +import Alert from "@codegouvfr/react-dsfr/Alert"; |
| 3 | +import { useQuery } from "@tanstack/react-query"; |
| 4 | +import { FC, useEffect } from "react"; |
5 | 5 | import { symToStr } from "tsafe/symToStr"; |
6 | 6 |
|
7 | | -import articles from "../../data/actualites.json"; |
8 | | -import { appRoot, routes } from "../../router/router"; |
9 | | -import { type NewsArticle } from "../../@types/newsArticle"; |
10 | | -import { formatDateFromISO } from "../../utils"; |
11 | 7 | import Main from "../../components/Layout/Main"; |
| 8 | +import LoadingText from "../../components/Utils/LoadingText"; |
| 9 | +import { useTranslation } from "../../i18n/i18n"; |
| 10 | +import SymfonyRouting from "../../modules/Routing"; |
| 11 | +import { routes } from "../../router/router"; |
12 | 12 |
|
13 | | -type NewsListItemProps = { |
14 | | - slug: string; |
15 | | - newsArticle: NewsArticle; |
| 13 | +// NOTE pour que la commande "react-dsfr update-icons" inclue l'icone article dans les assets qui est utilisée dans les articles |
| 14 | +// fr-icon-article-line |
| 15 | + |
| 16 | +type NewsListProps = { |
| 17 | + page: number; |
| 18 | + tag?: string; |
16 | 19 | }; |
| 20 | +const NewsList: FC<NewsListProps> = ({ page = 0, tag }) => { |
| 21 | + const { t: tCommon } = useTranslation("Common"); |
17 | 22 |
|
18 | | -const NewsListItem: FC<NewsListItemProps> = ({ slug, newsArticle }) => { |
19 | | - const SHORT_DESC_MAX_CHAR = 120; |
| 23 | + const articlesListQuery = useQuery({ |
| 24 | + queryKey: ["articles", "list", tag, page], |
| 25 | + queryFn: async ({ signal }) => { |
| 26 | + const url = SymfonyRouting.generate("cartesgouvfr_s3_gateway_get_content", { |
| 27 | + path: tag ? `articles/list/tags/${tag}/${page}.html` : `articles/list/${page}.html`, |
| 28 | + }); |
| 29 | + const response = await fetch(url, { signal }); |
20 | 30 |
|
21 | | - const tags = newsArticle?.tags?.map((tag, i) => <Tag key={`${slug}_tag_${i}`}>{tag}</Tag>); |
| 31 | + if (!response.ok) { |
| 32 | + return Promise.reject({ |
| 33 | + message: "Fetching articles failed", |
| 34 | + code: response.status, |
| 35 | + }); |
| 36 | + } |
22 | 37 |
|
23 | | - return ( |
24 | | - <div className={fr.cx("fr-col-sm-12", "fr-col-md-4", "fr-col-lg-4")}> |
25 | | - <Card |
26 | | - start={<div className={fr.cx("fr-tags-group")}>{tags}</div>} |
27 | | - desc={ |
28 | | - <span |
29 | | - dangerouslySetInnerHTML={{ |
30 | | - __html: |
31 | | - newsArticle?.short_description && newsArticle?.short_description.length > SHORT_DESC_MAX_CHAR |
32 | | - ? newsArticle?.short_description.substring(0, 100) + "..." |
33 | | - : (newsArticle?.short_description ?? ""), |
34 | | - }} |
35 | | - /> |
| 38 | + const text = await response.text(); |
| 39 | + return text; |
| 40 | + }, |
| 41 | + }); |
| 42 | + |
| 43 | + // @ts-expect-error fausse alerte |
| 44 | + if (articlesListQuery.error?.code === 404) { |
| 45 | + routes.news_list({ page: 0 }).push(); |
| 46 | + } |
| 47 | + |
| 48 | + // gestion des liens de navigation côté client pour les tags et les cartes d'articles |
| 49 | + useEffect(() => { |
| 50 | + const handleTagClick = (event: MouseEvent) => { |
| 51 | + const target = event.target as HTMLElement; |
| 52 | + if (target.tagName === "A" && target.classList?.contains("fr-tag")) { |
| 53 | + event.preventDefault(); |
| 54 | + const href = (target as HTMLAnchorElement).href; |
| 55 | + const tag = href.split("/")?.[5]; |
| 56 | + if (tag) { |
| 57 | + routes.news_list_by_tag({ page: 0, tag }).push(); |
| 58 | + } else { |
| 59 | + routes.news_list({ page: 0 }).push(); |
36 | 60 | } |
37 | | - detail={newsArticle?.date && formatDateFromISO(newsArticle?.date)} |
38 | | - enlargeLink |
39 | | - imageAlt={newsArticle?.thumbnail_alt ?? "Vignette de l’article"} |
40 | | - imageUrl={`${appRoot}/${newsArticle.thumbnail_url}`} |
41 | | - linkProps={routes.news_article({ slug }).link} |
42 | | - title={<span dangerouslySetInnerHTML={{ __html: newsArticle?.title ?? "" }} />} |
43 | | - titleAs="h2" |
44 | | - /> |
45 | | - </div> |
46 | | - ); |
47 | | -}; |
48 | | -NewsListItem.displayName = symToStr({ NewsListItem }); |
| 61 | + } |
| 62 | + }; |
| 63 | + |
| 64 | + const handleCardClick = (event: MouseEvent) => { |
| 65 | + const target = event.target as HTMLElement; |
| 66 | + if (target.tagName === "A" && target.parentElement?.className.includes("fr-card")) { |
| 67 | + event.preventDefault(); |
| 68 | + const href = (target as HTMLAnchorElement).href; |
| 69 | + const slug = href.split("/")?.[4]; |
| 70 | + if (slug) { |
| 71 | + routes.news_article({ slug }).push(); |
| 72 | + } |
| 73 | + } |
| 74 | + }; |
| 75 | + |
| 76 | + document.addEventListener("click", handleTagClick); |
| 77 | + document.addEventListener("click", handleCardClick); |
| 78 | + |
| 79 | + return () => { |
| 80 | + document.removeEventListener("click", handleTagClick); |
| 81 | + document.removeEventListener("click", handleCardClick); |
| 82 | + }; |
| 83 | + }, []); |
49 | 84 |
|
50 | | -const NewsList = () => { |
51 | 85 | return ( |
52 | 86 | <Main title="Actualités"> |
53 | | - <div className={fr.cx("fr-container")}> |
54 | | - <h1>Actualités</h1> |
| 87 | + {articlesListQuery.isLoading && <LoadingText message="Actualités" as="h1" withSpinnerIcon={true} />} |
| 88 | + |
| 89 | + {articlesListQuery.error && ( |
| 90 | + <Alert severity={"error"} title={tCommon("error")} description={articlesListQuery.error?.message} className={fr.cx("fr-my-3w")} /> |
| 91 | + )} |
55 | 92 |
|
56 | | - <div className={fr.cx("fr-grid-row", "fr-grid-row--gutters")}> |
57 | | - {Object.entries(articles)?.map(([slug, article]) => <NewsListItem key={slug} slug={slug} newsArticle={article} />)} |
58 | | - </div> |
59 | | - </div> |
| 93 | + {articlesListQuery.data && ( |
| 94 | + <div |
| 95 | + tabIndex={-1} |
| 96 | + dangerouslySetInnerHTML={{ |
| 97 | + __html: articlesListQuery.data, |
| 98 | + }} |
| 99 | + /> |
| 100 | + )} |
60 | 101 | </Main> |
61 | 102 | ); |
62 | 103 | }; |
|
0 commit comments