feat: create view from current table columns + persist view fields on… (#1308)

feat: create view from current table columns + persist view fields on Update View button click

Closes #1302, Closes #1307
This commit is contained in:
Thaïs
2023-08-25 18:21:27 +02:00
committed by GitHub
parent f520a00909
commit 432fea0ee3
27 changed files with 432 additions and 282 deletions

View File

@ -1,18 +1,17 @@
import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import {
import type {
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldTextMetadata,
} from '@/ui/editable-field/types/ViewField';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import {
tableColumnsByIdState,
tableColumnsState,
} from '@/ui/table/states/tableColumnsState';
import { savedTableColumnsScopedState } from '@/ui/table/states/savedTableColumnsScopedState';
import { savedTableColumnsByIdScopedSelector } from '@/ui/table/states/selectors/savedTableColumnsByIdScopedSelector';
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
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 {
SortOrder,
@ -20,8 +19,7 @@ import {
useGetViewFieldsQuery,
useUpdateViewFieldMutation,
} from '~/generated/graphql';
import { GET_VIEW_FIELDS } from '../graphql/queries/getViewFields';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = {
type: 'text',
@ -51,24 +49,34 @@ export const useTableViewFields = ({
currentTableViewIdState,
TableRecoilScopeContext,
);
const setColumns = useSetRecoilState(tableColumnsState);
const columnsById = useRecoilValue(tableColumnsByIdState);
const [columns, setColumns] = useRecoilScopedState(
tableColumnsScopedState,
TableRecoilScopeContext,
);
const setSavedColumns = useSetRecoilState(
savedTableColumnsScopedState(currentViewId),
);
const savedColumnsById = useRecoilValue(
savedTableColumnsByIdScopedSelector(currentViewId),
);
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const createViewFields = useCallback(
(columns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
(
columns: ViewFieldDefinition<ViewFieldMetadata>[],
viewId = currentViewId,
) => {
if (!columns.length) return;
return createViewFieldsMutation({
variables: {
data: columns.map((column) => ({
...toViewFieldInput(objectName, column),
viewId: currentViewId,
viewId,
})),
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
});
},
[createViewFieldsMutation, currentViewId, objectName],
@ -88,7 +96,6 @@ export const useTableViewFields = ({
},
where: { id: column.id },
},
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
}),
),
);
@ -96,7 +103,7 @@ export const useTableViewFields = ({
[updateViewFieldMutation],
);
useGetViewFieldsQuery({
const { refetch } = useGetViewFieldsQuery({
variables: {
orderBy: { index: SortOrder.Asc },
where: {
@ -104,50 +111,44 @@ export const useTableViewFields = ({
viewId: { equals: currentViewId ?? null },
},
},
onCompleted: (data) => {
if (data.viewFields.length) {
setColumns(
data.viewFields.map<ViewFieldDefinition<ViewFieldMetadata>>(
(viewField) => ({
...(viewFieldDefinitions.find(
({ columnLabel }) => viewField.fieldName === columnLabel,
) || { metadata: DEFAULT_VIEW_FIELD_METADATA }),
id: viewField.id,
columnLabel: viewField.fieldName,
columnOrder: viewField.index,
columnSize: viewField.sizeInPx,
isVisible: viewField.isVisible,
}),
),
);
return;
onCompleted: async (data) => {
if (!data.viewFields.length) {
// Populate if empty
await createViewFields(viewFieldDefinitions);
return refetch();
}
// Populate if empty
createViewFields(viewFieldDefinitions);
const nextColumns = data.viewFields.map<
ViewFieldDefinition<ViewFieldMetadata>
>((viewField) => ({
...(viewFieldDefinitions.find(
({ columnLabel }) => viewField.fieldName === columnLabel,
) || { metadata: DEFAULT_VIEW_FIELD_METADATA }),
id: viewField.id,
columnLabel: viewField.fieldName,
columnOrder: viewField.index,
columnSize: viewField.sizeInPx,
isVisible: viewField.isVisible,
}));
if (!isDeeplyEqual(columns, nextColumns)) {
setSavedColumns(nextColumns);
setColumns(nextColumns);
}
},
});
const handleColumnsChange = useCallback(
async (nextColumns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
setColumns(nextColumns);
const persistColumns = useCallback(async () => {
const viewFieldsToUpdate = columns.filter(
(column) =>
savedColumnsById[column.id] &&
(savedColumnsById[column.id].columnSize !== column.columnSize ||
savedColumnsById[column.id].isVisible !== column.isVisible),
);
await updateViewFields(viewFieldsToUpdate);
const viewFieldsToCreate = nextColumns.filter(
(nextColumn) => !columnsById[nextColumn.id],
);
await createViewFields(viewFieldsToCreate);
return refetch();
}, [columns, refetch, savedColumnsById, updateViewFields]);
const viewFieldsToUpdate = nextColumns.filter(
(nextColumn) =>
columnsById[nextColumn.id] &&
(columnsById[nextColumn.id].columnSize !== nextColumn.columnSize ||
columnsById[nextColumn.id].isVisible !== nextColumn.isVisible),
);
await updateViewFields(viewFieldsToUpdate);
},
[columnsById, createViewFields, setColumns, updateViewFields],
);
return { handleColumnsChange };
return { createViewFields, persistColumns };
};

View File

@ -0,0 +1,92 @@
import { useCallback } from 'react';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
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 { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableViewFields } from './useTableViewFields';
import { useViewFilters } from './useViewFilters';
import { useViews } from './useViews';
import { useViewSorts } from './useViewSorts';
export const useTableViews = <Entity, SortField>({
availableFilters,
availableSorts,
objectId,
viewFieldDefinitions,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
objectId: 'company' | 'person';
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
}) => {
const currentViewId = useRecoilScopedValue(
currentTableViewIdState,
TableRecoilScopeContext,
);
const currentColumns = useRecoilScopedValue(
tableColumnsScopedState,
TableRecoilScopeContext,
);
const selectedFilters = useRecoilScopedValue(
filtersScopedState,
TableRecoilScopeContext,
);
const selectedSorts = useRecoilScopedValue(
sortsScopedState,
TableRecoilScopeContext,
);
const { createViewFields, persistColumns } = useTableViewFields({
objectName: objectId,
viewFieldDefinitions,
});
const { createViewFilters, persistFilters } = useViewFilters({
availableFilters,
currentViewId,
scopeContext: TableRecoilScopeContext,
});
const { createViewSorts, persistSorts } = useViewSorts({
availableSorts,
currentViewId,
scopeContext: TableRecoilScopeContext,
});
const handleViewCreate = useCallback(
async (viewId: string) => {
await createViewFields(currentColumns, viewId);
await createViewFilters(selectedFilters, viewId);
await createViewSorts(selectedSorts, viewId);
},
[
createViewFields,
createViewFilters,
createViewSorts,
currentColumns,
selectedFilters,
selectedSorts,
],
);
const handleViewSubmit = useCallback(async () => {
await persistColumns();
await persistFilters();
await persistSorts();
}, [persistColumns, persistFilters, persistSorts]);
const { handleViewsChange } = useViews({
objectId,
onViewCreate: handleViewCreate,
});
return { handleViewsChange, handleViewSubmit };
};

View File

@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { Context, useCallback } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
@ -6,10 +6,7 @@ import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersS
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';
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,
@ -20,16 +17,16 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useViewFilters = <Entity>({
availableFilters,
currentViewId,
scopeContext,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
currentViewId: string | undefined;
scopeContext: Context<string | null>;
}) => {
const currentViewId = useRecoilScopedValue(
currentTableViewIdState,
TableRecoilScopeContext,
);
const [filters, setFilters] = useRecoilScopedState(
filtersScopedState,
TableRecoilScopeContext,
scopeContext,
);
const [, setSavedFilters] = useRecoilState(
savedFiltersScopedState(currentViewId),

View File

@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { Context, useCallback } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
@ -8,10 +8,7 @@ import type {
SelectedSortType,
SortType,
} from '@/ui/filter-n-sort/types/interface';
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 {
useCreateViewSortsMutation,
useDeleteViewSortsMutation,
@ -23,16 +20,16 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useViewSorts = <SortField>({
availableSorts,
currentViewId,
scopeContext,
}: {
availableSorts: SortType<SortField>[];
currentViewId: string | undefined;
scopeContext: Context<string | null>;
}) => {
const currentViewId = useRecoilScopedValue(
currentTableViewIdState,
TableRecoilScopeContext,
);
const [sorts, setSorts] = useRecoilScopedState(
sortsScopedState,
TableRecoilScopeContext,
scopeContext,
);
const [, setSavedSorts] = useRecoilState(
savedSortsScopedState(currentViewId),

View File

@ -1,9 +1,5 @@
import { useCallback } from 'react';
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,
@ -21,17 +17,12 @@ import {
} from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useViewFilters } from './useViewFilters';
import { useViewSorts } from './useViewSorts';
export const useViews = <Entity, SortField>({
availableFilters,
availableSorts,
export const useViews = ({
objectId,
onViewCreate,
}: {
availableFilters: FilterDefinitionByEntity<Entity>[];
availableSorts: SortType<SortField>[];
objectId: 'company' | 'person';
onViewCreate: (viewId: string) => Promise<void>;
}) => {
const [views, setViews] = useRecoilScopedState(
tableViewsState,
@ -41,22 +32,11 @@ export const useViews = <Entity, SortField>({
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(
async (views: TableView[]) => {
if (!views.length) return;
@ -71,21 +51,9 @@ export const useViews = <Entity, SortField>({
},
});
await Promise.all(
views.flatMap((view) => [
createViewFilters(selectedFilters, view.id),
createViewSorts(selectedSorts, view.id),
]),
);
await Promise.all(views.map((view) => onViewCreate(view.id)));
},
[
createViewFilters,
createViewSorts,
createViewsMutation,
objectId,
selectedFilters,
selectedSorts,
],
[createViewsMutation, objectId, onViewCreate],
);
const updateViews = useCallback(