Skip to content

Commit

Permalink
fix: unable to save search (#1581)
Browse files Browse the repository at this point in the history
  • Loading branch information
seanes authored Dec 16, 2024
1 parent 50050db commit ec9e232
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { BookmarkFillIcon, BookmarkIcon } from '@navikt/aksel-icons';
import { useQueryClient } from '@tanstack/react-query';
import type { SavedSearchData, SavedSearchesFieldsFragment, SearchDataValueFilter } from 'bff-types-generated';
import type { ButtonHTMLAttributes, RefAttributes } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { Filter } from '..';
import { useSnackbar } from '..';
import { deleteSavedSearch } from '../../api/queries';
import type { InboxViewType } from '../../api/useDialogs.tsx';
import { useParties } from '../../api/useParties';
import { QUERY_KEYS } from '../../constants/queryKeys';
import { useSavedSearches } from '../../pages/SavedSearches/useSavedSearches';
import { useSearchString } from '../PageLayout/Search/useSearchString.tsx';
import { useSearchString } from '../PageLayout/Search';
import { ProfileButton } from '../ProfileButton';
import { deepEqual } from './deepEqual';

Expand All @@ -27,27 +23,22 @@ const isSearchSavedAlready = (
};

type SaveSearchButtonProps = {
onBtnClick: () => Promise<void>;
isLoading?: boolean;
disabled?: boolean;
viewType: InboxViewType;
activeFilters: Filter[];
} & ButtonHTMLAttributes<HTMLButtonElement> &
RefAttributes<HTMLButtonElement>;

export const SaveSearchButton = ({
disabled,
onBtnClick,
className,
isLoading,
activeFilters,
}: SaveSearchButtonProps) => {
export const SaveSearchButton = ({ disabled, className, activeFilters, viewType }: SaveSearchButtonProps) => {
const { t } = useTranslation();
const { selectedPartyIds } = useParties();
const { enteredSearchValue } = useSearchString();
const [isDeleting, setIsDeleting] = useState(false);
const { currentPartySavedSearches: savedSearches } = useSavedSearches(selectedPartyIds);
const { openSnackbar } = useSnackbar();
const queryClient = useQueryClient();
const {
currentPartySavedSearches: savedSearches,
isCTALoading,
saveSearch,
deleteSearch,
} = useSavedSearches(selectedPartyIds);

const searchToCheckIfExistsAlready: SavedSearchData = {
filters: activeFilters as SearchDataValueFilter[],
Expand All @@ -60,26 +51,6 @@ export const SaveSearchButton = ({
searchToCheckIfExistsAlready,
);

const handleDeleteSearch = async (savedSearchId: number) => {
setIsDeleting(true);
try {
await deleteSavedSearch(savedSearchId);
openSnackbar({
message: t('savedSearches.deleted_success'),
variant: 'success',
});
await queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SAVED_SEARCHES] });
} catch (error) {
console.error('Failed to delete saved search:', error);
openSnackbar({
message: t('savedSearches.delete_failed'),
variant: 'error',
});
} finally {
setIsDeleting(false);
}
};

if (disabled) {
return null;
}
Expand All @@ -89,9 +60,9 @@ export const SaveSearchButton = ({
<ProfileButton
className={className}
size="xs"
onClick={() => handleDeleteSearch(alreadyExistingSavedSearch.id)}
onClick={() => deleteSearch(alreadyExistingSavedSearch.id)}
variant="tertiary"
isLoading={isLoading || isDeleting}
isLoading={isCTALoading}
>
<BookmarkFillIcon fontSize="1.25rem" />
{t('filter_bar.saved_search')}
Expand All @@ -103,9 +74,11 @@ export const SaveSearchButton = ({
<ProfileButton
className={className}
size="xs"
onClick={onBtnClick}
onClick={() =>
saveSearch({ filters: activeFilters, selectedParties: selectedPartyIds, enteredSearchValue, viewType })
}
variant="tertiary"
isLoading={isLoading || isDeleting}
isLoading={isCTALoading}
>
<BookmarkIcon fontSize="1.25rem" />
{t('filter_bar.save_search')}
Expand Down
26 changes: 9 additions & 17 deletions packages/frontend/src/pages/Inbox/Inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { useSearchDialogs, useSearchString } from '../../components/PageLayout/S
import { SaveSearchButton } from '../../components/SavedSearchButton/SaveSearchButton.tsx';
import { FeatureFlagKeys, useFeatureFlag } from '../../featureFlags';
import { useFormat } from '../../i18n/useDateFnsLocale.tsx';
import { handleSaveSearch } from '../SavedSearches';
import { useSavedSearches } from '../SavedSearches/useSavedSearches.ts';
import { InboxSkeleton } from './InboxSkeleton.tsx';
import { filterDialogs, getFilterBarSettings } from './filters.ts';
import styles from './inbox.module.css';
Expand Down Expand Up @@ -57,12 +57,11 @@ export const Inbox = ({ viewType }: InboxProps) => {
const location = useLocation();
const { selectedItems, setSelectedItems, selectedItemCount, inSelectionMode } = useSelectedDialogs();
const { openSnackbar } = useSnackbar();
const [isSavingSearch, setIsSavingSearch] = useState<boolean>(false);
const { selectedParties } = useParties();
const { selectedParties, selectedPartyIds } = useParties();
const { enteredSearchValue } = useSearchString();
const [initialFilters, setInitialFilters] = useState<Filter[]>([]);
const [activeFilters, setActiveFilters] = useState<Filter[]>([]);

const { saveSearch } = useSavedSearches(selectedPartyIds);
const { searchResults, isFetching: isFetchingSearchResults } = useSearchDialogs({
parties: selectedParties,
searchValue: enteredSearchValue,
Expand Down Expand Up @@ -103,8 +102,8 @@ export const Inbox = ({ viewType }: InboxProps) => {
(d) => new Date(d.createdAt).getFullYear() === new Date().getFullYear(),
);

const areNotInInbox = itemsToDisplay.every((d) => ['drafts', 'sent', 'bin', 'archive'].includes(getViewType(d)));
if (!shouldShowSearchResults && areNotInInbox) {
const youAreNotInInbox = itemsToDisplay.every((d) => ['drafts', 'sent', 'bin', 'archive'].includes(getViewType(d)));
if (!shouldShowSearchResults && youAreNotInInbox) {
return [
{
label: t(`inbox.heading.title.${viewType}`, { count: itemsToDisplay.length }),
Expand Down Expand Up @@ -148,14 +147,6 @@ export const Inbox = ({ viewType }: InboxProps) => {

const savedSearchDisabled = !activeFilters?.length && !enteredSearchValue;
const showFilterButton = filterBarSettings.length > 0;
const saveSearchHandler = () =>
handleSaveSearch({
activeFilters,
selectedParties,
enteredSearchValue,
viewType,
setIsSavingSearch,
});

if (isFetchingSearchResults) {
return (
Expand Down Expand Up @@ -203,10 +194,9 @@ export const Inbox = ({ viewType }: InboxProps) => {
resultsCount={itemsToDisplay.length}
/>
<SaveSearchButton
onBtnClick={saveSearchHandler}
viewType={viewType}
className={styles.hideForSmallScreens}
disabled={savedSearchDisabled}
isLoading={isSavingSearch}
activeFilters={activeFilters}
/>
</div>
Expand All @@ -219,7 +209,9 @@ export const Inbox = ({ viewType }: InboxProps) => {
}
: undefined
}
onSaveBtnClick={saveSearchHandler}
onSaveBtnClick={() =>
saveSearch({ filters: activeFilters, selectedParties: selectedPartyIds, enteredSearchValue, viewType })
}
hideSaveButton={savedSearchDisabled}
/>
</section>
Expand Down
59 changes: 3 additions & 56 deletions packages/frontend/src/pages/SavedSearches/SavedSearchesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import { useQueryClient } from '@tanstack/react-query';
import type {
PartyFieldsFragment,
SavedSearchData,
SavedSearchesFieldsFragment,
SearchDataValueFilter,
} from 'bff-types-generated';
import { type Dispatch, type SetStateAction, useRef, useState } from 'react';
import type { SavedSearchesFieldsFragment } from 'bff-types-generated';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createSavedSearch } from '../../api/queries.ts';
import type { InboxViewType } from '../../api/useDialogs.tsx';
import { useParties } from '../../api/useParties.ts';
import { type Filter, PartyDropdown, useSnackbar } from '../../components';
import { QUERY_KEYS } from '../../constants/queryKeys.ts';
import { PartyDropdown } from '../../components';
import { useFormatDistance } from '../../i18n/useDateFnsLocale.tsx';
import { Routes } from '../Inbox/Inbox.tsx';
import { ConfirmDeleteDialog, type DeleteSearchDialogRef } from './ConfirmDeleteDialog/ConfirmDeleteDialog.tsx';
import {
EditSavedSearchDialog,
Expand All @@ -26,49 +16,6 @@ import styles from './savedSearchesPage.module.css';
import { autoFormatRelativeTime, getMostRecentSearchDate } from './searchUtils.ts';
import { useSavedSearches } from './useSavedSearches.ts';

interface HandleSaveSearchProps {
activeFilters: Filter[];
selectedParties: PartyFieldsFragment[];
enteredSearchValue: string;
viewType: InboxViewType;
setIsSavingSearch: Dispatch<SetStateAction<boolean>>;
}

export const handleSaveSearch = async ({
activeFilters,
selectedParties,
enteredSearchValue,
viewType,
setIsSavingSearch,
}: HandleSaveSearchProps): Promise<void> => {
const { t } = useTranslation();
const { openSnackbar } = useSnackbar();
const queryClient = useQueryClient();
try {
const data: SavedSearchData = {
filters: activeFilters as SearchDataValueFilter[],
urn: selectedParties.map((party) => party.party) as string[],
searchString: enteredSearchValue,
fromView: Routes[viewType],
};
setIsSavingSearch(true);
await createSavedSearch('', data);
openSnackbar({
message: t('savedSearches.saved_success'),
variant: 'success',
});
} catch (error) {
openSnackbar({
message: t('savedSearches.saved_error'),
variant: 'error',
});
console.error('Error creating saved search: ', error);
} finally {
setIsSavingSearch(false);
void queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SAVED_SEARCHES] });
}
};

export const SavedSearchesPage = () => {
const [selectedSavedSearch, setSelectedSavedSearch] = useState<SavedSearchesFieldsFragment | null>(null);
const [selectedDeleteItem, setSelectedDeleteItem] = useState<number | undefined>(undefined);
Expand Down
94 changes: 90 additions & 4 deletions packages/frontend/src/pages/SavedSearches/useSavedSearches.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
import { useQuery } from '@tanstack/react-query';
import type { SavedSearchesFieldsFragment, SavedSearchesQuery } from 'bff-types-generated';
import { fetchSavedSearches } from '../../api/queries.ts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type {
SavedSearchData,
SavedSearchesFieldsFragment,
SavedSearchesQuery,
SearchDataValueFilter,
} from 'bff-types-generated';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createSavedSearch, deleteSavedSearch, fetchSavedSearches } from '../../api/queries.ts';
import type { InboxViewType } from '../../api/useDialogs.tsx';
import { type Filter, useSnackbar } from '../../components';
import { QUERY_KEYS } from '../../constants/queryKeys.ts';
import { Routes } from '../Inbox/Inbox.tsx';

interface UseSavedSearchesOutput {
savedSearches: SavedSearchesFieldsFragment[];
isSuccess: boolean;
isCTALoading: boolean;
isLoading: boolean;
currentPartySavedSearches: SavedSearchesFieldsFragment[] | undefined;
saveSearch: (props: HandleSaveSearchProps) => Promise<void>;
deleteSearch: (savedSearchId: number) => Promise<void>;
}

interface HandleSaveSearchProps {
filters: Filter[];
selectedParties: string[];
enteredSearchValue: string;
viewType: InboxViewType;
}

export const filterSavedSearches = (
Expand All @@ -32,13 +52,79 @@ export const filterSavedSearches = (
};

export const useSavedSearches = (selectedPartyIds?: string[]): UseSavedSearchesOutput => {
const [isCTALoading, setIsCTALoading] = useState<boolean>(false);
const { t } = useTranslation();
const queryClient = useQueryClient();
const { openSnackbar } = useSnackbar();

const { data, isLoading, isSuccess } = useQuery<SavedSearchesQuery>({
queryKey: [QUERY_KEYS.SAVED_SEARCHES, selectedPartyIds],
queryFn: fetchSavedSearches,
retry: 3,
staleTime: 1000 * 60 * 20,
});

const savedSearchesUnfiltered = data?.savedSearches as SavedSearchesFieldsFragment[];
const currentPartySavedSearches = filterSavedSearches(savedSearchesUnfiltered, selectedPartyIds || []);
return { savedSearches: savedSearchesUnfiltered, isLoading, isSuccess, currentPartySavedSearches };

const saveSearch = async ({
filters,
selectedParties,
enteredSearchValue,
viewType,
}: HandleSaveSearchProps): Promise<void> => {
try {
setIsCTALoading(true);
const data: SavedSearchData = {
filters: filters as SearchDataValueFilter[],
urn: selectedParties,
searchString: enteredSearchValue,
fromView: Routes[viewType],
};
await createSavedSearch('', data);
openSnackbar({
message: t('savedSearches.saved_success'),
variant: 'success',
});
} catch (error) {
openSnackbar({
message: t('savedSearches.saved_error'),
variant: 'error',
});
console.error('Error creating saved search: ', error);
} finally {
void queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SAVED_SEARCHES] });
setIsCTALoading(false);
}
};

const deleteSearch = async (savedSearchId: number) => {
setIsCTALoading(true);
try {
await deleteSavedSearch(savedSearchId);
openSnackbar({
message: t('savedSearches.deleted_success'),
variant: 'success',
});
await queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SAVED_SEARCHES] });
} catch (error) {
console.error('Failed to delete saved search:', error);
openSnackbar({
message: t('savedSearches.delete_failed'),
variant: 'error',
});
} finally {
setIsCTALoading(false);
}
};

return {
savedSearches: savedSearchesUnfiltered,
isLoading,
isSuccess,
currentPartySavedSearches,
isCTALoading,
saveSearch,
deleteSearch,
};
};

0 comments on commit ec9e232

Please sign in to comment.