diff --git a/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.locale.tsx b/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.locale.tsx index 8adeed58..31ef0019 100644 --- a/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.locale.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.locale.tsx @@ -1,7 +1,9 @@ import { declareComponentKeys } from "i18nifty"; import { ReactNode } from "react"; -import { Translations } from "../../../../i18n/types"; + import { formatDateFromISO } from "@/utils"; +import { Translations } from "../../../../i18n/types"; +import { FilterEnum, SortByEnum, SortOrderEnum } from "./DatasheetList.types"; const { i18n } = declareComponentKeys< | { K: "title"; P: { datastoreName?: string }; R: string } @@ -13,6 +15,15 @@ const { i18n } = declareComponentKeys< | { K: "sandbox_datastore_explanation"; R: ReactNode } | "refresh_datasheet_list" | { K: "last_refresh_date"; P: { dataUpdatedAt: number }; R: string } + | "filter_label" + | "filter_placeholder" + | { K: "filter_option"; P: { filter: FilterEnum }; R: string } + | "sort_label" + | "sort_placeholder" + | { K: "sort_option"; P: { sort: SortByEnum }; R: string } + | "sort_order_label" + | "sort_order_placeholder" + | { K: "sort_order_option"; P: { sortOrder: SortOrderEnum }; R: string } >()("DatasheetList"); export type I18n = typeof i18n; @@ -32,6 +43,42 @@ export const DatasheetListFrTranslations: Translations<"fr">["DatasheetList"] = ), refresh_datasheet_list: "Rafraîchir", last_refresh_date: ({ dataUpdatedAt }) => `Données mises à jour le ${formatDateFromISO(new Date(dataUpdatedAt).toISOString())}`, + filter_label: "Filtrer", + filter_placeholder: "Sélectionner un filtre", + filter_option: ({ filter }) => { + switch (filter) { + case FilterEnum.PUBLISHED: + return "Fiches publiées"; + case FilterEnum.NOT_PUBLISHED: + return "Fiches non publiées"; + default: + return "Filtre inconnu"; + } + }, + sort_label: "Trier", + sort_placeholder: "Trier par", + sort_option: ({ sort }) => { + switch (sort) { + case SortByEnum.NAME: + return "Nom"; + case SortByEnum.NB_SERVICES: + return "Fiches publiées"; + default: + return "Tri inconnu"; + } + }, + sort_order_label: "Trier", + sort_order_placeholder: "Dans l'ordre", + sort_order_option: ({ sortOrder }) => { + switch (sortOrder) { + case SortOrderEnum.ASCENDING: + return "Croissant"; + case SortOrderEnum.DESCENDING: + return "Décroissant"; + default: + return "Ordre inconnu"; + } + }, }; export const DatasheetListEnTranslations: Translations<"en">["DatasheetList"] = { @@ -44,4 +91,13 @@ export const DatasheetListEnTranslations: Translations<"en">["DatasheetList"] = sandbox_datastore_explanation: undefined, refresh_datasheet_list: undefined, last_refresh_date: undefined, + filter_label: undefined, + filter_placeholder: undefined, + filter_option: undefined, + sort_label: undefined, + sort_placeholder: undefined, + sort_option: undefined, + sort_order_label: undefined, + sort_order_placeholder: undefined, + sort_order_option: undefined, }; diff --git a/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.tsx b/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.tsx index 0cc07ec8..a222de10 100644 --- a/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.tsx @@ -2,19 +2,57 @@ import { fr } from "@codegouvfr/react-dsfr"; import Alert from "@codegouvfr/react-dsfr/Alert"; import ButtonsGroup from "@codegouvfr/react-dsfr/ButtonsGroup"; import Pagination from "@codegouvfr/react-dsfr/Pagination"; +import SearchBar from "@codegouvfr/react-dsfr/SearchBar"; +import SelectNext from "@codegouvfr/react-dsfr/SelectNext"; +import Tag from "@codegouvfr/react-dsfr/Tag"; import { useQuery } from "@tanstack/react-query"; -import { FC, useMemo } from "react"; +import { FC, useMemo, useState } from "react"; import { Datasheet, EndpointTypeEnum } from "../../../../@types/app"; +import Main from "../../../../components/Layout/Main"; import LoadingIcon from "../../../../components/Utils/LoadingIcon"; import Skeleton from "../../../../components/Utils/Skeleton"; +import { useDatastore } from "../../../../contexts/datastore"; import { useTranslation } from "../../../../i18n/i18n"; import RQKeys from "../../../../modules/entrepot/RQKeys"; import { routes, useRoute } from "../../../../router/router"; import api from "../../../api"; +import { FilterEnum, Sort, SortByEnum, SortOrderEnum } from "./DatasheetList.types"; import DatasheetListItem from "./DatasheetListItem"; -import { useDatastore } from "../../../../contexts/datastore"; -import Main from "../../../../components/Layout/Main"; + +const getFilteredList = (list: Datasheet[], filters: FilterEnum[], filterName?: string) => { + if (filterName) { + list = list.filter((d) => d.name.toLowerCase().includes(filterName.toLowerCase())); + } + + if (filters.length === 0) { + return list; + } + + let filtered: Datasheet[] = []; + + if (filters.includes(FilterEnum.PUBLISHED)) { + filtered = [...filtered, ...list.filter((d) => d.nb_publications > 0)]; + } + + if (filters.includes(FilterEnum.NOT_PUBLISHED)) { + filtered = [...filtered, ...list.filter((d) => d.nb_publications === 0)]; + } + return filtered; +}; + +const getSortedList = (list: Datasheet[], sort: Sort) => { + return list.sort((a, b) => { + switch (sort.by) { + case SortByEnum.NB_SERVICES: + return (a.nb_publications - b.nb_publications) * sort.order; + + case SortByEnum.NAME: + default: + return a.name.localeCompare(b.name) * sort.order; + } + }); +}; type DatasheetListProps = { datastoreId: string; @@ -22,6 +60,7 @@ type DatasheetListProps = { const DatasheetList: FC = ({ datastoreId }) => { const { t } = useTranslation("DatasheetList"); const { datastore, isFetching } = useDatastore(); + const { t: tCommon } = useTranslation("Common"); const { params } = useRoute(); const pagination = { @@ -45,10 +84,17 @@ const DatasheetList: FC = ({ datastoreId }) => { metadataEndpoint && metadataEndpoint?.quota && metadataEndpoint?.use && metadataEndpoint?.quota <= metadataEndpoint?.use ); + // filtre et tri + const [searchDatasheetName, setSearchDatasheetName] = useState(); + const [filters, setFilters] = useState([]); + const [sort, setSort] = useState({ by: SortByEnum.NAME, order: SortOrderEnum.ASCENDING }); + + const datasheetList = getSortedList(getFilteredList(datasheetListQuery.data ?? [], filters, searchDatasheetName), sort); + return (
-
+

{t("title", { datastoreName: datastore?.name })} {(isFetching || datasheetListQuery?.isFetching) && } @@ -98,13 +144,135 @@ const DatasheetList: FC = ({ datastoreId }) => { <>

{t("last_refresh_date", { dataUpdatedAt: datasheetListQuery.dataUpdatedAt })}

- {datasheetListQuery?.data +
+
+ { + setSearchDatasheetName(text); + routes.datasheet_list({ datastoreId }).replace(); + }} + allowEmptySearch={true} + /> +
+
+ + {/*
+
+ +
+
*/} + +
+
+ { + const selectedFilter = Number(e.currentTarget.value); + if (isNaN(selectedFilter) || selectedFilter === 0) return; + + setFilters((prev) => (prev.includes(selectedFilter) ? [...prev] : [...prev, selectedFilter])); + e.currentTarget.value = ""; + routes.datasheet_list({ datastoreId }).replace(); + }, + }} + placeholder={t("filter_placeholder")} + /> +
+
+
    + {filters.map((filter) => ( +
  • + { + setFilters((prev) => prev.filter((f) => f !== filter)); + routes.datasheet_list({ datastoreId }).replace(); + }, + }} + > + {t("filter_option", { filter: Number(filter) })} + +
  • + ))} +
+
+
+
+
+ { + const selectedSortBy = Number(e.currentTarget.value); + + if (isNaN(selectedSortBy) || selectedSortBy === 0) return; + setSort((prev) => ({ ...prev, by: selectedSortBy })); + }, + }} + placeholder={t("sort_placeholder")} + /> +
+
+ { + const selectedSortOrder = Number(e.currentTarget.value); + if (isNaN(selectedSortOrder) || selectedSortOrder === 0) return; + setSort((prev) => ({ ...prev, order: selectedSortOrder })); + }, + }} + placeholder={t("sort_order_placeholder")} + /> +
+
+ + {datasheetList ?.slice((pagination.page - 1) * pagination.limit, pagination.page * pagination.limit) .map((datasheet: Datasheet) => )}
({ ...routes.datasheet_list({ datastoreId, page: pageNumber, limit: pagination.limit }).link, diff --git a/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.types.tsx b/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.types.tsx new file mode 100644 index 00000000..4b780274 --- /dev/null +++ b/assets/entrepot/pages/datasheet/DatasheetList/DatasheetList.types.tsx @@ -0,0 +1,16 @@ +export enum FilterEnum { + PUBLISHED = 1, + NOT_PUBLISHED = 2, +} + +export enum SortByEnum { + NAME = 1, + NB_SERVICES = 2, +} + +export enum SortOrderEnum { + ASCENDING = 1, + DESCENDING = -1, +} + +export type Sort = { by: SortByEnum; order: SortOrderEnum }; diff --git a/assets/i18n/Common.locale.tsx b/assets/i18n/Common.locale.tsx index 2f47cafb..f216d99a 100644 --- a/assets/i18n/Common.locale.tsx +++ b/assets/i18n/Common.locale.tsx @@ -34,6 +34,8 @@ const { i18n } = declareComponentKeys< | "url_copied" | "copy_to_clipboard" | "go_to_content" + | "search" + | "clear" >()("Common"); export type I18n = typeof i18n; @@ -70,6 +72,8 @@ export const commonFrTranslations: Translations<"fr">["Common"] = { url_copied: "URL copiée", copy_to_clipboard: "Copier dans le presse-papier", go_to_content: "Aller au contenu", + search: "Rechercher", + clear: "Effacer", }; export const commonEnTranslations: Translations<"en">["Common"] = { @@ -105,4 +109,6 @@ export const commonEnTranslations: Translations<"en">["Common"] = { url_copied: "URL copied", copy_to_clipboard: "Copy to clipboard", go_to_content: "Go to content", + search: "Search", + clear: "Clear", };