feat: persist view filters and sorts on Update View button click (#1290)
* feat: add viewFilters table Closes #1121 * feat: add Update View button + Create View dropdown Closes #1124, #1289 * feat: add View Filter resolvers * feat: persist view filters and sorts on Update View button click Closes #1123 * refactor: code review - Rename recoil selectors - Rename filters `field` property to `key`
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_VIEW_FILTERS = gql`
|
||||
mutation CreateViewFilters($data: [ViewFilterCreateManyInput!]!) {
|
||||
createManyViewFilter(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_VIEW_FILTERS = gql`
|
||||
mutation DeleteViewFilters($where: ViewFilterWhereInput!) {
|
||||
deleteManyViewFilter(where: $where) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,16 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_VIEW_FILTER = gql`
|
||||
mutation UpdateViewFilter(
|
||||
$data: ViewFilterUpdateInput!
|
||||
$where: ViewFilterWhereUniqueInput!
|
||||
) {
|
||||
viewFilter: updateOneViewFilter(data: $data, where: $where) {
|
||||
displayValue
|
||||
key
|
||||
name
|
||||
operand
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
13
front/src/modules/views/graphql/queries/getViewFilters.ts
Normal file
13
front/src/modules/views/graphql/queries/getViewFilters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_VIEW_FILTERS = gql`
|
||||
query GetViewFilters($where: ViewFilterWhereInput) {
|
||||
viewFilters: findManyViewFilter(where: $where) {
|
||||
displayValue
|
||||
key
|
||||
name
|
||||
operand
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
172
front/src/modules/views/hooks/useViewFilters.ts
Normal file
172
front/src/modules/views/hooks/useViewFilters.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { savedFiltersByKeyScopedSelector } from '@/ui/filter-n-sort/states/savedFiltersByKeyScopedSelector';
|
||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||
import type { Filter } from '@/ui/filter-n-sort/types/Filter';
|
||||
import type { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import {
|
||||
useCreateViewFiltersMutation,
|
||||
useDeleteViewFiltersMutation,
|
||||
useGetViewFiltersQuery,
|
||||
useUpdateViewFilterMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useViewFilters = <Entity>({
|
||||
availableFilters,
|
||||
}: {
|
||||
availableFilters: FilterDefinitionByEntity<Entity>[];
|
||||
}) => {
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [filters, setFilters] = useRecoilScopedState(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [, setSavedFilters] = useRecoilState(
|
||||
savedFiltersScopedState(currentViewId),
|
||||
);
|
||||
const savedFiltersByKey = useRecoilValue(
|
||||
savedFiltersByKeyScopedSelector(currentViewId),
|
||||
);
|
||||
|
||||
const { refetch } = useGetViewFiltersQuery({
|
||||
skip: !currentViewId,
|
||||
variables: {
|
||||
where: {
|
||||
viewId: { equals: currentViewId },
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
const nextFilters = data.viewFilters
|
||||
.map(({ __typename, name: _name, ...viewFilter }) => {
|
||||
const availableFilter = availableFilters.find(
|
||||
(filter) => filter.key === viewFilter.key,
|
||||
);
|
||||
|
||||
return availableFilter
|
||||
? {
|
||||
...viewFilter,
|
||||
displayValue: viewFilter.displayValue ?? viewFilter.value,
|
||||
type: availableFilter.type,
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((filter): filter is Filter => !!filter);
|
||||
|
||||
if (!isDeeplyEqual(filters, nextFilters)) {
|
||||
setSavedFilters(nextFilters);
|
||||
setFilters(nextFilters);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [createViewFiltersMutation] = useCreateViewFiltersMutation();
|
||||
const [updateViewFilterMutation] = useUpdateViewFilterMutation();
|
||||
const [deleteViewFiltersMutation] = useDeleteViewFiltersMutation();
|
||||
|
||||
const createViewFilters = useCallback(
|
||||
(filters: Filter[]) => {
|
||||
if (!currentViewId || !filters.length) return;
|
||||
|
||||
return createViewFiltersMutation({
|
||||
variables: {
|
||||
data: filters.map((filter) => ({
|
||||
displayValue: filter.displayValue ?? filter.value,
|
||||
key: filter.key,
|
||||
name:
|
||||
availableFilters.find(({ key }) => key === filter.key)?.label ??
|
||||
'',
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
},
|
||||
});
|
||||
},
|
||||
[availableFilters, createViewFiltersMutation, currentViewId],
|
||||
);
|
||||
|
||||
const updateViewFilters = useCallback(
|
||||
(filters: Filter[]) => {
|
||||
if (!currentViewId || !filters.length) return;
|
||||
|
||||
return Promise.all(
|
||||
filters.map((filter) =>
|
||||
updateViewFilterMutation({
|
||||
variables: {
|
||||
data: {
|
||||
displayValue: filter.displayValue ?? filter.value,
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
},
|
||||
where: {
|
||||
viewId_key: { key: filter.key, viewId: currentViewId },
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[currentViewId, updateViewFilterMutation],
|
||||
);
|
||||
|
||||
const deleteViewFilters = useCallback(
|
||||
(filterKeys: string[]) => {
|
||||
if (!currentViewId || !filterKeys.length) return;
|
||||
|
||||
return deleteViewFiltersMutation({
|
||||
variables: {
|
||||
where: {
|
||||
key: { in: filterKeys },
|
||||
viewId: { equals: currentViewId },
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
[currentViewId, deleteViewFiltersMutation],
|
||||
);
|
||||
|
||||
const persistFilters = useCallback(async () => {
|
||||
if (!currentViewId) return;
|
||||
|
||||
const filtersToCreate = filters.filter(
|
||||
(filter) => !savedFiltersByKey[filter.key],
|
||||
);
|
||||
await createViewFilters(filtersToCreate);
|
||||
|
||||
const filtersToUpdate = filters.filter(
|
||||
(filter) =>
|
||||
savedFiltersByKey[filter.key] &&
|
||||
(savedFiltersByKey[filter.key].operand !== filter.operand ||
|
||||
savedFiltersByKey[filter.key].value !== filter.value),
|
||||
);
|
||||
await updateViewFilters(filtersToUpdate);
|
||||
|
||||
const filterKeys = filters.map((filter) => filter.key);
|
||||
const filterKeysToDelete = Object.keys(savedFiltersByKey).filter(
|
||||
(previousFilterKey) => !filterKeys.includes(previousFilterKey),
|
||||
);
|
||||
await deleteViewFilters(filterKeysToDelete);
|
||||
|
||||
return refetch();
|
||||
}, [
|
||||
currentViewId,
|
||||
filters,
|
||||
createViewFilters,
|
||||
updateViewFilters,
|
||||
savedFiltersByKey,
|
||||
deleteViewFilters,
|
||||
refetch,
|
||||
]);
|
||||
|
||||
return { persistFilters };
|
||||
};
|
||||
@ -1,10 +1,9 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
sortsByKeyScopedState,
|
||||
sortScopedState,
|
||||
} from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { savedSortsByKeyScopedSelector } from '@/ui/filter-n-sort/states/savedSortsByKeyScopedSelector';
|
||||
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||
import type {
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
@ -20,8 +19,7 @@ import {
|
||||
useUpdateViewSortMutation,
|
||||
ViewSortDirection,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_VIEW_SORTS } from '../graphql/queries/getViewSorts';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useViewSorts = <SortField>({
|
||||
availableSorts,
|
||||
@ -32,20 +30,18 @@ export const useViewSorts = <SortField>({
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [, setSorts] = useRecoilScopedState(
|
||||
sortScopedState,
|
||||
const [sorts, setSorts] = useRecoilScopedState(
|
||||
sortsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const sortsByKey = useRecoilScopedValue(
|
||||
sortsByKeyScopedState,
|
||||
TableRecoilScopeContext,
|
||||
const [, setSavedSorts] = useRecoilState(
|
||||
savedSortsScopedState(currentViewId),
|
||||
);
|
||||
const savedSortsByKey = useRecoilValue(
|
||||
savedSortsByKeyScopedSelector(currentViewId),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentViewId) setSorts([]);
|
||||
}, [currentViewId, setSorts]);
|
||||
|
||||
useGetViewSortsQuery({
|
||||
const { refetch } = useGetViewSortsQuery({
|
||||
skip: !currentViewId,
|
||||
variables: {
|
||||
where: {
|
||||
@ -53,23 +49,26 @@ export const useViewSorts = <SortField>({
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
setSorts(
|
||||
data.viewSorts
|
||||
.map((viewSort) => {
|
||||
const availableSort = availableSorts.find(
|
||||
(sort) => sort.key === viewSort.key,
|
||||
);
|
||||
const nextSorts = data.viewSorts
|
||||
.map((viewSort) => {
|
||||
const availableSort = availableSorts.find(
|
||||
(sort) => sort.key === viewSort.key,
|
||||
);
|
||||
|
||||
return availableSort
|
||||
? {
|
||||
...availableSort,
|
||||
label: viewSort.name,
|
||||
order: viewSort.direction.toLowerCase(),
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((sort): sort is SelectedSortType<SortField> => !!sort),
|
||||
);
|
||||
return availableSort
|
||||
? {
|
||||
...availableSort,
|
||||
label: viewSort.name,
|
||||
order: viewSort.direction.toLowerCase(),
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((sort): sort is SelectedSortType<SortField> => !!sort);
|
||||
|
||||
if (!isDeeplyEqual(sorts, nextSorts)) {
|
||||
setSavedSorts(nextSorts);
|
||||
setSorts(nextSorts);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -90,7 +89,6 @@ export const useViewSorts = <SortField>({
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
|
||||
});
|
||||
},
|
||||
[createViewSortsMutation, currentViewId],
|
||||
@ -111,7 +109,6 @@ export const useViewSorts = <SortField>({
|
||||
viewId_key: { key: sort.key, viewId: currentViewId },
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -130,45 +127,40 @@ export const useViewSorts = <SortField>({
|
||||
viewId: { equals: currentViewId },
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_SORTS) ?? ''],
|
||||
});
|
||||
},
|
||||
[currentViewId, deleteViewSortsMutation],
|
||||
);
|
||||
|
||||
const handleSortsChange = useCallback(
|
||||
async (nextSorts: SelectedSortType<SortField>[]) => {
|
||||
if (!currentViewId) return;
|
||||
const persistSorts = useCallback(async () => {
|
||||
if (!currentViewId) return;
|
||||
|
||||
setSorts(nextSorts);
|
||||
const sortsToCreate = sorts.filter((sort) => !savedSortsByKey[sort.key]);
|
||||
await createViewSorts(sortsToCreate);
|
||||
|
||||
const sortsToCreate = nextSorts.filter(
|
||||
(nextSort) => !sortsByKey[nextSort.key],
|
||||
);
|
||||
await createViewSorts(sortsToCreate);
|
||||
const sortsToUpdate = sorts.filter(
|
||||
(sort) =>
|
||||
savedSortsByKey[sort.key] &&
|
||||
savedSortsByKey[sort.key].order !== sort.order,
|
||||
);
|
||||
await updateViewSorts(sortsToUpdate);
|
||||
|
||||
const sortsToUpdate = nextSorts.filter(
|
||||
(nextSort) =>
|
||||
sortsByKey[nextSort.key] &&
|
||||
sortsByKey[nextSort.key].order !== nextSort.order,
|
||||
);
|
||||
await updateViewSorts(sortsToUpdate);
|
||||
const sortKeys = sorts.map((sort) => sort.key);
|
||||
const sortKeysToDelete = Object.keys(savedSortsByKey).filter(
|
||||
(previousSortKey) => !sortKeys.includes(previousSortKey),
|
||||
);
|
||||
await deleteViewSorts(sortKeysToDelete);
|
||||
|
||||
const nextSortKeys = nextSorts.map((nextSort) => nextSort.key);
|
||||
const sortKeysToDelete = Object.keys(sortsByKey).filter(
|
||||
(previousSortKey) => !nextSortKeys.includes(previousSortKey),
|
||||
);
|
||||
return deleteViewSorts(sortKeysToDelete);
|
||||
},
|
||||
[
|
||||
createViewSorts,
|
||||
currentViewId,
|
||||
deleteViewSorts,
|
||||
setSorts,
|
||||
sortsByKey,
|
||||
updateViewSorts,
|
||||
],
|
||||
);
|
||||
return refetch();
|
||||
}, [
|
||||
currentViewId,
|
||||
sorts,
|
||||
createViewSorts,
|
||||
updateViewSorts,
|
||||
savedSortsByKey,
|
||||
deleteViewSorts,
|
||||
refetch,
|
||||
]);
|
||||
|
||||
return { handleSortsChange };
|
||||
return { persistSorts };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user