Skip to content

Commit

Permalink
feat: write and read filters as query params in url (#1601)
Browse files Browse the repository at this point in the history
Please enter the commit message for your changes. Lines starting
  • Loading branch information
mbacherycz authored Dec 27, 2024
1 parent 87c5161 commit 52252dd
Show file tree
Hide file tree
Showing 22 changed files with 498 additions and 301 deletions.
16 changes: 8 additions & 8 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import { ProtectedPageLayout } from './components/PageLayout/PageLayout.tsx';
import { Inbox } from './pages/Inbox';
import { Routes as AppRoutes } from './pages/Inbox/Inbox';
import { InboxItemPage } from './pages/InboxItemPage';
import { Logout } from './pages/LogoutPage';
import { SavedSearchesPage } from './pages/SavedSearches';
import { PageRoutes } from './pages/routes.ts';

import './app.css';

Expand All @@ -13,13 +13,13 @@ function App() {
<div className="app">
<Routes>
<Route element={<ProtectedPageLayout />}>
<Route path={AppRoutes.inbox} element={<Inbox key="inbox" viewType={'inbox'} />} />
<Route path={AppRoutes.drafts} element={<Inbox key="draft" viewType={'drafts'} />} />
<Route path={AppRoutes.sent} element={<Inbox key="sent" viewType={'sent'} />} />
<Route path={AppRoutes.archive} element={<Inbox key="archive" viewType={'archive'} />} />
<Route path={AppRoutes.bin} element={<Inbox key="bin" viewType={'bin'} />} />
<Route path={AppRoutes.inboxItem} element={<InboxItemPage />} />
<Route path={AppRoutes.savedSearches} element={<SavedSearchesPage />} />
<Route path={PageRoutes.inbox} element={<Inbox key="inbox" viewType={'inbox'} />} />
<Route path={PageRoutes.drafts} element={<Inbox key="draft" viewType={'drafts'} />} />
<Route path={PageRoutes.sent} element={<Inbox key="sent" viewType={'sent'} />} />
<Route path={PageRoutes.archive} element={<Inbox key="archive" viewType={'archive'} />} />
<Route path={PageRoutes.bin} element={<Inbox key="bin" viewType={'bin'} />} />
<Route path={PageRoutes.inboxItem} element={<InboxItemPage />} />
<Route path={PageRoutes.savedSearches} element={<SavedSearchesPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Route>
<Route path="/loggedout" element={<Logout />} />
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/api/useDialogByIdSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { SSE } from 'sse.js';
import { QUERY_KEYS } from '../constants/queryKeys.ts';
import { Routes } from '../pages/Inbox/Inbox.tsx';
import { PageRoutes } from '../pages/routes.ts';

export const useDialogByIdSubscription = (dialogId: string | undefined, dialogToken: string | undefined) => {
const queryClient = useQueryClient();
Expand All @@ -28,7 +28,7 @@ export const useDialogByIdSubscription = (dialogId: string | undefined, dialogTo
const updatedType: DialogEventType | undefined = jsonPayload.data?.dialogEvents?.type;
if (updatedType && updatedType === DialogEventType.DialogDeleted) {
// Redirect to inbox if the dialog was deleted
navigate(Routes.inbox);
navigate(PageRoutes.inbox);
} else if (updatedType && updatedType === DialogEventType.DialogUpdated) {
void queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.DIALOG_BY_ID] });
}
Expand Down
14 changes: 5 additions & 9 deletions packages/frontend/src/components/FilterBar/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ type ListOpenTarget = 'none' | 'add_filter' | string;

export type FilterBarRef = {
openFilter: () => void;
resetFilters: () => void;
};

/**
Expand Down Expand Up @@ -116,9 +115,6 @@ export const FilterBar = forwardRef(
openFilter() {
setListOpenForTarget('add_filter');
},
resetFilters() {
setSelectedFilters([]);
},
}));

// biome-ignore lint: lint/correctness/useExhaustiveDependencies
Expand All @@ -137,7 +133,7 @@ export const FilterBar = forwardRef(
[selectedFilters, onFilterChange],
);

const getFilterSetting = (id: string) => settings.find((setting) => setting.id === id);
const getFilterConfig = (id: string) => settings.find((setting) => setting.id === id);

/**
* Toggles the filter value for a given filter ID. If `overrideValue` is `true`,
Expand All @@ -151,7 +147,7 @@ export const FilterBar = forwardRef(
(id: string, value: FilterValueType, overrideValue?: boolean) => {
const existingFilters = selectedFilters.filter((filter) => filter.id === id);
const filterExists = existingFilters.some((filter) => filter.value === value);
const setting = getFilterSetting(id);
const setting = getFilterConfig(id);
const allowMultiselect = setting?.operation === 'includes';

let updatedFilters: Filter[];
Expand Down Expand Up @@ -200,15 +196,15 @@ export const FilterBar = forwardRef(
<section className={styles.filterBar}>
<div className={styles.filterButtons}>
{Object.keys(filtersById)
.filter((id) => getFilterSetting(id) !== undefined)
.filter((id) => getFilterConfig(id) !== undefined)
.map((id) => {
const setting = getFilterSetting(id)!;
const filterConfig = getFilterConfig(id)!;
const isFilterMenuOpen = listOpenForTarget === id;
return (
<FilterButton
key={id}
isOpen={isFilterMenuOpen}
filterFieldData={setting}
filterFieldData={filterConfig}
onBtnClick={() => setListOpenForTarget(isFilterMenuOpen ? 'none' : id)}
onBackBtnClick={() => setListOpenForTarget('add_filter')}
onRemove={() => handleOnRemove(id)}
Expand Down
10 changes: 8 additions & 2 deletions packages/frontend/src/components/FosToolbar/FosToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface FosToolbarProps {
onFilterBtnClick?: () => void;
onSaveBtnClick: () => void;
hideSaveButton?: boolean;
hideFilterButton?: boolean;
}
/*
* FosToolbar is a floating toolbar that is only visible on mobile and contains action buttons for filtering, sorting and saving search.
Expand All @@ -16,12 +17,17 @@ interface FosToolbarProps {
* @param hideSaveButton - Optional boolean that determines if the save button should be hidden. Default is false
* @returns A floating toolbar with action buttons for filtering, sorting and saving search.
*/
export const FosToolbar = ({ onFilterBtnClick, onSaveBtnClick, hideSaveButton = false }: FosToolbarProps) => {
export const FosToolbar = ({
onFilterBtnClick,
onSaveBtnClick,
hideSaveButton = false,
hideFilterButton = false,
}: FosToolbarProps) => {
const { t } = useTranslation();
return (
<div className={styles.fosToolbar}>
<div className={styles.buttons}>
{onFilterBtnClick && (
{hideFilterButton ? null : (
<ProfileButton onClick={onFilterBtnClick} size="sm" variant="tertiary">
<PlusIcon fontSize="1.5rem" /> {t('fos.buttons.filter')}
</ProfileButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next';
import { Link, useLocation } from 'react-router-dom';
import { useWindowSize } from '../../../../utils/useWindowSize.tsx';
import type { InboxViewType } from '../../../api/useDialogs.tsx';
import { Routes } from '../../../pages/Inbox/Inbox.tsx';
import { getGlobalSearchQueryParams } from '../../../pages/Inbox/queryParams.ts';
import { PageRoutes } from '../../../pages/routes.ts';
export type SideBarView = InboxViewType | 'saved-searches' | 'archive' | 'bin';

export type ItemPerViewCount = {
Expand Down Expand Up @@ -36,6 +37,7 @@ const createMenuItemComponent =
export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobalMenuProps => {
const { t } = useTranslation();
const { pathname, search } = useLocation();
const globalSearchQueryParams = getGlobalSearchQueryParams(search);
const { isMobile } = useWindowSize();
const linksMenuItems: MenuItemProps[] = [
{
Expand Down Expand Up @@ -70,10 +72,10 @@ export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobal
title: t('sidebar.inbox'),
color: 'strong',
badge: getBadgeProps(itemsPerViewCount.inbox, 'alert'),
selected: pathname === Routes.inbox,
selected: pathname === PageRoutes.inbox,
expanded: true,
as: createMenuItemComponent({
to: Routes.inbox + search,
to: PageRoutes.inbox + globalSearchQueryParams,
}),
items: [
{
Expand All @@ -82,9 +84,9 @@ export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobal
icon: 'doc-pencil',
title: t('sidebar.drafts'),
badge: getBadgeProps(itemsPerViewCount.drafts),
selected: pathname === Routes.drafts,
selected: pathname === PageRoutes.drafts,
as: createMenuItemComponent({
to: Routes.drafts + search,
to: PageRoutes.drafts + globalSearchQueryParams,
}),
},
{
Expand All @@ -93,9 +95,9 @@ export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobal
icon: 'file-checkmark',
title: t('sidebar.sent'),
badge: getBadgeProps(itemsPerViewCount.sent),
selected: pathname === Routes.sent,
selected: pathname === PageRoutes.sent,
as: createMenuItemComponent({
to: Routes.sent + search,
to: PageRoutes.sent + globalSearchQueryParams,
}),
},
{
Expand All @@ -104,9 +106,9 @@ export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobal
icon: 'bookmark',
title: t('sidebar.saved_searches'),
badge: getBadgeProps(itemsPerViewCount['saved-searches']),
selected: pathname === Routes.savedSearches,
selected: pathname === PageRoutes.savedSearches,
as: createMenuItemComponent({
to: Routes.savedSearches + search,
to: PageRoutes.savedSearches + globalSearchQueryParams,
}),
},
{
Expand All @@ -115,9 +117,9 @@ export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobal
icon: 'archive',
title: t('sidebar.archived'),
badge: getBadgeProps(itemsPerViewCount.archive),
selected: pathname === Routes.archive,
selected: pathname === PageRoutes.archive,
as: createMenuItemComponent({
to: Routes.archive + search,
to: PageRoutes.archive + globalSearchQueryParams,
}),
},
{
Expand All @@ -126,9 +128,9 @@ export const useGlobalMenu = ({ itemsPerViewCount }: UseSidebarProps): UseGlobal
icon: 'trash',
title: t('sidebar.deleted'),
badge: getBadgeProps(itemsPerViewCount.bin),
selected: pathname === Routes.bin,
selected: pathname === PageRoutes.bin,
as: createMenuItemComponent({
to: Routes.bin + search,
to: PageRoutes.bin + globalSearchQueryParams,
}),
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ export const useSearchDialogs = ({ parties, searchValue }: searchDialogsProps):
});
const [searchResults, setSearchResults] = useState([] as InboxItemInput[]);

// biome-ignore lint/correctness/useExhaustiveDependencies: Full control of what triggers this code is needed
useEffect(() => {
setSearchResults(enabled ? mapDialogDtoToInboxItem(data?.searchDialogs?.items ?? [], parties, organizations) : []);
}, [setSearchResults, data?.searchDialogs?.items, enabled, parties, organizations]);
}, [data?.searchDialogs?.items, enabled, parties, organizations]);

return {
isLoading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { QUERY_KEYS } from '../../../constants/queryKeys.ts';
import { Routes } from '../../../pages/Inbox/Inbox.tsx';
import { getSearchStringFromQueryParams } from '../../../pages/Inbox/queryParams.ts';
import { PageRoutes } from '../../../pages/routes.ts';

export const useSearchString = () => {
const [searchParams, setSearchParams] = useSearchParams();
Expand Down Expand Up @@ -48,10 +48,10 @@ export const useSearchString = () => {
} else {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set('search', value);
if (location.pathname !== Routes.inbox) {
navigate(Routes.inbox + `?${newSearchParams.toString()}`);
if (location.pathname !== PageRoutes.inbox) {
navigate(PageRoutes.inbox + `?${newSearchParams.toString()}`);
} else {
setSearchParams(newSearchParams, { replace: true });
setSearchParams(newSearchParams);
}
setEnteredSearchValue(value);
}
Expand All @@ -61,7 +61,7 @@ export const useSearchString = () => {
const newSearchParams = new URLSearchParams(searchParams);
if (newSearchParams.has('search')) {
newSearchParams.delete('search');
setSearchParams(newSearchParams, { replace: true });
setSearchParams(newSearchParams);
}
setSearchValue('');
setEnteredSearchValue('');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BookmarkFillIcon, BookmarkIcon } from '@navikt/aksel-icons';
import type { SavedSearchData, SavedSearchesFieldsFragment, SearchDataValueFilter } from 'bff-types-generated';
import type { SavedSearchData, SearchDataValueFilter } from 'bff-types-generated';
import type { ButtonHTMLAttributes, RefAttributes } from 'react';
import { useTranslation } from 'react-i18next';
import type { Filter } from '..';
Expand All @@ -8,19 +8,7 @@ import { useParties } from '../../api/useParties';
import { useSavedSearches } from '../../pages/SavedSearches/useSavedSearches';
import { useSearchString } from '../PageLayout/Search';
import { ProfileButton } from '../ProfileButton';
import { deepEqual } from './deepEqual';

const isSearchSavedAlready = (
savedSearches: SavedSearchesFieldsFragment[],
searchDataToCheck: SavedSearchData,
): SavedSearchesFieldsFragment | undefined => {
if (!searchDataToCheck) return undefined;
return savedSearches.find((s) =>
Object.keys(searchDataToCheck).every((key) =>
deepEqual(s.data[key as keyof SavedSearchData], searchDataToCheck[key as keyof SavedSearchData]),
),
);
};
import { getAlreadySavedSearch } from './alreadySaved.ts';

type SaveSearchButtonProps = {
disabled?: boolean;
Expand All @@ -46,21 +34,18 @@ export const SaveSearchButton = ({ disabled, className, activeFilters, viewType
searchString: enteredSearchValue,
};

const alreadyExistingSavedSearch = isSearchSavedAlready(
savedSearches ?? ([] as SavedSearchesFieldsFragment[]),
searchToCheckIfExistsAlready,
);
const alreadySavedSearch = getAlreadySavedSearch(searchToCheckIfExistsAlready, savedSearches);

if (disabled) {
return null;
}

if (alreadyExistingSavedSearch) {
if (alreadySavedSearch) {
return (
<ProfileButton
className={className}
size="xs"
onClick={() => deleteSearch(alreadyExistingSavedSearch.id)}
onClick={() => deleteSearch(alreadySavedSearch.id)}
variant="tertiary"
isLoading={isCTALoading}
>
Expand Down
Loading

0 comments on commit 52252dd

Please sign in to comment.