From ebf39cf9bb5819db98bee79ff4e7da5037a21877 Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Thu, 18 Jun 2026 11:35:10 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20filter=20the=20search=20m?= =?UTF-8?q?odal=20by=20location,=20type,=20contact=20and=20date?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the search modal type/workspace/scope filters with the location, file type, shared-with and modification date filters, matching the new search design. --- CHANGELOG.md | 1 + .../apps/drive/src/features/drivers/Driver.ts | 1 + .../filters/ExplorerFilterLocation.tsx | 53 +++++++++++++++++++ .../explorer/components/filters/index.ts | 3 +- .../modals/search/ExplorerSearchModal.tsx | 48 +++++++++++------ .../drive/src/features/i18n/translations.json | 18 +++++++ .../e2e/__tests__/app-drive/search.spec.ts | 8 +-- 7 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 src/frontend/apps/drive/src/features/explorer/components/filters/ExplorerFilterLocation.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index bdc43ad2..bf354c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to - ✨(backend) allow converting a file while it is being analyzed - ✨(frontend) add file type, contact and modification date topbar filters +- ✨(frontend) add location, file type, contact and date search filters ### Fixed diff --git a/src/frontend/apps/drive/src/features/drivers/Driver.ts b/src/frontend/apps/drive/src/features/drivers/Driver.ts index 049acb57..f4879cea 100644 --- a/src/frontend/apps/drive/src/features/drivers/Driver.ts +++ b/src/frontend/apps/drive/src/features/drivers/Driver.ts @@ -42,6 +42,7 @@ export type ItemFilters = { is_favorite?: boolean; category?: string; contact?: string; + location?: string; updated_at_after?: string; updated_at_before?: string; }; diff --git a/src/frontend/apps/drive/src/features/explorer/components/filters/ExplorerFilterLocation.tsx b/src/frontend/apps/drive/src/features/explorer/components/filters/ExplorerFilterLocation.tsx new file mode 100644 index 00000000..71636673 --- /dev/null +++ b/src/frontend/apps/drive/src/features/explorer/components/filters/ExplorerFilterLocation.tsx @@ -0,0 +1,53 @@ +import { Filter, FilterOption, IconProps } from "@gouvfr-lasuite/ui-kit"; +import { JSX, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Key } from "react-aria-components"; +import { MyFilesIcon } from "@/features/ui/components/icon/MyFilesIcon"; +import { SharedWithMeIcon } from "@/features/ui/components/icon/SharedWithMeIcon"; +import { StarredIcon } from "@/features/ui/components/icon/StarredIcon"; +import { TrashIcon } from "@/features/ui/components/icon/TrashIcon"; +import { getResetOption } from "./filterUtils"; + +const LOCATION_OPTIONS: { + value: string; + icon: (props: Partial) => JSX.Element; +}[] = [ + { value: "my_files", icon: MyFilesIcon }, + { value: "shared_with_me", icon: SharedWithMeIcon }, + { value: "starred", icon: StarredIcon }, + { value: "trashbin", icon: TrashIcon }, +]; + +export const ExplorerFilterLocation = (props: { + value: string | null; + onChange: (value: Key | null) => void; +}) => { + const { t } = useTranslation(); + + const options: FilterOption[] = useMemo( + () => [ + // Reset sits at the top of the list, above the locations, as in the design. + { ...getResetOption(t), showSeparator: true }, + ...LOCATION_OPTIONS.map(({ value, icon: Icon }) => ({ + label: t(`explorer.filters.location.options.${value}`), + value, + render: () => ( +
+ + {t(`explorer.filters.location.options.${value}`)} +
+ ), + })), + ], + [t], + ); + + return ( + + ); +}; diff --git a/src/frontend/apps/drive/src/features/explorer/components/filters/index.ts b/src/frontend/apps/drive/src/features/explorer/components/filters/index.ts index f0294d2f..37b74e9f 100644 --- a/src/frontend/apps/drive/src/features/explorer/components/filters/index.ts +++ b/src/frontend/apps/drive/src/features/explorer/components/filters/index.ts @@ -1,6 +1,7 @@ export { ExplorerFilters } from "./ExplorerFilters"; -export { handleFilterChange } from "./filterUtils"; +export { ALL, handleFilterChange } from "./filterUtils"; export { ExplorerFilterContact } from "./ExplorerFilterContact"; +export { ExplorerFilterLocation } from "./ExplorerFilterLocation"; export { ExplorerFilterCategory } from "./ExplorerFilterCategory"; export { ExplorerFilterModified } from "./ExplorerFilterModified"; export { ExplorerFilterType } from "./ExplorerFilterType"; diff --git a/src/frontend/apps/drive/src/features/explorer/components/modals/search/ExplorerSearchModal.tsx b/src/frontend/apps/drive/src/features/explorer/components/modals/search/ExplorerSearchModal.tsx index c8f26364..5b72bbe8 100644 --- a/src/frontend/apps/drive/src/features/explorer/components/modals/search/ExplorerSearchModal.tsx +++ b/src/frontend/apps/drive/src/features/explorer/components/modals/search/ExplorerSearchModal.tsx @@ -21,16 +21,23 @@ import { useGlobalExplorer, } from "../../GlobalExplorerContext"; import { - ExplorerFilterType, - ExplorerFilterWorkspace, - ExplorerFilterScope, + ALL, + ExplorerFilterCategory, + ExplorerFilterContact, + ExplorerFilterLocation, + ExplorerFilterModified, handleFilterChange, } from "@/features/explorer/components/filters"; import { ItemFilters } from "@/features/drivers/Driver"; import { Key } from "react-aria-components"; import { clearFromRoute, getItemTitle } from "@/features/explorer/utils/utils"; import { messageModalTrashNavigate } from "../../trash/utils"; -import { useIsMinimalLayout } from "@/utils/useLayout"; +import { useAuth } from "@/features/auth/Auth"; +import { + applyDateRange, + DateRange, + dateRangeFromFilters, +} from "@/features/explorer/utils/dateFilters"; import { openWopiInNewTab } from "@/features/wopi/openWopi"; import { itemToPreviewFile } from "@/features/explorer/utils/utils"; @@ -40,8 +47,8 @@ type ExplorerSearchModalProps = Pick & { export const ExplorerSearchModal = (props: ExplorerSearchModalProps) => { const { t } = useTranslation(); + const { user } = useAuth(); const [inputValue, setInputValue] = useState(""); - const isMinimalLayout = useIsMinimalLayout(); const [filters, setFilters] = useState( props.defaultFilters || {}, ); @@ -86,6 +93,11 @@ export const ExplorerSearchModal = (props: ExplorerSearchModalProps) => { setFilters(handleFilterChange(filters, name, value)); }; + // The modification date filter drives two query params at once. + const onModifiedChange = (range: DateRange | null) => { + setFilters(applyDateRange(filters, range)); + }; + const modals = useModals(); const onItemClick = (item: Item) => { @@ -149,18 +161,24 @@ export const ExplorerSearchModal = (props: ExplorerSearchModalProps) => { >
- onFilterChange("type", value)} + onFilterChange("location", value)} /> - onFilterChange("workspace", value)} + onFilterChange("category", value)} /> - onFilterChange("scope", value)} + {/* Contacts and user search both require authentication. */} + {user && ( + onFilterChange("contact", value ?? ALL)} + /> + )} +
diff --git a/src/frontend/apps/drive/src/features/i18n/translations.json b/src/frontend/apps/drive/src/features/i18n/translations.json index 9133c417..f0dd74bc 100644 --- a/src/frontend/apps/drive/src/features/i18n/translations.json +++ b/src/frontend/apps/drive/src/features/i18n/translations.json @@ -246,6 +246,15 @@ } }, "filters": { + "location": { + "label": "Location", + "options": { + "my_files": "My files", + "shared_with_me": "Shared with me", + "starred": "Starred", + "trashbin": "Trash" + } + }, "scopes": { "label": "Location", "options": { @@ -884,6 +893,15 @@ } }, "filters": { + "location": { + "label": "Emplacement", + "options": { + "my_files": "Mes fichiers", + "shared_with_me": "Partagés avec moi", + "starred": "Favoris", + "trashbin": "Corbeille" + } + }, "scopes": { "label": "Emplacement", "options": { diff --git a/src/frontend/apps/e2e/__tests__/app-drive/search.spec.ts b/src/frontend/apps/e2e/__tests__/app-drive/search.spec.ts index f9b6780f..d2aea973 100644 --- a/src/frontend/apps/e2e/__tests__/app-drive/search.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-drive/search.spec.ts @@ -105,9 +105,9 @@ test("Search folder from trash and cannot navigate to it", async ({ page }) => { await expect(input).toBeVisible(); await input.fill("I am"); - // Set the scope to trash. + // Set the location to the trash. await page.getByRole("button", { name: "Location" }).click(); - await page.getByRole("option", { name: "Recycle bin" }).click(); + await page.getByRole("option", { name: "Trash" }).click(); searchItems = page.getByTestId("search-item"); await expect(searchItems).toHaveCount(1); @@ -142,9 +142,9 @@ test("Search a deleted file and click on it", async ({ page }) => { await expect(input).toBeVisible(); await input.fill("resum"); - // Set the scope to trash. + // Set the location to the trash. await page.getByRole("button", { name: "Location" }).click(); - await page.getByRole("option", { name: "Recycle bin" }).click(); + await page.getByRole("option", { name: "Trash" }).click(); let searchItems = page.getByTestId("search-item"); await expect(searchItems).toHaveCount(1);