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,5 +1,3 @@
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';
@ -11,10 +9,7 @@ import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTable
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 { useTableViews } from '@/views/hooks/useTableViews';
import { useViewFilters } from '@/views/hooks/useViewFilters';
import { useViews } from '@/views/hooks/useViews';
import { useViewSorts } from '@/views/hooks/useViewSorts';
import { import {
SortOrder, SortOrder,
UpdateOneCompanyMutationVariables, UpdateOneCompanyMutationVariables,
@ -37,31 +32,18 @@ export function CompanyTable() {
const [updateEntityMutation] = useUpdateOneCompanyMutation(); const [updateEntityMutation] = useUpdateOneCompanyMutation();
const upsertEntityTableItem = useUpsertEntityTableItem(); const upsertEntityTableItem = useUpsertEntityTableItem();
const objectId = 'company'; const { handleViewsChange, handleViewSubmit } = useTableViews({
const { handleViewsChange } = useViews({
availableFilters: companiesFilters, availableFilters: companiesFilters,
availableSorts, availableSorts,
objectId, objectId: 'company',
});
const { handleColumnsChange } = useTableViewFields({
objectName: objectId,
viewFieldDefinitions: companyViewFields, viewFieldDefinitions: companyViewFields,
}); });
const { persistFilters } = useViewFilters({
availableFilters: companiesFilters,
});
const { persistSorts } = useViewSorts({ availableSorts });
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport(); const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
const { setContextMenuEntries } = useCompanyTableContextMenuEntries(); const { setContextMenuEntries } = useCompanyTableContextMenuEntries();
const { setActionBarEntries } = useCompanyTableActionBarEntries(); const { setActionBarEntries } = useCompanyTableActionBarEntries();
const handleViewSubmit = useCallback(async () => {
await persistFilters();
await persistSorts();
}, [persistFilters, persistSorts]);
function handleImport() { function handleImport() {
openCompanySpreadsheetImport(); openCompanySpreadsheetImport();
} }
@ -80,7 +62,6 @@ export function CompanyTable() {
<EntityTable <EntityTable
viewName="All Companies" viewName="All Companies"
availableSorts={availableSorts} availableSorts={availableSorts}
onColumnsChange={handleColumnsChange}
onViewsChange={handleViewsChange} onViewsChange={handleViewsChange}
onViewSubmit={handleViewSubmit} onViewSubmit={handleViewSubmit}
onImport={handleImport} onImport={handleImport}

View File

@ -1,15 +1,19 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { tableColumnsState } from '@/ui/table/states/tableColumnsState'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { companyViewFields } from '../../constants/companyViewFields'; import { companyViewFields } from '../../constants/companyViewFields';
import { mockedCompaniesData } from './companies-mock-data'; import { mockedCompaniesData } from './companies-mock-data';
export function CompanyTableMockData() { export function CompanyTableMockData() {
const setColumns = useSetRecoilState(tableColumnsState); const [, setColumns] = useRecoilScopedState(
tableColumnsScopedState,
TableRecoilScopeContext,
);
const setEntityTableData = useSetEntityTableData(); const setEntityTableData = useSetEntityTableData();
setEntityTableData(mockedCompaniesData, []); setEntityTableData(mockedCompaniesData, []);

View File

@ -1,5 +1,3 @@
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';
@ -11,10 +9,7 @@ import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTable
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 { useTableViews } from '@/views/hooks/useTableViews';
import { useViewFilters } from '@/views/hooks/useViewFilters';
import { useViews } from '@/views/hooks/useViews';
import { useViewSorts } from '@/views/hooks/useViewSorts';
import { import {
SortOrder, SortOrder,
UpdateOnePersonMutationVariables, UpdateOnePersonMutationVariables,
@ -38,29 +33,16 @@ export function PeopleTable() {
const upsertEntityTableItem = useUpsertEntityTableItem(); const upsertEntityTableItem = useUpsertEntityTableItem();
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport(); const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
const objectId = 'person'; const { handleViewsChange, handleViewSubmit } = useTableViews({
const { handleViewsChange } = useViews({
availableFilters: peopleFilters, availableFilters: peopleFilters,
availableSorts, availableSorts,
objectId, objectId: 'person',
});
const { handleColumnsChange } = useTableViewFields({
objectName: objectId,
viewFieldDefinitions: peopleViewFields, viewFieldDefinitions: peopleViewFields,
}); });
const { persistFilters } = useViewFilters({
availableFilters: peopleFilters,
});
const { persistSorts } = useViewSorts({ availableSorts });
const { setContextMenuEntries } = usePersonTableContextMenuEntries(); const { setContextMenuEntries } = usePersonTableContextMenuEntries();
const { setActionBarEntries } = usePersonTableActionBarEntries(); const { setActionBarEntries } = usePersonTableActionBarEntries();
const handleViewSubmit = useCallback(async () => {
await persistFilters();
await persistSorts();
}, [persistFilters, persistSorts]);
function handleImport() { function handleImport() {
openPersonSpreadsheetImport(); openPersonSpreadsheetImport();
} }
@ -79,7 +61,6 @@ export function PeopleTable() {
<EntityTable <EntityTable
viewName="All People" viewName="All People"
availableSorts={availableSorts} availableSorts={availableSorts}
onColumnsChange={handleColumnsChange}
onViewsChange={handleViewsChange} onViewsChange={handleViewsChange}
onViewSubmit={handleViewSubmit} onViewSubmit={handleViewSubmit}
onImport={handleImport} onImport={handleImport}

View File

@ -1,10 +1,6 @@
import { useRef } from 'react'; import { useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { SortType } from '@/ui/filter-n-sort/types/interface'; import { SortType } from '@/ui/filter-n-sort/types/interface';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
@ -92,7 +88,6 @@ type OwnProps<SortField> = {
viewName: string; viewName: string;
viewIcon?: React.ReactNode; viewIcon?: React.ReactNode;
availableSorts?: Array<SortType<SortField>>; availableSorts?: Array<SortType<SortField>>;
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onViewsChange?: (views: TableView[]) => void; onViewsChange?: (views: TableView[]) => void;
onViewSubmit?: () => void; onViewSubmit?: () => void;
onImport?: () => void; onImport?: () => void;
@ -102,7 +97,6 @@ type OwnProps<SortField> = {
export function EntityTable<SortField>({ export function EntityTable<SortField>({
viewName, viewName,
availableSorts, availableSorts,
onColumnsChange,
onViewsChange, onViewsChange,
onViewSubmit, onViewSubmit,
onImport, onImport,
@ -131,7 +125,6 @@ export function EntityTable<SortField>({
<TableHeader <TableHeader
viewName={viewName} viewName={viewName}
availableSorts={availableSorts} availableSorts={availableSorts}
onColumnsChange={onColumnsChange}
onViewsChange={onViewsChange} onViewsChange={onViewsChange}
onViewSubmit={onViewSubmit} onViewSubmit={onViewSubmit}
onImport={onImport} onImport={onImport}
@ -139,7 +132,7 @@ export function EntityTable<SortField>({
<ScrollWrapper> <ScrollWrapper>
<div> <div>
<StyledTable> <StyledTable>
<EntityTableHeader onColumnsChange={onColumnsChange} /> <EntityTableHeader />
<EntityTableBody /> <EntityTableBody />
</StyledTable> </StyledTable>
</div> </div>

View File

@ -1,7 +1,6 @@
import { cloneElement, ComponentProps, useRef } from 'react'; import { cloneElement, ComponentProps, useRef } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton'; import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
@ -9,8 +8,10 @@ import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { IconPlus } from '@/ui/icon'; import { IconPlus } from '@/ui/icon';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { hiddenTableColumnsState } from '../states/tableColumnsState'; import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector';
const StyledColumnMenu = styled(StyledDropdownMenu)` const StyledColumnMenu = styled(StyledDropdownMenu)`
font-weight: ${({ theme }) => theme.font.weight.regular}; font-weight: ${({ theme }) => theme.font.weight.regular};
@ -29,7 +30,10 @@ export const EntityTableColumnMenu = ({
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const theme = useTheme(); const theme = useTheme();
const hiddenColumns = useRecoilValue(hiddenTableColumnsState); const hiddenColumns = useRecoilScopedValue(
hiddenTableColumnsScopedSelector,
TableRecoilScopeContext,
);
useListenClickOutside({ useListenClickOutside({
refs: [ref], refs: [ref],

View File

@ -1,23 +1,20 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilState } from 'recoil';
import { IconButton } from '@/ui/button/components/IconButton'; import { IconButton } from '@/ui/button/components/IconButton';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { IconPlus } from '@/ui/icon'; import { IconPlus } from '@/ui/icon';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState'; import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
import { import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector';
hiddenTableColumnsState, import { tableColumnsByIdScopedSelector } from '../states/selectors/tableColumnsByIdScopedSelector';
tableColumnsByIdState, import { visibleTableColumnsScopedSelector } from '../states/selectors/visibleTableColumnsScopedSelector';
tableColumnsState, import { tableColumnsScopedState } from '../states/tableColumnsScopedState';
visibleTableColumnsState,
} from '../states/tableColumnsState';
import { ColumnHead } from './ColumnHead'; import { ColumnHead } from './ColumnHead';
import { EntityTableColumnMenu } from './EntityTableColumnMenu'; import { EntityTableColumnMenu } from './EntityTableColumnMenu';
@ -78,18 +75,26 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
z-index: ${({ theme }) => theme.lastLayerZIndex}; z-index: ${({ theme }) => theme.lastLayerZIndex};
`; `;
export type EntityTableHeaderProps = { export function EntityTableHeader() {
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
};
export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
const theme = useTheme(); const theme = useTheme();
const [columns, setColumns] = useRecoilState(tableColumnsState);
const [offset, setOffset] = useRecoilState(resizeFieldOffsetState); const [offset, setOffset] = useRecoilState(resizeFieldOffsetState);
const columnsById = useRecoilValue(tableColumnsByIdState); const [columns, setColumns] = useRecoilScopedState(
const hiddenColumns = useRecoilValue(hiddenTableColumnsState); tableColumnsScopedState,
const visibleColumns = useRecoilValue(visibleTableColumnsState); TableRecoilScopeContext,
);
const columnsById = useRecoilScopedValue(
tableColumnsByIdScopedSelector,
TableRecoilScopeContext,
);
const hiddenColumns = useRecoilScopedValue(
hiddenTableColumnsScopedSelector,
TableRecoilScopeContext,
);
const visibleColumns = useRecoilScopedValue(
visibleTableColumnsScopedSelector,
TableRecoilScopeContext,
);
const [initialPointerPositionX, setInitialPointerPositionX] = useState< const [initialPointerPositionX, setInitialPointerPositionX] = useState<
number | null number | null
@ -129,14 +134,14 @@ export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
: column, : column,
); );
(onColumnsChange ?? setColumns)(nextColumns); setColumns(nextColumns);
} }
set(resizeFieldOffsetState, 0); set(resizeFieldOffsetState, 0);
setInitialPointerPositionX(null); setInitialPointerPositionX(null);
setResizedFieldId(null); setResizedFieldId(null);
}, },
[resizedFieldId, columnsById, columns, onColumnsChange, setColumns], [resizedFieldId, columnsById, columns, setColumns],
); );
useTrackPointer({ useTrackPointer({
@ -158,9 +163,9 @@ export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) {
column.id === columnId ? { ...column, isVisible: true } : column, column.id === columnId ? { ...column, isVisible: true } : column,
); );
(onColumnsChange ?? setColumns)(nextColumns); setColumns(nextColumns);
}, },
[columns, onColumnsChange, setColumns], [columns, setColumns],
); );
return ( return (

View File

@ -1,9 +1,11 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewFieldContext } from '../contexts/ViewFieldContext'; import { ViewFieldContext } from '../contexts/ViewFieldContext';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected'; import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { visibleTableColumnsState } from '../states/tableColumnsState'; import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
import { visibleTableColumnsScopedSelector } from '../states/selectors/visibleTableColumnsScopedSelector';
import { CheckboxCell } from './CheckboxCell'; import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCell'; import { EntityTableCell } from './EntityTableCell';
@ -14,7 +16,10 @@ const StyledRow = styled.tr<{ selected: boolean }>`
`; `;
export function EntityTableRow({ rowId }: { rowId: string }) { export function EntityTableRow({ rowId }: { rowId: string }) {
const columns = useRecoilValue(visibleTableColumnsState); const columns = useRecoilScopedValue(
visibleTableColumnsScopedSelector,
TableRecoilScopeContext,
);
const { currentRowSelected } = useCurrentRowSelected(); const { currentRowSelected } = useCurrentRowSelected();
return ( return (

View File

@ -1,13 +1,17 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { numberOfTableRowsState } from '../states/numberOfTableRowsState'; import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
import { numberOfTableColumnsScopedSelector } from '../states/selectors/numberOfTableColumnsScopedSelector';
import { softFocusPositionState } from '../states/softFocusPositionState'; import { softFocusPositionState } from '../states/softFocusPositionState';
import { numberOfTableColumnsState } from '../states/tableColumnsState';
import { useSetSoftFocusPosition } from './useSetSoftFocusPosition'; import { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
// TODO: stories // TODO: stories
export function useMoveSoftFocus() { export function useMoveSoftFocus() {
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const setSoftFocusPosition = useSetSoftFocusPosition(); const setSoftFocusPosition = useSetSoftFocusPosition();
const moveUp = useRecoilCallback( const moveUp = useRecoilCallback(
@ -64,7 +68,7 @@ export function useMoveSoftFocus() {
.valueOrThrow(); .valueOrThrow();
const numberOfTableColumns = snapshot const numberOfTableColumns = snapshot
.getLoadable(numberOfTableColumnsState) .getLoadable(numberOfTableColumnsScopedSelector(tableScopeId))
.valueOrThrow(); .valueOrThrow();
const numberOfTableRows = snapshot const numberOfTableRows = snapshot
@ -101,7 +105,7 @@ export function useMoveSoftFocus() {
}); });
} }
}, },
[setSoftFocusPosition], [setSoftFocusPosition, tableScopeId],
); );
const moveLeft = useRecoilCallback( const moveLeft = useRecoilCallback(
@ -112,7 +116,7 @@ export function useMoveSoftFocus() {
.valueOrThrow(); .valueOrThrow();
const numberOfTableColumns = snapshot const numberOfTableColumns = snapshot
.getLoadable(numberOfTableColumnsState) .getLoadable(numberOfTableColumnsScopedSelector(tableScopeId))
.valueOrThrow(); .valueOrThrow();
const currentColumnNumber = softFocusPosition.column; const currentColumnNumber = softFocusPosition.column;
@ -142,7 +146,7 @@ export function useMoveSoftFocus() {
}); });
} }
}, },
[setSoftFocusPosition], [setSoftFocusPosition, tableScopeId],
); );
return { return {

View File

@ -1,8 +1,4 @@
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { type TableView } from '../../states/tableViewsState'; import { type TableView } from '../../states/tableViewsState';
@ -11,14 +7,12 @@ import { TableOptionsDropdownButton } from './TableOptionsDropdownButton';
import { TableOptionsDropdownContent } from './TableOptionsDropdownContent'; import { TableOptionsDropdownContent } from './TableOptionsDropdownContent';
type TableOptionsDropdownProps = { type TableOptionsDropdownProps = {
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onViewsChange?: (views: TableView[]) => void; onViewsChange?: (views: TableView[]) => void;
onImport?: () => void; onImport?: () => void;
customHotkeyScope: HotkeyScope; customHotkeyScope: HotkeyScope;
}; };
export function TableOptionsDropdown({ export function TableOptionsDropdown({
onColumnsChange,
onViewsChange, onViewsChange,
onImport, onImport,
customHotkeyScope, customHotkeyScope,
@ -30,7 +24,6 @@ export function TableOptionsDropdown({
dropdownKey="options" dropdownKey="options"
dropdownComponents={ dropdownComponents={
<TableOptionsDropdownContent <TableOptionsDropdownContent
onColumnsChange={onColumnsChange}
onImport={onImport} onImport={onImport}
onViewsChange={onViewsChange} onViewsChange={onViewsChange}
/> />

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 { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -27,16 +27,16 @@ import {
IconPlus, IconPlus,
IconTag, IconTag,
} from '@/ui/icon'; } from '@/ui/icon';
import { import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
hiddenTableColumnsState,
tableColumnsState,
visibleTableColumnsState,
} from '@/ui/table/states/tableColumnsState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
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 { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector';
import { import {
currentTableViewIdState, currentTableViewIdState,
type TableView, type TableView,
@ -49,7 +49,6 @@ import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
import { TableOptionsDropdownSection } from './TableOptionsDropdownSection'; import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
type TableOptionsDropdownButtonProps = { type TableOptionsDropdownButtonProps = {
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onViewsChange?: (views: TableView[]) => void; onViewsChange?: (views: TableView[]) => void;
onImport?: () => void; onImport?: () => void;
}; };
@ -59,7 +58,6 @@ enum Option {
} }
export function TableOptionsDropdownContent({ export function TableOptionsDropdownContent({
onColumnsChange,
onViewsChange, onViewsChange,
onImport, onImport,
}: TableOptionsDropdownButtonProps) { }: TableOptionsDropdownButtonProps) {
@ -75,28 +73,37 @@ export function TableOptionsDropdownContent({
const viewEditInputRef = useRef<HTMLInputElement>(null); const viewEditInputRef = useRef<HTMLInputElement>(null);
const [columns, setColumns] = useRecoilState(tableColumnsState);
const [viewEditMode, setViewEditMode] = useRecoilState( const [viewEditMode, setViewEditMode] = useRecoilState(
tableViewEditModeState, tableViewEditModeState,
); );
const visibleColumns = useRecoilValue(visibleTableColumnsState); const [columns, setColumns] = useRecoilScopedState(
const hiddenColumns = useRecoilValue(hiddenTableColumnsState); tableColumnsScopedState,
TableRecoilScopeContext,
);
const visibleColumns = useRecoilScopedValue(
visibleTableColumnsScopedSelector,
TableRecoilScopeContext,
);
const hiddenColumns = useRecoilScopedValue(
hiddenTableColumnsScopedSelector,
TableRecoilScopeContext,
);
const viewsById = useRecoilScopedValue( const viewsById = useRecoilScopedValue(
tableViewsByIdState, tableViewsByIdState,
TableRecoilScopeContext, TableRecoilScopeContext,
); );
const handleColumnVisibilityChange = useCallback( const handleColumnVisibilityChange = useCallback(
(columnId: string, nextIsVisible: boolean) => { async (columnId: string, nextIsVisible: boolean) => {
const nextColumns = columns.map((column) => const nextColumns = columns.map((column) =>
column.id === columnId column.id === columnId
? { ...column, isVisible: nextIsVisible } ? { ...column, isVisible: nextIsVisible }
: column, : column,
); );
(onColumnsChange ?? setColumns)(nextColumns); setColumns(nextColumns);
}, },
[columns, onColumnsChange, setColumns], [columns, setColumns],
); );
const renderFieldActions = useCallback( const renderFieldActions = useCallback(
@ -144,6 +151,14 @@ export function TableOptionsDropdownContent({
const viewToCreate = { id: v4(), name }; const viewToCreate = { id: v4(), name };
const nextViews = [...views, viewToCreate]; const nextViews = [...views, viewToCreate];
const currentColumns = await snapshot.getPromise(
tableColumnsScopedState(tableScopeId),
);
set(
savedTableColumnsScopedState(viewToCreate.id),
currentColumns.map((column) => ({ ...column, id: v4() })),
);
const selectedFilters = await snapshot.getPromise( const selectedFilters = await snapshot.getPromise(
filtersScopedState(tableScopeId), filtersScopedState(tableScopeId),
); );

View File

@ -1,7 +1,7 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { Button, ButtonSize } from '@/ui/button/components/Button'; import { Button, ButtonSize } from '@/ui/button/components/Button';
@ -22,6 +22,9 @@ import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextS
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 { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
import { canPersistTableColumnsScopedSelector } from '../../states/selectors/canPersistTableColumnsScopedSelector';
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
import { import {
currentTableViewIdState, currentTableViewIdState,
tableViewEditModeState, tableViewEditModeState,
@ -56,12 +59,38 @@ export const TableUpdateViewButtonGroup = ({
currentTableViewIdState, currentTableViewIdState,
TableRecoilScopeContext, TableRecoilScopeContext,
); );
const currentColumns = useRecoilScopedValue(
tableColumnsScopedState,
TableRecoilScopeContext,
);
const setSavedColumns = useSetRecoilState(
savedTableColumnsScopedState(currentViewId),
);
const canPersistColumns = useRecoilValue(
canPersistTableColumnsScopedSelector([tableScopeId, currentViewId]),
);
const selectedFilters = useRecoilScopedValue(
filtersScopedState,
TableRecoilScopeContext,
);
const setSavedFilters = useSetRecoilState(
savedFiltersScopedState(currentViewId),
);
const canPersistFilters = useRecoilValue( const canPersistFilters = useRecoilValue(
canPersistFiltersScopedSelector([tableScopeId, currentViewId]), canPersistFiltersScopedSelector([tableScopeId, currentViewId]),
); );
const selectedSorts = useRecoilScopedValue(
sortsScopedState,
TableRecoilScopeContext,
);
const setSavedSorts = useSetRecoilState(savedSortsScopedState(currentViewId));
const canPersistSorts = useRecoilValue( const canPersistSorts = useRecoilValue(
canPersistSortsScopedSelector([tableScopeId, currentViewId]), canPersistSortsScopedSelector([tableScopeId, currentViewId]),
); );
const setViewEditMode = useSetRecoilState(tableViewEditModeState); const setViewEditMode = useSetRecoilState(tableViewEditModeState);
const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({
@ -82,23 +111,21 @@ export const TableUpdateViewButtonGroup = ({
setIsDropdownOpen(false); setIsDropdownOpen(false);
}, []); }, []);
const handleViewSubmit = useRecoilCallback( const handleViewSubmit = useCallback(async () => {
({ set, snapshot }) => await Promise.resolve(onViewSubmit?.());
async () => {
await Promise.resolve(onViewSubmit?.());
const selectedFilters = await snapshot.getPromise( setSavedColumns(currentColumns);
filtersScopedState(tableScopeId), setSavedFilters(selectedFilters);
); setSavedSorts(selectedSorts);
set(savedFiltersScopedState(currentViewId), selectedFilters); }, [
currentColumns,
const selectedSorts = await snapshot.getPromise( onViewSubmit,
sortsScopedState(tableScopeId), selectedFilters,
); selectedSorts,
set(savedSortsScopedState(currentViewId), selectedSorts); setSavedColumns,
}, setSavedFilters,
[currentViewId, onViewSubmit, tableScopeId], setSavedSorts,
); ]);
useScopedHotkeys( useScopedHotkeys(
[Key.Enter, Key.Escape], [Key.Enter, Key.Escape],
@ -112,7 +139,10 @@ export const TableUpdateViewButtonGroup = ({
<ButtonGroup size={ButtonSize.Small}> <ButtonGroup size={ButtonSize.Small}>
<Button <Button
title="Update view" title="Update view"
disabled={!currentViewId || (!canPersistFilters && !canPersistSorts)} disabled={
!currentViewId ||
(!canPersistColumns && !canPersistFilters && !canPersistSorts)
}
onClick={handleViewSubmit} onClick={handleViewSubmit}
/> />
<Button <Button

View File

@ -33,6 +33,8 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi
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 { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope'; import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
const StyledBoldDropdownMenuItemsContainer = styled( const StyledBoldDropdownMenuItemsContainer = styled(
@ -95,6 +97,9 @@ export const TableViewsDropdownButton = ({
const handleViewSelect = useRecoilCallback( const handleViewSelect = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
async (viewId?: string) => { async (viewId?: string) => {
const savedColumns = await snapshot.getPromise(
savedTableColumnsScopedState(viewId),
);
const savedFilters = await snapshot.getPromise( const savedFilters = await snapshot.getPromise(
savedFiltersScopedState(viewId), savedFiltersScopedState(viewId),
); );
@ -102,6 +107,7 @@ export const TableViewsDropdownButton = ({
savedSortsScopedState(viewId), savedSortsScopedState(viewId),
); );
set(tableColumnsScopedState(tableScopeId), savedColumns);
set(filtersScopedState(tableScopeId), savedFilters); set(filtersScopedState(tableScopeId), savedFilters);
set(sortsScopedState(tableScopeId), savedSorts); set(sortsScopedState(tableScopeId), savedSorts);
set(currentTableViewIdState(tableScopeId), viewId); set(currentTableViewIdState(tableScopeId), viewId);

View File

@ -0,0 +1,14 @@
import { atomFamily } from 'recoil';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
export const savedTableColumnsScopedState = atomFamily<
ViewFieldDefinition<ViewFieldMetadata>[],
string | undefined
>({
key: 'savedTableColumnsScopedState',
default: [],
});

View File

@ -0,0 +1,17 @@
import { selectorFamily } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { savedTableColumnsScopedState } from '../savedTableColumnsScopedState';
import { tableColumnsScopedState } from '../tableColumnsScopedState';
export const canPersistTableColumnsScopedSelector = selectorFamily({
key: 'canPersistTableColumnsScopedSelector',
get:
([scopeId, viewId]: [string, string | undefined]) =>
({ get }) =>
!isDeeplyEqual(
get(savedTableColumnsScopedState(viewId)),
get(tableColumnsScopedState(scopeId)),
),
});

View File

@ -0,0 +1,13 @@
import { selectorFamily } from 'recoil';
import { tableColumnsScopedState } from '../tableColumnsScopedState';
export const hiddenTableColumnsScopedSelector = selectorFamily({
key: 'hiddenTableColumnsScopedSelector',
get:
(scopeId: string) =>
({ get }) =>
get(tableColumnsScopedState(scopeId)).filter(
(column) => !column.isVisible,
),
});

View File

@ -0,0 +1,11 @@
import { selectorFamily } from 'recoil';
import { tableColumnsScopedState } from '../tableColumnsScopedState';
export const numberOfTableColumnsScopedSelector = selectorFamily({
key: 'numberOfTableColumnsScopedSelector',
get:
(scopeId: string) =>
({ get }) =>
get(tableColumnsScopedState(scopeId)).length,
});

View File

@ -0,0 +1,18 @@
import { selectorFamily } from 'recoil';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { savedTableColumnsScopedState } from '../savedTableColumnsScopedState';
export const savedTableColumnsByIdScopedSelector = selectorFamily({
key: 'savedTableColumnsByIdScopedSelector',
get:
(viewId: string | undefined) =>
({ get }) =>
get(savedTableColumnsScopedState(viewId)).reduce<
Record<string, ViewFieldDefinition<ViewFieldMetadata>>
>((result, column) => ({ ...result, [column.id]: column }), {}),
});

View File

@ -0,0 +1,18 @@
import { selectorFamily } from 'recoil';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { tableColumnsScopedState } from '../tableColumnsScopedState';
export const tableColumnsByIdScopedSelector = selectorFamily({
key: 'tableColumnsByIdScopedSelector',
get:
(scopeId: string) =>
({ get }) =>
get(tableColumnsScopedState(scopeId)).reduce<
Record<string, ViewFieldDefinition<ViewFieldMetadata>>
>((result, column) => ({ ...result, [column.id]: column }), {}),
});

View File

@ -0,0 +1,13 @@
import { selectorFamily } from 'recoil';
import { tableColumnsScopedState } from '../tableColumnsScopedState';
export const visibleTableColumnsScopedSelector = selectorFamily({
key: 'visibleTableColumnsScopedSelector',
get:
(scopeId: string) =>
({ get }) =>
get(tableColumnsScopedState(scopeId)).filter(
(column) => column.isVisible,
),
});

View File

@ -0,0 +1,14 @@
import { atomFamily } from 'recoil';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
export const tableColumnsScopedState = atomFamily<
ViewFieldDefinition<ViewFieldMetadata>[],
string
>({
key: 'tableColumnsScopedState',
default: [],
});

View File

@ -1,37 +0,0 @@
import { atom, selector } from 'recoil';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
export const tableColumnsState = atom<ViewFieldDefinition<ViewFieldMetadata>[]>(
{
key: 'tableColumnsState',
default: [],
},
);
export const tableColumnsByIdState = selector({
key: 'tableColumnsByIdState',
get: ({ get }) =>
get(tableColumnsState).reduce<
Record<string, ViewFieldDefinition<ViewFieldMetadata>>
>((result, column) => ({ ...result, [column.id]: column }), {}),
});
export const numberOfTableColumnsState = selector<number>({
key: 'numberOfTableColumnsState',
get: ({ get }) => get(tableColumnsState).length,
});
export const visibleTableColumnsState = selector({
key: 'visibleTableColumnsState',
get: ({ get }) => get(tableColumnsState).filter((column) => column.isVisible),
});
export const hiddenTableColumnsState = selector({
key: 'hiddenTableColumnsState',
get: ({ get }) =>
get(tableColumnsState).filter((column) => !column.isVisible),
});

View File

@ -1,10 +1,6 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { FilterDropdownButton } from '@/ui/filter-n-sort/components/FilterDropdownButton'; import { FilterDropdownButton } from '@/ui/filter-n-sort/components/FilterDropdownButton';
import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar'; import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton'; import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
@ -26,7 +22,6 @@ import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
type OwnProps<SortField> = { type OwnProps<SortField> = {
viewName: string; viewName: string;
availableSorts?: Array<SortType<SortField>>; availableSorts?: Array<SortType<SortField>>;
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
onViewsChange?: (views: TableView[]) => void; onViewsChange?: (views: TableView[]) => void;
onViewSubmit?: () => void; onViewSubmit?: () => void;
onImport?: () => void; onImport?: () => void;
@ -35,7 +30,6 @@ type OwnProps<SortField> = {
export function TableHeader<SortField>({ export function TableHeader<SortField>({
viewName, viewName,
availableSorts, availableSorts,
onColumnsChange,
onViewsChange, onViewsChange,
onViewSubmit, onViewSubmit,
onImport, onImport,
@ -89,7 +83,6 @@ export function TableHeader<SortField>({
/> />
<TableOptionsDropdown <TableOptionsDropdown
onImport={onImport} onImport={onImport}
onColumnsChange={onColumnsChange}
onViewsChange={onViewsChange} onViewsChange={onViewsChange}
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }} customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
/> />

View File

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

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

View File

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

View File

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