feat: create view from selected filters and sorts + switch to newly created view on view creation (#1301)

* feat: create view from selected filters and sorts

Closes #1292

* refactor: use selector to obtain table filters where query option

* refactor: activate exhaustive deps eslint rule for useRecoilCallback

* feat: switch to newly created view on view creation

Closes #1297

* refactor: code review

- use `useCallback` instead of `useRecoilCallback`
- rename `useTableViews` to `useViews`
- move filter-n-sort selectors to /states/selector subfolder
This commit is contained in:
Thaïs
2023-08-25 12:43:21 +02:00
committed by GitHub
parent de569f4c06
commit c3d6451dd0
23 changed files with 233 additions and 162 deletions

View File

@ -2,8 +2,8 @@ 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 { savedFiltersByKeyScopedSelector } from '@/ui/filter-n-sort/states/selectors/savedFiltersByKeyScopedSelector';
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';
@ -74,8 +74,8 @@ export const useViewFilters = <Entity>({
const [deleteViewFiltersMutation] = useDeleteViewFiltersMutation();
const createViewFilters = useCallback(
(filters: Filter[]) => {
if (!currentViewId || !filters.length) return;
(filters: Filter[], viewId = currentViewId) => {
if (!viewId || !filters.length) return;
return createViewFiltersMutation({
variables: {
@ -87,7 +87,7 @@ export const useViewFilters = <Entity>({
'',
operand: filter.operand,
value: filter.value,
viewId: currentViewId,
viewId,
})),
},
});
@ -168,5 +168,5 @@ export const useViewFilters = <Entity>({
refetch,
]);
return { persistFilters };
return { createViewFilters, persistFilters };
};

View File

@ -1,8 +1,8 @@
import { useCallback } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { savedSortsByKeyScopedSelector } from '@/ui/filter-n-sort/states/savedSortsByKeyScopedSelector';
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
import { savedSortsByKeyScopedSelector } from '@/ui/filter-n-sort/states/selectors/savedSortsByKeyScopedSelector';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import type {
SelectedSortType,
@ -77,8 +77,8 @@ export const useViewSorts = <SortField>({
const [deleteViewSortsMutation] = useDeleteViewSortsMutation();
const createViewSorts = useCallback(
(sorts: SelectedSortType<SortField>[]) => {
if (!currentViewId || !sorts.length) return;
(sorts: SelectedSortType<SortField>[], viewId = currentViewId) => {
if (!viewId || !sorts.length) return;
return createViewSortsMutation({
variables: {
@ -86,7 +86,7 @@ export const useViewSorts = <SortField>({
key: sort.key,
direction: sort.order as ViewSortDirection,
name: sort.label,
viewId: currentViewId,
viewId,
})),
},
});
@ -162,5 +162,5 @@ export const useViewSorts = <SortField>({
refetch,
]);
return { persistSorts };
return { createViewSorts, persistSorts };
};

View File

@ -1,6 +1,9 @@
import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import type { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
import type { SortType } from '@/ui/filter-n-sort/types/interface';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import {
type TableView,
@ -16,15 +19,21 @@ import {
useUpdateViewMutation,
ViewType,
} from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { GET_VIEWS } from '../graphql/queries/getViews';
import { useViewFilters } from './useViewFilters';
import { useViewSorts } from './useViewSorts';
export const useTableViews = ({
export const useViews = <Entity, SortField>({
availableFilters,
availableSorts,
objectId,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
objectId: 'company' | 'person';
}) => {
const [, setViews] = useRecoilScopedState(
const [views, setViews] = useRecoilScopedState(
tableViewsState,
TableRecoilScopeContext,
);
@ -32,16 +41,27 @@ export const useTableViews = ({
tableViewsByIdState,
TableRecoilScopeContext,
);
const selectedFilters = useRecoilScopedValue(
filtersScopedState,
TableRecoilScopeContext,
);
const selectedSorts = useRecoilScopedValue(
sortsScopedState,
TableRecoilScopeContext,
);
const [createViewsMutation] = useCreateViewsMutation();
const [updateViewMutation] = useUpdateViewMutation();
const [deleteViewsMutation] = useDeleteViewsMutation();
const { createViewFilters } = useViewFilters({ availableFilters });
const { createViewSorts } = useViewSorts({ availableSorts });
const createViews = useCallback(
(views: TableView[]) => {
async (views: TableView[]) => {
if (!views.length) return;
return createViewsMutation({
await createViewsMutation({
variables: {
data: views.map((view) => ({
...view,
@ -49,13 +69,26 @@ export const useTableViews = ({
type: ViewType.Table,
})),
},
refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
});
await Promise.all(
views.flatMap((view) => [
createViewFilters(selectedFilters, view.id),
createViewSorts(selectedSorts, view.id),
]),
);
},
[createViewsMutation, objectId],
[
createViewFilters,
createViewSorts,
createViewsMutation,
objectId,
selectedFilters,
selectedSorts,
],
);
const updateViewFields = useCallback(
const updateViews = useCallback(
(views: TableView[]) => {
if (!views.length) return;
@ -66,7 +99,6 @@ export const useTableViews = ({
data: { name: view.name },
where: { id: view.id },
},
refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
}),
),
);
@ -84,32 +116,29 @@ export const useTableViews = ({
id: { in: viewIds },
},
},
refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
});
},
[deleteViewsMutation],
);
useGetViewsQuery({
const { refetch } = useGetViewsQuery({
variables: {
where: {
objectId: { equals: objectId },
},
},
onCompleted: (data) => {
setViews(
data.views.map((view) => ({
id: view.id,
name: view.name,
})),
);
const nextViews = data.views.map((view) => ({
id: view.id,
name: view.name,
}));
if (!isDeeplyEqual(views, nextViews)) setViews(nextViews);
},
});
const handleViewsChange = useCallback(
async (nextViews: TableView[]) => {
setViews(nextViews);
const viewsToCreate = nextViews.filter(
(nextView) => !viewsById[nextView.id],
);
@ -120,15 +149,17 @@ export const useTableViews = ({
viewsById[nextView.id] &&
viewsById[nextView.id].name !== nextView.name,
);
await updateViewFields(viewsToUpdate);
await updateViews(viewsToUpdate);
const nextViewIds = nextViews.map((nextView) => nextView.id);
const viewIdsToDelete = Object.keys(viewsById).filter(
(previousViewId) => !nextViewIds.includes(previousViewId),
);
return deleteViews(viewIdsToDelete);
await deleteViews(viewIdsToDelete);
return refetch();
},
[createViews, deleteViews, setViews, updateViewFields, viewsById],
[createViews, deleteViews, refetch, updateViews, viewsById],
);
return { handleViewsChange };