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

@ -52,6 +52,11 @@ module.exports = {
'func-style':['error', 'declaration', { 'allowArrowFunctions': true }], 'func-style':['error', 'declaration', { 'allowArrowFunctions': true }],
"@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off",
"no-unused-vars": "off", "no-unused-vars": "off",
"react-hooks/exhaustive-deps": [
"warn", {
"additionalHooks": "useRecoilCallback"
}
],
"unused-imports/no-unused-imports": "warn", "unused-imports/no-unused-imports": "warn",
"unused-imports/no-unused-vars": [ "unused-imports/no-unused-vars": [
"warn", "warn",

View File

@ -3415,13 +3415,6 @@ export type CreateViewsMutationVariables = Exact<{
export type CreateViewsMutation = { __typename?: 'Mutation', createManyView: { __typename?: 'AffectedRows', count: number } }; export type CreateViewsMutation = { __typename?: 'Mutation', createManyView: { __typename?: 'AffectedRows', count: number } };
export type DeleteViewsMutationVariables = Exact<{
where: ViewWhereInput;
}>;
export type DeleteViewsMutation = { __typename?: 'Mutation', deleteManyView: { __typename?: 'AffectedRows', count: number } };
export type DeleteViewFiltersMutationVariables = Exact<{ export type DeleteViewFiltersMutationVariables = Exact<{
where: ViewFilterWhereInput; where: ViewFilterWhereInput;
}>; }>;
@ -3436,6 +3429,13 @@ export type DeleteViewSortsMutationVariables = Exact<{
export type DeleteViewSortsMutation = { __typename?: 'Mutation', deleteManyViewSort: { __typename?: 'AffectedRows', count: number } }; export type DeleteViewSortsMutation = { __typename?: 'Mutation', deleteManyViewSort: { __typename?: 'AffectedRows', count: number } };
export type DeleteViewsMutationVariables = Exact<{
where: ViewWhereInput;
}>;
export type DeleteViewsMutation = { __typename?: 'Mutation', deleteManyView: { __typename?: 'AffectedRows', count: number } };
export type UpdateViewMutationVariables = Exact<{ export type UpdateViewMutationVariables = Exact<{
data: ViewUpdateInput; data: ViewUpdateInput;
where: ViewWhereUniqueInput; where: ViewWhereUniqueInput;
@ -6247,39 +6247,6 @@ export function useCreateViewsMutation(baseOptions?: Apollo.MutationHookOptions<
export type CreateViewsMutationHookResult = ReturnType<typeof useCreateViewsMutation>; export type CreateViewsMutationHookResult = ReturnType<typeof useCreateViewsMutation>;
export type CreateViewsMutationResult = Apollo.MutationResult<CreateViewsMutation>; export type CreateViewsMutationResult = Apollo.MutationResult<CreateViewsMutation>;
export type CreateViewsMutationOptions = Apollo.BaseMutationOptions<CreateViewsMutation, CreateViewsMutationVariables>; export type CreateViewsMutationOptions = Apollo.BaseMutationOptions<CreateViewsMutation, CreateViewsMutationVariables>;
export const DeleteViewsDocument = gql`
mutation DeleteViews($where: ViewWhereInput!) {
deleteManyView(where: $where) {
count
}
}
`;
export type DeleteViewsMutationFn = Apollo.MutationFunction<DeleteViewsMutation, DeleteViewsMutationVariables>;
/**
* __useDeleteViewsMutation__
*
* To run a mutation, you first call `useDeleteViewsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteViewsMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteViewsMutation, { data, loading, error }] = useDeleteViewsMutation({
* variables: {
* where: // value for 'where'
* },
* });
*/
export function useDeleteViewsMutation(baseOptions?: Apollo.MutationHookOptions<DeleteViewsMutation, DeleteViewsMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteViewsMutation, DeleteViewsMutationVariables>(DeleteViewsDocument, options);
}
export type DeleteViewsMutationHookResult = ReturnType<typeof useDeleteViewsMutation>;
export type DeleteViewsMutationResult = Apollo.MutationResult<DeleteViewsMutation>;
export type DeleteViewsMutationOptions = Apollo.BaseMutationOptions<DeleteViewsMutation, DeleteViewsMutationVariables>;
export const DeleteViewFiltersDocument = gql` export const DeleteViewFiltersDocument = gql`
mutation DeleteViewFilters($where: ViewFilterWhereInput!) { mutation DeleteViewFilters($where: ViewFilterWhereInput!) {
deleteManyViewFilter(where: $where) { deleteManyViewFilter(where: $where) {
@ -6346,6 +6313,39 @@ export function useDeleteViewSortsMutation(baseOptions?: Apollo.MutationHookOpti
export type DeleteViewSortsMutationHookResult = ReturnType<typeof useDeleteViewSortsMutation>; export type DeleteViewSortsMutationHookResult = ReturnType<typeof useDeleteViewSortsMutation>;
export type DeleteViewSortsMutationResult = Apollo.MutationResult<DeleteViewSortsMutation>; export type DeleteViewSortsMutationResult = Apollo.MutationResult<DeleteViewSortsMutation>;
export type DeleteViewSortsMutationOptions = Apollo.BaseMutationOptions<DeleteViewSortsMutation, DeleteViewSortsMutationVariables>; export type DeleteViewSortsMutationOptions = Apollo.BaseMutationOptions<DeleteViewSortsMutation, DeleteViewSortsMutationVariables>;
export const DeleteViewsDocument = gql`
mutation DeleteViews($where: ViewWhereInput!) {
deleteManyView(where: $where) {
count
}
}
`;
export type DeleteViewsMutationFn = Apollo.MutationFunction<DeleteViewsMutation, DeleteViewsMutationVariables>;
/**
* __useDeleteViewsMutation__
*
* To run a mutation, you first call `useDeleteViewsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteViewsMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteViewsMutation, { data, loading, error }] = useDeleteViewsMutation({
* variables: {
* where: // value for 'where'
* },
* });
*/
export function useDeleteViewsMutation(baseOptions?: Apollo.MutationHookOptions<DeleteViewsMutation, DeleteViewsMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteViewsMutation, DeleteViewsMutationVariables>(DeleteViewsDocument, options);
}
export type DeleteViewsMutationHookResult = ReturnType<typeof useDeleteViewsMutation>;
export type DeleteViewsMutationResult = Apollo.MutationResult<DeleteViewsMutation>;
export type DeleteViewsMutationOptions = Apollo.BaseMutationOptions<DeleteViewsMutation, DeleteViewsMutationVariables>;
export const UpdateViewDocument = gql` export const UpdateViewDocument = gql`
mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) { mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) {
updateOneView(data: $data, where: $where) { updateOneView(data: $data, where: $where) {

View File

@ -1,20 +1,19 @@
import { useCallback, useMemo } from 'react'; import { useCallback } from 'react';
import { companyViewFields } from '@/companies/constants/companyViewFields'; import { companyViewFields } from '@/companies/constants/companyViewFields';
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries'; import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries'; import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport'; import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { filtersWhereScopedSelector } from '@/ui/filter-n-sort/states/selectors/filtersWhereScopedSelector';
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/sortsOrderByScopedSelector'; import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/selectors/sortsOrderByScopedSelector';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { EntityTable } from '@/ui/table/components/EntityTable'; import { EntityTable } from '@/ui/table/components/EntityTable';
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData'; import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem'; import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableViewFields } from '@/views/hooks/useTableViewFields'; import { useTableViewFields } from '@/views/hooks/useTableViewFields';
import { useTableViews } from '@/views/hooks/useTableViews';
import { useViewFilters } from '@/views/hooks/useViewFilters'; import { useViewFilters } from '@/views/hooks/useViewFilters';
import { useViews } from '@/views/hooks/useViews';
import { useViewSorts } from '@/views/hooks/useViewSorts'; import { useViewSorts } from '@/views/hooks/useViewSorts';
import { import {
SortOrder, SortOrder,
@ -30,11 +29,20 @@ export function CompanyTable() {
sortsOrderByScopedSelector, sortsOrderByScopedSelector,
TableRecoilScopeContext, TableRecoilScopeContext,
); );
const whereFilters = useRecoilScopedValue(
filtersWhereScopedSelector,
TableRecoilScopeContext,
);
const [updateEntityMutation] = useUpdateOneCompanyMutation(); const [updateEntityMutation] = useUpdateOneCompanyMutation();
const upsertEntityTableItem = useUpsertEntityTableItem(); const upsertEntityTableItem = useUpsertEntityTableItem();
const objectId = 'company'; const objectId = 'company';
const { handleViewsChange } = useTableViews({ objectId }); const { handleViewsChange } = useViews({
availableFilters: companiesFilters,
availableSorts,
objectId,
});
const { handleColumnsChange } = useTableViewFields({ const { handleColumnsChange } = useTableViewFields({
objectName: objectId, objectName: objectId,
viewFieldDefinitions: companyViewFields, viewFieldDefinitions: companyViewFields,
@ -46,15 +54,6 @@ export function CompanyTable() {
const { persistSorts } = useViewSorts({ availableSorts }); const { persistSorts } = useViewSorts({ availableSorts });
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport(); const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
const filters = useRecoilScopedValue(
filtersScopedState,
TableRecoilScopeContext,
);
const whereFilters = useMemo(() => {
return { AND: filters.map(turnFilterIntoWhereClause) };
}, [filters]) as any;
const { setContextMenuEntries } = useCompanyTableContextMenuEntries(); const { setContextMenuEntries } = useCompanyTableContextMenuEntries();
const { setActionBarEntries } = useCompanyTableActionBarEntries(); const { setActionBarEntries } = useCompanyTableActionBarEntries();

View File

@ -132,6 +132,6 @@ export function useSetPeopleEntityTable() {
set(isFetchingEntityTableDataState, false); set(isFetchingEntityTableDataState, false);
}, },
[], [currentLocation, resetTableRowSelection, tableContextScopeId],
); );
} }

View File

@ -1,20 +1,19 @@
import { useCallback, useMemo } from 'react'; import { useCallback } from 'react';
import { peopleViewFields } from '@/people/constants/peopleViewFields'; import { peopleViewFields } from '@/people/constants/peopleViewFields';
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries'; import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries'; import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport'; import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { filtersWhereScopedSelector } from '@/ui/filter-n-sort/states/selectors/filtersWhereScopedSelector';
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/sortsOrderByScopedSelector'; import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/selectors/sortsOrderByScopedSelector';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { EntityTable } from '@/ui/table/components/EntityTable'; import { EntityTable } from '@/ui/table/components/EntityTable';
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData'; import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem'; import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableViewFields } from '@/views/hooks/useTableViewFields'; import { useTableViewFields } from '@/views/hooks/useTableViewFields';
import { useTableViews } from '@/views/hooks/useTableViews';
import { useViewFilters } from '@/views/hooks/useViewFilters'; import { useViewFilters } from '@/views/hooks/useViewFilters';
import { useViews } from '@/views/hooks/useViews';
import { useViewSorts } from '@/views/hooks/useViewSorts'; import { useViewSorts } from '@/views/hooks/useViewSorts';
import { import {
SortOrder, SortOrder,
@ -30,12 +29,21 @@ export function PeopleTable() {
sortsOrderByScopedSelector, sortsOrderByScopedSelector,
TableRecoilScopeContext, TableRecoilScopeContext,
); );
const whereFilters = useRecoilScopedValue(
filtersWhereScopedSelector,
TableRecoilScopeContext,
);
const [updateEntityMutation] = useUpdateOnePersonMutation(); const [updateEntityMutation] = useUpdateOnePersonMutation();
const upsertEntityTableItem = useUpsertEntityTableItem(); const upsertEntityTableItem = useUpsertEntityTableItem();
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport(); const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
const objectId = 'person'; const objectId = 'person';
const { handleViewsChange } = useTableViews({ objectId }); const { handleViewsChange } = useViews({
availableFilters: peopleFilters,
availableSorts,
objectId,
});
const { handleColumnsChange } = useTableViewFields({ const { handleColumnsChange } = useTableViewFields({
objectName: objectId, objectName: objectId,
viewFieldDefinitions: peopleViewFields, viewFieldDefinitions: peopleViewFields,
@ -45,15 +53,6 @@ export function PeopleTable() {
}); });
const { persistSorts } = useViewSorts({ availableSorts }); const { persistSorts } = useViewSorts({ availableSorts });
const filters = useRecoilScopedValue(
filtersScopedState,
TableRecoilScopeContext,
);
const whereFilters = useMemo(() => {
return { AND: filters.map(turnFilterIntoWhereClause) };
}, [filters]) as any;
const { setContextMenuEntries } = usePersonTableContextMenuEntries(); const { setContextMenuEntries } = usePersonTableContextMenuEntries();
const { setActionBarEntries } = usePersonTableActionBarEntries(); const { setActionBarEntries } = usePersonTableActionBarEntries();

View File

@ -1,5 +1,5 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilValue } from 'recoil';
import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState'; import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState';
@ -9,10 +9,9 @@ import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export function useCurrentCardSelected() { export function useCurrentCardSelected() {
const currentCardId = useContext(BoardCardIdContext); const currentCardId = useContext(BoardCardIdContext);
const [isCardSelected] = useRecoilState( const isCardSelected = useRecoilValue(
isCardSelectedFamilyState(currentCardId ?? ''), isCardSelectedFamilyState(currentCardId ?? ''),
); );
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
const setCurrentCardSelected = useRecoilCallback( const setCurrentCardSelected = useRecoilCallback(
({ set }) => ({ set }) =>
@ -20,9 +19,9 @@ export function useCurrentCardSelected() {
if (!currentCardId) return; if (!currentCardId) return;
set(isCardSelectedFamilyState(currentCardId), selected); set(isCardSelectedFamilyState(currentCardId), selected);
setActionBarOpenState(true); set(actionBarOpenState, true);
}, },
[], [currentCardId],
); );
return { return {

View File

@ -5,8 +5,8 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { filtersScopedState } from '../filtersScopedState'; import { filtersScopedState } from '../filtersScopedState';
import { savedFiltersScopedState } from '../savedFiltersScopedState'; import { savedFiltersScopedState } from '../savedFiltersScopedState';
export const canPersistFiltersScopedState = selectorFamily({ export const canPersistFiltersScopedSelector = selectorFamily({
key: 'canPersistFiltersScopedState', key: 'canPersistFiltersScopedSelector',
get: get:
([scopeId, viewId]: [string, string | undefined]) => ([scopeId, viewId]: [string, string | undefined]) =>
({ get }) => ({ get }) =>

View File

@ -5,8 +5,8 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { savedSortsScopedState } from '../savedSortsScopedState'; import { savedSortsScopedState } from '../savedSortsScopedState';
import { sortsScopedState } from '../sortsScopedState'; import { sortsScopedState } from '../sortsScopedState';
export const canPersistSortsScopedState = selectorFamily({ export const canPersistSortsScopedSelector = selectorFamily({
key: 'canPersistSortsScopedState', key: 'canPersistSortsScopedSelector',
get: get:
([scopeId, viewId]: [string, string | undefined]) => ([scopeId, viewId]: [string, string | undefined]) =>
({ get }) => ({ get }) =>

View File

@ -0,0 +1,13 @@
import { selectorFamily } from 'recoil';
import { turnFilterIntoWhereClause } from '../../utils/turnFilterIntoWhereClause';
import { filtersScopedState } from '../filtersScopedState';
export const filtersWhereScopedSelector = selectorFamily({
key: 'filtersWhereScopedSelector',
get:
(param: string) =>
({ get }) => ({
AND: get(filtersScopedState(param)).map(turnFilterIntoWhereClause),
}),
});

View File

@ -1,8 +1,7 @@
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
import type { Filter } from '../types/Filter'; import type { Filter } from '../../types/Filter';
import { savedFiltersScopedState } from '../savedFiltersScopedState';
import { savedFiltersScopedState } from './savedFiltersScopedState';
export const savedFiltersByKeyScopedSelector = selectorFamily({ export const savedFiltersByKeyScopedSelector = selectorFamily({
key: 'savedFiltersByKeyScopedSelector', key: 'savedFiltersByKeyScopedSelector',

View File

@ -1,8 +1,7 @@
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
import type { SelectedSortType } from '../types/interface'; import type { SelectedSortType } from '../../types/interface';
import { savedSortsScopedState } from '../savedSortsScopedState';
import { savedSortsScopedState } from './savedSortsScopedState';
export const savedSortsByKeyScopedSelector = selectorFamily({ export const savedSortsByKeyScopedSelector = selectorFamily({
key: 'savedSortsByKeyScopedSelector', key: 'savedSortsByKeyScopedSelector',

View File

@ -1,8 +1,7 @@
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
import { reduceSortsToOrderBy } from '../helpers'; import { reduceSortsToOrderBy } from '../../helpers';
import { sortsScopedState } from '../sortsScopedState';
import { sortsScopedState } from './sortsScopedState';
export const sortsOrderByScopedSelector = selectorFamily({ export const sortsOrderByScopedSelector = selectorFamily({
key: 'sortsOrderByScopedSelector', key: 'sortsOrderByScopedSelector',

View File

@ -136,7 +136,7 @@ export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
setInitialPointerPositionX(null); setInitialPointerPositionX(null);
setResizedFieldId(null); setResizedFieldId(null);
}, },
[resizedFieldId, columnsById, setResizedFieldId], [resizedFieldId, columnsById, columns, onColumnsChange, setColumns],
); );
useTrackPointer({ useTrackPointer({

View File

@ -1,6 +1,5 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState'; import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
@ -13,8 +12,6 @@ export function useLeaveTableFocus() {
const disableSoftFocus = useDisableSoftFocus(); const disableSoftFocus = useDisableSoftFocus();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode(); const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const setHotkeyScope = useSetHotkeyScope();
return useRecoilCallback( return useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
() => { () => {
@ -37,6 +34,6 @@ export function useLeaveTableFocus() {
closeCurrentCellInEditMode(); closeCurrentCellInEditMode();
disableSoftFocus(); disableSoftFocus();
}, },
[setHotkeyScope, closeCurrentCellInEditMode, disableSoftFocus], [closeCurrentCellInEditMode, disableSoftFocus],
); );
} }

View File

@ -50,6 +50,6 @@ export function useSetEntityTableData() {
set(isFetchingEntityTableDataState, false); set(isFetchingEntityTableDataState, false);
}, },
[], [resetTableRowSelection, tableContextScopeId],
); );
} }

View File

@ -1,6 +1,6 @@
import { type FormEvent, useCallback, useRef, useState } from 'react'; import { type FormEvent, useCallback, useRef, useState } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -16,6 +16,10 @@ import type {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import { import {
IconChevronLeft, IconChevronLeft,
IconFileImport, IconFileImport,
@ -29,11 +33,12 @@ import {
visibleTableColumnsState, visibleTableColumnsState,
} from '@/ui/table/states/tableColumnsState'; } from '@/ui/table/states/tableColumnsState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
import { import {
currentTableViewIdState,
type TableView, type TableView,
tableViewEditModeState, tableViewEditModeState,
tableViewsByIdState, tableViewsByIdState,
@ -60,6 +65,8 @@ export function TableOptionsDropdownContent({
}: TableOptionsDropdownButtonProps) { }: TableOptionsDropdownButtonProps) {
const theme = useTheme(); const theme = useTheme();
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const { closeDropdownButton } = useDropdownButton({ key: 'options' }); const { closeDropdownButton } = useDropdownButton({ key: 'options' });
const [selectedOption, setSelectedOption] = useState<Option | undefined>( const [selectedOption, setSelectedOption] = useState<Option | undefined>(
@ -72,10 +79,6 @@ export function TableOptionsDropdownContent({
const [viewEditMode, setViewEditMode] = useRecoilState( const [viewEditMode, setViewEditMode] = useRecoilState(
tableViewEditModeState, tableViewEditModeState,
); );
const [views, setViews] = useRecoilScopedState(
tableViewsState,
TableRecoilScopeContext,
);
const visibleColumns = useRecoilValue(visibleTableColumnsState); const visibleColumns = useRecoilValue(visibleTableColumnsState);
const hiddenColumns = useRecoilValue(hiddenTableColumnsState); const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
const viewsById = useRecoilScopedValue( const viewsById = useRecoilScopedValue(
@ -124,31 +127,56 @@ export function TableOptionsDropdownContent({
} }
}, [setViewEditMode]); }, [setViewEditMode]);
const handleViewNameSubmit = useCallback( const handleViewNameSubmit = useRecoilCallback(
(event?: FormEvent) => { ({ set, snapshot }) =>
event?.preventDefault(); async (event?: FormEvent) => {
event?.preventDefault();
if (viewEditMode.mode && viewEditInputRef.current?.value) { const name = viewEditInputRef.current?.value;
const name = viewEditInputRef.current.value;
const nextViews =
viewEditMode.mode === 'create'
? [...views, { id: v4(), name }]
: views.map((view) =>
view.id === viewEditMode.viewId ? { ...view, name } : view,
);
(onViewsChange ?? setViews)(nextViews); if (!viewEditMode.mode || !name) {
} return resetViewEditMode();
}
resetViewEditMode(); const views = await snapshot.getPromise(tableViewsState(tableScopeId));
},
if (viewEditMode.mode === 'create') {
const viewToCreate = { id: v4(), name };
const nextViews = [...views, viewToCreate];
const selectedFilters = await snapshot.getPromise(
filtersScopedState(tableScopeId),
);
set(savedFiltersScopedState(viewToCreate.id), selectedFilters);
const selectedSorts = await snapshot.getPromise(
sortsScopedState(tableScopeId),
);
set(savedSortsScopedState(viewToCreate.id), selectedSorts);
set(tableViewsState(tableScopeId), nextViews);
await Promise.resolve(onViewsChange?.(nextViews));
set(currentTableViewIdState(tableScopeId), viewToCreate.id);
}
if (viewEditMode.mode === 'edit') {
const nextViews = views.map((view) =>
view.id === viewEditMode.viewId ? { ...view, name } : view,
);
set(tableViewsState(tableScopeId), nextViews);
await Promise.resolve(onViewsChange?.(nextViews));
}
return resetViewEditMode();
},
[ [
onViewsChange, onViewsChange,
resetViewEditMode, resetViewEditMode,
setViews, tableScopeId,
viewEditMode.mode, viewEditMode.mode,
viewEditMode.viewId, viewEditMode.viewId,
views,
], ],
); );

View File

@ -97,7 +97,7 @@ export const TableUpdateViewButtonGroup = ({
); );
set(savedSortsScopedState(currentViewId), selectedSorts); set(savedSortsScopedState(currentViewId), selectedSorts);
}, },
[currentViewId, onViewSubmit], [currentViewId, onViewSubmit, tableScopeId],
); );
useScopedHotkeys( useScopedHotkeys(

View File

@ -73,6 +73,10 @@ export const TableViewsDropdownButton = ({
key: 'options', key: 'options',
}); });
const [, setCurrentViewId] = useRecoilScopedState(
currentTableViewIdState,
TableRecoilScopeContext,
);
const currentView = useRecoilScopedValue( const currentView = useRecoilScopedValue(
currentTableViewState, currentTableViewState,
TableRecoilScopeContext, TableRecoilScopeContext,
@ -81,10 +85,6 @@ export const TableViewsDropdownButton = ({
tableViewsState, tableViewsState,
TableRecoilScopeContext, TableRecoilScopeContext,
); );
const [, setCurrentViewId] = useRecoilScopedState(
currentTableViewIdState,
TableRecoilScopeContext,
);
const setViewEditMode = useSetRecoilState(tableViewEditModeState); const setViewEditMode = useSetRecoilState(tableViewEditModeState);
const { const {
@ -104,10 +104,10 @@ export const TableViewsDropdownButton = ({
set(filtersScopedState(tableScopeId), savedFilters); set(filtersScopedState(tableScopeId), savedFilters);
set(sortsScopedState(tableScopeId), savedSorts); set(sortsScopedState(tableScopeId), savedSorts);
setCurrentViewId(viewId); set(currentTableViewIdState(tableScopeId), viewId);
setIsUnfolded(false); setIsUnfolded(false);
}, },
[setCurrentViewId], [tableScopeId],
); );
const handleAddViewButtonClick = useCallback(() => { const handleAddViewButtonClick = useCallback(() => {
@ -126,12 +126,15 @@ export const TableViewsDropdownButton = ({
); );
const handleDeleteViewButtonClick = useCallback( const handleDeleteViewButtonClick = useCallback(
(event: MouseEvent<HTMLButtonElement>, viewId: string) => { async (event: MouseEvent<HTMLButtonElement>, viewId: string) => {
event.stopPropagation(); event.stopPropagation();
if (currentView?.id === viewId) setCurrentViewId(undefined); if (currentView?.id === viewId) setCurrentViewId(undefined);
(onViewsChange ?? setViews)(views.filter((view) => view.id !== viewId)); const nextViews = views.filter((view) => view.id !== viewId);
setViews(nextViews);
await Promise.resolve(onViewsChange?.(nextViews));
setIsUnfolded(false); setIsUnfolded(false);
}, },
[currentView?.id, onViewsChange, setCurrentViewId, setViews, views], [currentView?.id, onViewsChange, setCurrentViewId, setViews, views],

View File

@ -27,7 +27,7 @@ export function usePreviousHotkeyScope() {
set(previousHotkeyScopeState, null); set(previousHotkeyScopeState, null);
}, },
[], [setHotkeyScope],
); );
const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback( const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback(
@ -40,7 +40,7 @@ export function usePreviousHotkeyScope() {
setHotkeyScope(scope, customScopes); setHotkeyScope(scope, customScopes);
set(previousHotkeyScopeState, currentHotkeyScope); set(previousHotkeyScopeState, currentHotkeyScope);
}, },
[], [setHotkeyScope],
); );
return { return {

View File

@ -2,8 +2,8 @@ import { useCallback } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; 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 { 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 { Filter } from '@/ui/filter-n-sort/types/Filter';
import type { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity'; import type { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
@ -74,8 +74,8 @@ export const useViewFilters = <Entity>({
const [deleteViewFiltersMutation] = useDeleteViewFiltersMutation(); const [deleteViewFiltersMutation] = useDeleteViewFiltersMutation();
const createViewFilters = useCallback( const createViewFilters = useCallback(
(filters: Filter[]) => { (filters: Filter[], viewId = currentViewId) => {
if (!currentViewId || !filters.length) return; if (!viewId || !filters.length) return;
return createViewFiltersMutation({ return createViewFiltersMutation({
variables: { variables: {
@ -87,7 +87,7 @@ export const useViewFilters = <Entity>({
'', '',
operand: filter.operand, operand: filter.operand,
value: filter.value, value: filter.value,
viewId: currentViewId, viewId,
})), })),
}, },
}); });
@ -168,5 +168,5 @@ export const useViewFilters = <Entity>({
refetch, refetch,
]); ]);
return { persistFilters }; return { createViewFilters, persistFilters };
}; };

View File

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

View File

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