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:
@ -52,6 +52,11 @@ module.exports = {
|
||||
'func-style':['error', 'declaration', { 'allowArrowFunctions': true }],
|
||||
"@typescript-eslint/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-vars": [
|
||||
"warn",
|
||||
|
||||
@ -3415,13 +3415,6 @@ export type CreateViewsMutationVariables = Exact<{
|
||||
|
||||
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<{
|
||||
where: ViewFilterWhereInput;
|
||||
}>;
|
||||
@ -3436,6 +3429,13 @@ export type DeleteViewSortsMutationVariables = Exact<{
|
||||
|
||||
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<{
|
||||
data: ViewUpdateInput;
|
||||
where: ViewWhereUniqueInput;
|
||||
@ -6247,39 +6247,6 @@ export function useCreateViewsMutation(baseOptions?: Apollo.MutationHookOptions<
|
||||
export type CreateViewsMutationHookResult = ReturnType<typeof useCreateViewsMutation>;
|
||||
export type CreateViewsMutationResult = Apollo.MutationResult<CreateViewsMutation>;
|
||||
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`
|
||||
mutation DeleteViewFilters($where: ViewFilterWhereInput!) {
|
||||
deleteManyViewFilter(where: $where) {
|
||||
@ -6346,6 +6313,39 @@ export function useDeleteViewSortsMutation(baseOptions?: Apollo.MutationHookOpti
|
||||
export type DeleteViewSortsMutationHookResult = ReturnType<typeof useDeleteViewSortsMutation>;
|
||||
export type DeleteViewSortsMutationResult = Apollo.MutationResult<DeleteViewSortsMutation>;
|
||||
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`
|
||||
mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) {
|
||||
updateOneView(data: $data, where: $where) {
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { companyViewFields } from '@/companies/constants/companyViewFields';
|
||||
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
|
||||
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
|
||||
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/sortsOrderByScopedSelector';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { filtersWhereScopedSelector } from '@/ui/filter-n-sort/states/selectors/filtersWhereScopedSelector';
|
||||
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/selectors/sortsOrderByScopedSelector';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useTableViews } from '@/views/hooks/useTableViews';
|
||||
import { useViewFilters } from '@/views/hooks/useViewFilters';
|
||||
import { useViews } from '@/views/hooks/useViews';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import {
|
||||
SortOrder,
|
||||
@ -30,11 +29,20 @@ export function CompanyTable() {
|
||||
sortsOrderByScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const whereFilters = useRecoilScopedValue(
|
||||
filtersWhereScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const [updateEntityMutation] = useUpdateOneCompanyMutation();
|
||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||
|
||||
const objectId = 'company';
|
||||
const { handleViewsChange } = useTableViews({ objectId });
|
||||
const { handleViewsChange } = useViews({
|
||||
availableFilters: companiesFilters,
|
||||
availableSorts,
|
||||
objectId,
|
||||
});
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: objectId,
|
||||
viewFieldDefinitions: companyViewFields,
|
||||
@ -46,15 +54,6 @@ export function CompanyTable() {
|
||||
const { persistSorts } = useViewSorts({ availableSorts });
|
||||
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const whereFilters = useMemo(() => {
|
||||
return { AND: filters.map(turnFilterIntoWhereClause) };
|
||||
}, [filters]) as any;
|
||||
|
||||
const { setContextMenuEntries } = useCompanyTableContextMenuEntries();
|
||||
const { setActionBarEntries } = useCompanyTableActionBarEntries();
|
||||
|
||||
|
||||
@ -132,6 +132,6 @@ export function useSetPeopleEntityTable() {
|
||||
|
||||
set(isFetchingEntityTableDataState, false);
|
||||
},
|
||||
[],
|
||||
[currentLocation, resetTableRowSelection, tableContextScopeId],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
|
||||
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
|
||||
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/sortsOrderByScopedSelector';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { filtersWhereScopedSelector } from '@/ui/filter-n-sort/states/selectors/filtersWhereScopedSelector';
|
||||
import { sortsOrderByScopedSelector } from '@/ui/filter-n-sort/states/selectors/sortsOrderByScopedSelector';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useTableViews } from '@/views/hooks/useTableViews';
|
||||
import { useViewFilters } from '@/views/hooks/useViewFilters';
|
||||
import { useViews } from '@/views/hooks/useViews';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import {
|
||||
SortOrder,
|
||||
@ -30,12 +29,21 @@ export function PeopleTable() {
|
||||
sortsOrderByScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const whereFilters = useRecoilScopedValue(
|
||||
filtersWhereScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const [updateEntityMutation] = useUpdateOnePersonMutation();
|
||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
|
||||
|
||||
const objectId = 'person';
|
||||
const { handleViewsChange } = useTableViews({ objectId });
|
||||
const { handleViewsChange } = useViews({
|
||||
availableFilters: peopleFilters,
|
||||
availableSorts,
|
||||
objectId,
|
||||
});
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: objectId,
|
||||
viewFieldDefinitions: peopleViewFields,
|
||||
@ -45,15 +53,6 @@ export function PeopleTable() {
|
||||
});
|
||||
const { persistSorts } = useViewSorts({ availableSorts });
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const whereFilters = useMemo(() => {
|
||||
return { AND: filters.map(turnFilterIntoWhereClause) };
|
||||
}, [filters]) as any;
|
||||
|
||||
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
|
||||
const { setActionBarEntries } = usePersonTableActionBarEntries();
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState';
|
||||
|
||||
@ -9,10 +9,9 @@ import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
|
||||
export function useCurrentCardSelected() {
|
||||
const currentCardId = useContext(BoardCardIdContext);
|
||||
|
||||
const [isCardSelected] = useRecoilState(
|
||||
const isCardSelected = useRecoilValue(
|
||||
isCardSelectedFamilyState(currentCardId ?? ''),
|
||||
);
|
||||
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
|
||||
|
||||
const setCurrentCardSelected = useRecoilCallback(
|
||||
({ set }) =>
|
||||
@ -20,9 +19,9 @@ export function useCurrentCardSelected() {
|
||||
if (!currentCardId) return;
|
||||
|
||||
set(isCardSelectedFamilyState(currentCardId), selected);
|
||||
setActionBarOpenState(true);
|
||||
set(actionBarOpenState, true);
|
||||
},
|
||||
[],
|
||||
[currentCardId],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -5,8 +5,8 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { filtersScopedState } from '../filtersScopedState';
|
||||
import { savedFiltersScopedState } from '../savedFiltersScopedState';
|
||||
|
||||
export const canPersistFiltersScopedState = selectorFamily({
|
||||
key: 'canPersistFiltersScopedState',
|
||||
export const canPersistFiltersScopedSelector = selectorFamily({
|
||||
key: 'canPersistFiltersScopedSelector',
|
||||
get:
|
||||
([scopeId, viewId]: [string, string | undefined]) =>
|
||||
({ get }) =>
|
||||
@ -5,8 +5,8 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { savedSortsScopedState } from '../savedSortsScopedState';
|
||||
import { sortsScopedState } from '../sortsScopedState';
|
||||
|
||||
export const canPersistSortsScopedState = selectorFamily({
|
||||
key: 'canPersistSortsScopedState',
|
||||
export const canPersistSortsScopedSelector = selectorFamily({
|
||||
key: 'canPersistSortsScopedSelector',
|
||||
get:
|
||||
([scopeId, viewId]: [string, string | undefined]) =>
|
||||
({ get }) =>
|
||||
@ -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),
|
||||
}),
|
||||
});
|
||||
@ -1,8 +1,7 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import type { Filter } from '../types/Filter';
|
||||
|
||||
import { savedFiltersScopedState } from './savedFiltersScopedState';
|
||||
import type { Filter } from '../../types/Filter';
|
||||
import { savedFiltersScopedState } from '../savedFiltersScopedState';
|
||||
|
||||
export const savedFiltersByKeyScopedSelector = selectorFamily({
|
||||
key: 'savedFiltersByKeyScopedSelector',
|
||||
@ -1,8 +1,7 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import type { SelectedSortType } from '../types/interface';
|
||||
|
||||
import { savedSortsScopedState } from './savedSortsScopedState';
|
||||
import type { SelectedSortType } from '../../types/interface';
|
||||
import { savedSortsScopedState } from '../savedSortsScopedState';
|
||||
|
||||
export const savedSortsByKeyScopedSelector = selectorFamily({
|
||||
key: 'savedSortsByKeyScopedSelector',
|
||||
@ -1,8 +1,7 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { reduceSortsToOrderBy } from '../helpers';
|
||||
|
||||
import { sortsScopedState } from './sortsScopedState';
|
||||
import { reduceSortsToOrderBy } from '../../helpers';
|
||||
import { sortsScopedState } from '../sortsScopedState';
|
||||
|
||||
export const sortsOrderByScopedSelector = selectorFamily({
|
||||
key: 'sortsOrderByScopedSelector',
|
||||
@ -136,7 +136,7 @@ export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
|
||||
setInitialPointerPositionX(null);
|
||||
setResizedFieldId(null);
|
||||
},
|
||||
[resizedFieldId, columnsById, setResizedFieldId],
|
||||
[resizedFieldId, columnsById, columns, onColumnsChange, setColumns],
|
||||
);
|
||||
|
||||
useTrackPointer({
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
|
||||
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
|
||||
@ -13,8 +12,6 @@ export function useLeaveTableFocus() {
|
||||
const disableSoftFocus = useDisableSoftFocus();
|
||||
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
return useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
@ -37,6 +34,6 @@ export function useLeaveTableFocus() {
|
||||
closeCurrentCellInEditMode();
|
||||
disableSoftFocus();
|
||||
},
|
||||
[setHotkeyScope, closeCurrentCellInEditMode, disableSoftFocus],
|
||||
[closeCurrentCellInEditMode, disableSoftFocus],
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,6 +50,6 @@ export function useSetEntityTableData() {
|
||||
|
||||
set(isFetchingEntityTableDataState, false);
|
||||
},
|
||||
[],
|
||||
[resetTableRowSelection, tableContextScopeId],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type FormEvent, useCallback, useRef, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@ -16,6 +16,10 @@ import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} 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 {
|
||||
IconChevronLeft,
|
||||
IconFileImport,
|
||||
@ -29,11 +33,12 @@ import {
|
||||
visibleTableColumnsState,
|
||||
} from '@/ui/table/states/tableColumnsState';
|
||||
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 { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import {
|
||||
currentTableViewIdState,
|
||||
type TableView,
|
||||
tableViewEditModeState,
|
||||
tableViewsByIdState,
|
||||
@ -60,6 +65,8 @@ export function TableOptionsDropdownContent({
|
||||
}: TableOptionsDropdownButtonProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const { closeDropdownButton } = useDropdownButton({ key: 'options' });
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
|
||||
@ -72,10 +79,6 @@ export function TableOptionsDropdownContent({
|
||||
const [viewEditMode, setViewEditMode] = useRecoilState(
|
||||
tableViewEditModeState,
|
||||
);
|
||||
const [views, setViews] = useRecoilScopedState(
|
||||
tableViewsState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const visibleColumns = useRecoilValue(visibleTableColumnsState);
|
||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||
const viewsById = useRecoilScopedValue(
|
||||
@ -124,31 +127,56 @@ export function TableOptionsDropdownContent({
|
||||
}
|
||||
}, [setViewEditMode]);
|
||||
|
||||
const handleViewNameSubmit = useCallback(
|
||||
(event?: FormEvent) => {
|
||||
event?.preventDefault();
|
||||
const handleViewNameSubmit = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (event?: FormEvent) => {
|
||||
event?.preventDefault();
|
||||
|
||||
if (viewEditMode.mode && 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,
|
||||
);
|
||||
const name = viewEditInputRef.current?.value;
|
||||
|
||||
(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,
|
||||
resetViewEditMode,
|
||||
setViews,
|
||||
tableScopeId,
|
||||
viewEditMode.mode,
|
||||
viewEditMode.viewId,
|
||||
views,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ export const TableUpdateViewButtonGroup = ({
|
||||
);
|
||||
set(savedSortsScopedState(currentViewId), selectedSorts);
|
||||
},
|
||||
[currentViewId, onViewSubmit],
|
||||
[currentViewId, onViewSubmit, tableScopeId],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
|
||||
@ -73,6 +73,10 @@ export const TableViewsDropdownButton = ({
|
||||
key: 'options',
|
||||
});
|
||||
|
||||
const [, setCurrentViewId] = useRecoilScopedState(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const currentView = useRecoilScopedValue(
|
||||
currentTableViewState,
|
||||
TableRecoilScopeContext,
|
||||
@ -81,10 +85,6 @@ export const TableViewsDropdownButton = ({
|
||||
tableViewsState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [, setCurrentViewId] = useRecoilScopedState(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setViewEditMode = useSetRecoilState(tableViewEditModeState);
|
||||
|
||||
const {
|
||||
@ -104,10 +104,10 @@ export const TableViewsDropdownButton = ({
|
||||
|
||||
set(filtersScopedState(tableScopeId), savedFilters);
|
||||
set(sortsScopedState(tableScopeId), savedSorts);
|
||||
setCurrentViewId(viewId);
|
||||
set(currentTableViewIdState(tableScopeId), viewId);
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[setCurrentViewId],
|
||||
[tableScopeId],
|
||||
);
|
||||
|
||||
const handleAddViewButtonClick = useCallback(() => {
|
||||
@ -126,12 +126,15 @@ export const TableViewsDropdownButton = ({
|
||||
);
|
||||
|
||||
const handleDeleteViewButtonClick = useCallback(
|
||||
(event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
||||
async (event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
||||
event.stopPropagation();
|
||||
|
||||
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);
|
||||
},
|
||||
[currentView?.id, onViewsChange, setCurrentViewId, setViews, views],
|
||||
|
||||
@ -27,7 +27,7 @@ export function usePreviousHotkeyScope() {
|
||||
|
||||
set(previousHotkeyScopeState, null);
|
||||
},
|
||||
[],
|
||||
[setHotkeyScope],
|
||||
);
|
||||
|
||||
const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback(
|
||||
@ -40,7 +40,7 @@ export function usePreviousHotkeyScope() {
|
||||
setHotkeyScope(scope, customScopes);
|
||||
set(previousHotkeyScopeState, currentHotkeyScope);
|
||||
},
|
||||
[],
|
||||
[setHotkeyScope],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
|
||||
@ -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 };
|
||||
Reference in New Issue
Block a user