feat: persist table columns on change (#1697)
* feat: persist table columns on change Closes #1580 * fix: fix drag-and-select on Table Options dropdown toggle
This commit is contained in:
@ -5,6 +5,7 @@ import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyT
|
|||||||
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
||||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||||
import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect';
|
import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect';
|
||||||
|
import { TableContext } from '@/ui/table/contexts/TableContext';
|
||||||
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';
|
||||||
@ -35,11 +36,16 @@ export const CompanyTable = () => {
|
|||||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||||
|
|
||||||
const [getWorkspaceMember] = useGetWorkspaceMembersLazyQuery();
|
const [getWorkspaceMember] = useGetWorkspaceMembersLazyQuery();
|
||||||
const { createView, deleteView, submitCurrentView, updateView } =
|
const {
|
||||||
useTableViews({
|
createView,
|
||||||
objectId: 'company',
|
deleteView,
|
||||||
columnDefinitions: companiesAvailableColumnDefinitions,
|
persistColumns,
|
||||||
});
|
submitCurrentView,
|
||||||
|
updateView,
|
||||||
|
} = useTableViews({
|
||||||
|
objectId: 'company',
|
||||||
|
columnDefinitions: companiesAvailableColumnDefinitions,
|
||||||
|
});
|
||||||
|
|
||||||
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
|
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
|
||||||
|
|
||||||
@ -76,7 +82,7 @@ export const CompanyTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<TableContext.Provider value={{ onColumnsChange: persistColumns }}>
|
||||||
<EntityTableEffect
|
<EntityTableEffect
|
||||||
getRequestResultKey="companies"
|
getRequestResultKey="companies"
|
||||||
useGetRequest={useGetCompaniesQuery}
|
useGetRequest={useGetCompaniesQuery}
|
||||||
@ -109,6 +115,6 @@ export const CompanyTable = () => {
|
|||||||
}) => updateCompany(variables)}
|
}) => updateCompany(variables)}
|
||||||
/>
|
/>
|
||||||
</ViewBarContext.Provider>
|
</ViewBarContext.Provider>
|
||||||
</>
|
</TableContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableAct
|
|||||||
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
||||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||||
import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect';
|
import { EntityTableEffect } from '@/ui/table/components/EntityTableEffect';
|
||||||
|
import { TableContext } from '@/ui/table/contexts/TableContext';
|
||||||
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';
|
||||||
@ -34,11 +35,16 @@ export const PeopleTable = () => {
|
|||||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||||
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
|
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
|
||||||
|
|
||||||
const { createView, deleteView, submitCurrentView, updateView } =
|
const {
|
||||||
useTableViews({
|
createView,
|
||||||
objectId: 'person',
|
deleteView,
|
||||||
columnDefinitions: peopleAvailableColumnDefinitions,
|
persistColumns,
|
||||||
});
|
submitCurrentView,
|
||||||
|
updateView,
|
||||||
|
} = useTableViews({
|
||||||
|
objectId: 'person',
|
||||||
|
columnDefinitions: peopleAvailableColumnDefinitions,
|
||||||
|
});
|
||||||
|
|
||||||
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
|
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
|
||||||
const { setActionBarEntries } = usePersonTableActionBarEntries();
|
const { setActionBarEntries } = usePersonTableActionBarEntries();
|
||||||
@ -48,7 +54,7 @@ export const PeopleTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<TableContext.Provider value={{ onColumnsChange: persistColumns }}>
|
||||||
<EntityTableEffect
|
<EntityTableEffect
|
||||||
getRequestResultKey="people"
|
getRequestResultKey="people"
|
||||||
useGetRequest={useGetPeopleQuery}
|
useGetRequest={useGetPeopleQuery}
|
||||||
@ -91,6 +97,6 @@ export const PeopleTable = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ViewBarContext.Provider>
|
</ViewBarContext.Provider>
|
||||||
</>
|
</TableContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
|
|||||||
import { useDropdownButton } from '../hooks/useDropdownButton';
|
import { useDropdownButton } from '../hooks/useDropdownButton';
|
||||||
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
|
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
|
||||||
|
|
||||||
import { DropdownCloseEffect } from './DropdownCloseEffect';
|
import { DropdownToggleEffect } from './DropdownToggleEffect';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
buttonComponents?: JSX.Element | JSX.Element[];
|
buttonComponents?: JSX.Element | JSX.Element[];
|
||||||
@ -24,6 +24,7 @@ type OwnProps = {
|
|||||||
dropdownPlacement?: Placement;
|
dropdownPlacement?: Placement;
|
||||||
onClickOutside?: () => void;
|
onClickOutside?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
onOpen?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropdownButton = ({
|
export const DropdownButton = ({
|
||||||
@ -35,6 +36,7 @@ export const DropdownButton = ({
|
|||||||
dropdownPlacement = 'bottom-end',
|
dropdownPlacement = 'bottom-end',
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
onClose,
|
onClose,
|
||||||
|
onOpen,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -93,9 +95,10 @@ export const DropdownButton = ({
|
|||||||
{dropdownComponents}
|
{dropdownComponents}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<DropdownCloseEffect
|
<DropdownToggleEffect
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
onDropdownClose={() => onClose?.()}
|
onDropdownClose={onClose}
|
||||||
|
onDropdownOpen={onOpen}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,20 +2,24 @@ import { useEffect } from 'react';
|
|||||||
|
|
||||||
import { useDropdownButton } from '../hooks/useDropdownButton';
|
import { useDropdownButton } from '../hooks/useDropdownButton';
|
||||||
|
|
||||||
export const DropdownCloseEffect = ({
|
export const DropdownToggleEffect = ({
|
||||||
dropdownId,
|
dropdownId,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
|
onDropdownOpen,
|
||||||
}: {
|
}: {
|
||||||
dropdownId: string;
|
dropdownId: string;
|
||||||
onDropdownClose: () => void;
|
onDropdownClose?: () => void;
|
||||||
|
onDropdownOpen?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { isDropdownButtonOpen } = useDropdownButton({ dropdownId });
|
const { isDropdownButtonOpen } = useDropdownButton({ dropdownId });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isDropdownButtonOpen) {
|
if (isDropdownButtonOpen) {
|
||||||
onDropdownClose();
|
onDropdownOpen?.();
|
||||||
|
} else {
|
||||||
|
onDropdownClose?.();
|
||||||
}
|
}
|
||||||
}, [isDropdownButtonOpen, onDropdownClose]);
|
}, [isDropdownButtonOpen, onDropdownClose, onDropdownOpen]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
@ -90,9 +90,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
export const EntityTable = ({ updateEntityMutation }: OwnProps) => {
|
export const EntityTable = ({ updateEntityMutation }: OwnProps) => {
|
||||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||||
const [isDraggingAndSelecting, setIsDraggingAndSelecting] = useRecoilState(
|
const isDraggingAndSelecting = useRecoilValue(isDraggingAndSelectingState);
|
||||||
isDraggingAndSelectingState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setRowSelectedState = useSetRowSelectedState();
|
const setRowSelectedState = useSetRowSelectedState();
|
||||||
const resetTableRowSelection = useResetTableRowSelection();
|
const resetTableRowSelection = useResetTableRowSelection();
|
||||||
@ -105,7 +103,6 @@ export const EntityTable = ({ updateEntityMutation }: OwnProps) => {
|
|||||||
refs: [tableBodyRef],
|
refs: [tableBodyRef],
|
||||||
callback: () => {
|
callback: () => {
|
||||||
leaveTableFocus();
|
leaveTableFocus();
|
||||||
setIsDraggingAndSelecting(true);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-co
|
|||||||
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 { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
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 { useTableColumns } from '../hooks/useTableColumns';
|
||||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
|
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
|
||||||
import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector';
|
import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector';
|
||||||
@ -76,7 +76,7 @@ export const EntityTableHeader = () => {
|
|||||||
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
|
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
|
||||||
resizeFieldOffsetState,
|
resizeFieldOffsetState,
|
||||||
);
|
);
|
||||||
const [tableColumns, setTableColumns] = useRecoilScopedState(
|
const tableColumns = useRecoilScopedValue(
|
||||||
tableColumnsScopedState,
|
tableColumnsScopedState,
|
||||||
TableRecoilScopeContext,
|
TableRecoilScopeContext,
|
||||||
);
|
);
|
||||||
@ -99,6 +99,8 @@ export const EntityTableHeader = () => {
|
|||||||
const [resizedFieldKey, setResizedFieldKey] = useState<string | null>(null);
|
const [resizedFieldKey, setResizedFieldKey] = useState<string | null>(null);
|
||||||
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
|
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
const { handleColumnsChange } = useTableColumns();
|
||||||
|
|
||||||
const handleResizeHandlerStart = useCallback((positionX: number) => {
|
const handleResizeHandlerStart = useCallback((positionX: number) => {
|
||||||
setInitialPointerPositionX(positionX);
|
setInitialPointerPositionX(positionX);
|
||||||
}, []);
|
}, []);
|
||||||
@ -113,7 +115,7 @@ export const EntityTableHeader = () => {
|
|||||||
|
|
||||||
const handleResizeHandlerEnd = useRecoilCallback(
|
const handleResizeHandlerEnd = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
() => {
|
async () => {
|
||||||
if (!resizedFieldKey) return;
|
if (!resizedFieldKey) return;
|
||||||
|
|
||||||
const nextWidth = Math.round(
|
const nextWidth = Math.round(
|
||||||
@ -131,14 +133,14 @@ export const EntityTableHeader = () => {
|
|||||||
: column,
|
: column,
|
||||||
);
|
);
|
||||||
|
|
||||||
setTableColumns(nextColumns);
|
await handleColumnsChange(nextColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(resizeFieldOffsetState, 0);
|
set(resizeFieldOffsetState, 0);
|
||||||
setInitialPointerPositionX(null);
|
setInitialPointerPositionX(null);
|
||||||
setResizedFieldKey(null);
|
setResizedFieldKey(null);
|
||||||
},
|
},
|
||||||
[resizedFieldKey, tableColumnsByKey, tableColumns, setTableColumns],
|
[resizedFieldKey, tableColumnsByKey, tableColumns, handleColumnsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
useTrackPointer({
|
useTrackPointer({
|
||||||
|
|||||||
11
front/src/modules/ui/table/contexts/TableContext.ts
Normal file
11
front/src/modules/ui/table/contexts/TableContext.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||||
|
|
||||||
|
import type { ColumnDefinition } from '../types/ColumnDefinition';
|
||||||
|
|
||||||
|
export const TableContext = createContext<{
|
||||||
|
onColumnsChange?: (
|
||||||
|
columns: ColumnDefinition<ViewFieldMetadata>[],
|
||||||
|
) => void | Promise<void>;
|
||||||
|
}>({});
|
||||||
@ -1,15 +1,28 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||||
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 { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
|
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||||
|
|
||||||
|
import { TableContext } from '../contexts/TableContext';
|
||||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
|
import { savedTableColumnsFamilyState } from '../states/savedTableColumnsFamilyState';
|
||||||
import { tableColumnsByKeyScopedSelector } from '../states/selectors/tableColumnsByKeyScopedSelector';
|
import { tableColumnsByKeyScopedSelector } from '../states/selectors/tableColumnsByKeyScopedSelector';
|
||||||
import { tableColumnsScopedState } from '../states/tableColumnsScopedState';
|
import { tableColumnsScopedState } from '../states/tableColumnsScopedState';
|
||||||
import type { ColumnDefinition } from '../types/ColumnDefinition';
|
import type { ColumnDefinition } from '../types/ColumnDefinition';
|
||||||
|
|
||||||
export const useTableColumns = () => {
|
export const useTableColumns = () => {
|
||||||
|
const { onColumnsChange } = useContext(TableContext);
|
||||||
|
|
||||||
|
const currentViewId = useRecoilScopedValue(
|
||||||
|
currentViewIdScopedState,
|
||||||
|
TableRecoilScopeContext,
|
||||||
|
);
|
||||||
|
const setSavedTableColumns = useSetRecoilState(
|
||||||
|
savedTableColumnsFamilyState(currentViewId),
|
||||||
|
);
|
||||||
const [tableColumns, setTableColumns] = useRecoilScopedState(
|
const [tableColumns, setTableColumns] = useRecoilScopedState(
|
||||||
tableColumnsScopedState,
|
tableColumnsScopedState,
|
||||||
TableRecoilScopeContext,
|
TableRecoilScopeContext,
|
||||||
@ -19,21 +32,30 @@ export const useTableColumns = () => {
|
|||||||
TableRecoilScopeContext,
|
TableRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleColumnReorder = useCallback(
|
const handleColumnsChange = useCallback(
|
||||||
(columns: ColumnDefinition<ViewFieldMetadata>[]) => {
|
async (columns: ColumnDefinition<ViewFieldMetadata>[]) => {
|
||||||
const updatedColumnOrder = columns
|
await onColumnsChange?.(columns);
|
||||||
.map((column, index) => {
|
|
||||||
return { ...column, index };
|
|
||||||
})
|
|
||||||
.sort((columnA, columnB) => columnA.index - columnB.index);
|
|
||||||
|
|
||||||
setTableColumns(updatedColumnOrder);
|
setSavedTableColumns(columns);
|
||||||
|
setTableColumns(columns);
|
||||||
},
|
},
|
||||||
[setTableColumns],
|
[onColumnsChange, setSavedTableColumns, setTableColumns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleColumnReorder = useCallback(
|
||||||
|
async (columns: ColumnDefinition<ViewFieldMetadata>[]) => {
|
||||||
|
const updatedColumns = columns.map((column, index) => ({
|
||||||
|
...column,
|
||||||
|
index,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await handleColumnsChange(updatedColumns);
|
||||||
|
},
|
||||||
|
[handleColumnsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleColumnVisibilityChange = useCallback(
|
const handleColumnVisibilityChange = useCallback(
|
||||||
(column: ColumnDefinition<ViewFieldMetadata>) => {
|
async (column: ColumnDefinition<ViewFieldMetadata>) => {
|
||||||
const nextColumns = tableColumnsByKey[column.key]
|
const nextColumns = tableColumnsByKey[column.key]
|
||||||
? tableColumns.map((previousColumn) =>
|
? tableColumns.map((previousColumn) =>
|
||||||
previousColumn.key === column.key
|
previousColumn.key === column.key
|
||||||
@ -44,13 +66,13 @@ export const useTableColumns = () => {
|
|||||||
(columnA, columnB) => columnA.index - columnB.index,
|
(columnA, columnB) => columnA.index - columnB.index,
|
||||||
);
|
);
|
||||||
|
|
||||||
setTableColumns(nextColumns);
|
await handleColumnsChange(nextColumns);
|
||||||
},
|
},
|
||||||
[tableColumnsByKey, tableColumns, setTableColumns],
|
[tableColumnsByKey, tableColumns, handleColumnsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleColumnMove = useCallback(
|
const handleColumnMove = useCallback(
|
||||||
(direction: string, column: ColumnDefinition<ViewFieldMetadata>) => {
|
async (direction: string, column: ColumnDefinition<ViewFieldMetadata>) => {
|
||||||
const currentColumnArrayIndex = tableColumns.findIndex(
|
const currentColumnArrayIndex = tableColumns.findIndex(
|
||||||
(tableColumn) => tableColumn.key === column.key,
|
(tableColumn) => tableColumn.key === column.key,
|
||||||
);
|
);
|
||||||
@ -73,10 +95,10 @@ export const useTableColumns = () => {
|
|||||||
index: targetColumn.index,
|
index: targetColumn.index,
|
||||||
};
|
};
|
||||||
|
|
||||||
setTableColumns(newTableColumns);
|
await handleColumnsChange(newTableColumns);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[tableColumns, setTableColumns],
|
[tableColumns, handleColumnsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleColumnLeftMove = useCallback(
|
const handleColumnLeftMove = useCallback(
|
||||||
@ -98,5 +120,6 @@ export const useTableColumns = () => {
|
|||||||
handleColumnLeftMove,
|
handleColumnLeftMove,
|
||||||
handleColumnRightMove,
|
handleColumnRightMove,
|
||||||
handleColumnReorder,
|
handleColumnReorder,
|
||||||
|
handleColumnsChange,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { useResetRecoilState } from 'recoil';
|
import { useRecoilState, useResetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
|
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
|
||||||
|
|
||||||
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
|
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
|
||||||
|
import { isDraggingAndSelectingState } from '../../states/isDraggingAndSelectingState';
|
||||||
|
|
||||||
import { TableOptionsDropdownButton } from './TableOptionsDropdownButton';
|
import { TableOptionsDropdownButton } from './TableOptionsDropdownButton';
|
||||||
import { TableOptionsDropdownContent } from './TableOptionsDropdownContent';
|
import { TableOptionsDropdownContent } from './TableOptionsDropdownContent';
|
||||||
@ -18,6 +19,14 @@ export const TableOptionsDropdown = ({
|
|||||||
}: TableOptionsDropdownProps) => {
|
}: TableOptionsDropdownProps) => {
|
||||||
const resetViewEditMode = useResetRecoilState(viewEditModeState);
|
const resetViewEditMode = useResetRecoilState(viewEditModeState);
|
||||||
|
|
||||||
|
const [, setIsDraggingAndSelecting] = useRecoilState(
|
||||||
|
isDraggingAndSelectingState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClose = () => setIsDraggingAndSelecting(true);
|
||||||
|
|
||||||
|
const handleOpen = () => setIsDraggingAndSelecting(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
buttonComponents={<TableOptionsDropdownButton />}
|
buttonComponents={<TableOptionsDropdownButton />}
|
||||||
@ -25,6 +34,8 @@ export const TableOptionsDropdown = ({
|
|||||||
dropdownId={TableOptionsDropdownId}
|
dropdownId={TableOptionsDropdownId}
|
||||||
dropdownComponents={<TableOptionsDropdownContent />}
|
dropdownComponents={<TableOptionsDropdownContent />}
|
||||||
onClickOutside={resetViewEditMode}
|
onClickOutside={resetViewEditMode}
|
||||||
|
onClose={handleClose}
|
||||||
|
onOpen={handleOpen}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,27 +1,16 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
|
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import { TableOptionsDropdownId } from '@/ui/table/constants/TableOptionsDropdownId';
|
import { TableOptionsDropdownId } from '@/ui/table/constants/TableOptionsDropdownId';
|
||||||
import { isDraggingAndSelectingState } from '@/ui/table/states/isDraggingAndSelectingState';
|
|
||||||
|
|
||||||
export const TableOptionsDropdownButton = () => {
|
export const TableOptionsDropdownButton = () => {
|
||||||
const [, setIsDraggingAndSelecting] = useRecoilState(
|
|
||||||
isDraggingAndSelectingState,
|
|
||||||
);
|
|
||||||
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
|
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
|
||||||
dropdownId: TableOptionsDropdownId,
|
dropdownId: TableOptionsDropdownId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
|
||||||
setIsDraggingAndSelecting(false);
|
|
||||||
toggleDropdownButton();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledHeaderDropdownButton
|
<StyledHeaderDropdownButton
|
||||||
isUnfolded={isDropdownButtonOpen}
|
isUnfolded={isDropdownButtonOpen}
|
||||||
onClick={toggleDropdown}
|
onClick={toggleDropdownButton}
|
||||||
>
|
>
|
||||||
Options
|
Options
|
||||||
</StyledHeaderDropdownButton>
|
</StyledHeaderDropdownButton>
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
import { selectorFamily } from 'recoil';
|
|
||||||
|
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
|
||||||
|
|
||||||
import { savedTableColumnsFamilyState } from '../savedTableColumnsFamilyState';
|
|
||||||
import { tableColumnsScopedState } from '../tableColumnsScopedState';
|
|
||||||
|
|
||||||
export const canPersistTableColumnsScopedFamilySelector = selectorFamily({
|
|
||||||
key: 'canPersistTableColumnsScopedFamilySelector',
|
|
||||||
get:
|
|
||||||
({
|
|
||||||
recoilScopeId,
|
|
||||||
viewId,
|
|
||||||
}: {
|
|
||||||
recoilScopeId: string;
|
|
||||||
viewId: string | undefined;
|
|
||||||
}) =>
|
|
||||||
({ get }) =>
|
|
||||||
!isDeeplyEqual(
|
|
||||||
get(savedTableColumnsFamilyState(viewId)),
|
|
||||||
get(tableColumnsScopedState(recoilScopeId)),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
@ -1,21 +1,16 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
|
||||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
|
||||||
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
|
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
|
||||||
import { ViewBar } from '@/ui/view-bar/components/ViewBar';
|
import { ViewBar } from '@/ui/view-bar/components/ViewBar';
|
||||||
import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
|
import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
|
||||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
|
||||||
|
|
||||||
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
|
import { TableOptionsDropdownId } from '../../constants/TableOptionsDropdownId';
|
||||||
import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown';
|
import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown';
|
||||||
import { isDraggingAndSelectingState } from '../../states/isDraggingAndSelectingState';
|
|
||||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
|
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
|
||||||
import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector';
|
|
||||||
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
|
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
|
||||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||||
|
|
||||||
@ -24,29 +19,6 @@ export const TableHeader = () => {
|
|||||||
useContext(ViewBarContext);
|
useContext(ViewBarContext);
|
||||||
const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext);
|
const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext);
|
||||||
|
|
||||||
const currentViewId = useRecoilScopedValue(
|
|
||||||
currentViewIdScopedState,
|
|
||||||
TableRecoilScopeContext,
|
|
||||||
);
|
|
||||||
const canPersistTableColumns = useRecoilValue(
|
|
||||||
canPersistTableColumnsScopedFamilySelector({
|
|
||||||
recoilScopeId: tableRecoilScopeId,
|
|
||||||
viewId: currentViewId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const [tableColumns, setTableColumns] = useRecoilScopedState(
|
|
||||||
tableColumnsScopedState,
|
|
||||||
TableRecoilScopeContext,
|
|
||||||
);
|
|
||||||
const [savedTableColumns, setSavedTableColumns] = useRecoilState(
|
|
||||||
savedTableColumnsFamilyState(currentViewId),
|
|
||||||
);
|
|
||||||
const [, setIsDraggingAndSelecting] = useRecoilState(
|
|
||||||
isDraggingAndSelectingState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleViewBarReset = () => setTableColumns(savedTableColumns);
|
|
||||||
|
|
||||||
const handleViewSelect = useRecoilCallback(
|
const handleViewSelect = useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
async (viewId: string) => {
|
async (viewId: string) => {
|
||||||
@ -58,23 +30,12 @@ export const TableHeader = () => {
|
|||||||
[tableRecoilScopeId],
|
[tableRecoilScopeId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCurrentViewSubmit = async () => {
|
|
||||||
if (canPersistTableColumns) {
|
|
||||||
setSavedTableColumns(tableColumns);
|
|
||||||
setIsDraggingAndSelecting(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
await onCurrentViewSubmit?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
|
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
|
||||||
<ViewBarContext.Provider
|
<ViewBarContext.Provider
|
||||||
value={{
|
value={{
|
||||||
...viewBarContextProps,
|
...viewBarContextProps,
|
||||||
canPersistViewFields: canPersistTableColumns,
|
onCurrentViewSubmit,
|
||||||
onCurrentViewSubmit: handleCurrentViewSubmit,
|
|
||||||
onViewBarReset: handleViewBarReset,
|
|
||||||
onViewSelect: handleViewSelect,
|
onViewSelect: handleViewSelect,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { type ReactNode, useContext } from 'react';
|
import { type ReactNode, useContext } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { IconArrowDown, IconArrowUp } from '@/ui/icon/index';
|
import { IconArrowDown, IconArrowUp } from '@/ui/icon/index';
|
||||||
import { isDraggingAndSelectingState } from '@/ui/table/states/isDraggingAndSelectingState';
|
|
||||||
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 { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
|
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
|
||||||
@ -116,10 +115,6 @@ export const ViewBarDetails = ({
|
|||||||
ViewBarRecoilScopeContext,
|
ViewBarRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [, setIsDraggingAndSelecting] = useRecoilState(
|
|
||||||
isDraggingAndSelectingState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const savedFilters = useRecoilValue(
|
const savedFilters = useRecoilValue(
|
||||||
savedFiltersFamilySelector(currentViewId),
|
savedFiltersFamilySelector(currentViewId),
|
||||||
);
|
);
|
||||||
@ -172,7 +167,6 @@ export const ViewBarDetails = ({
|
|||||||
|
|
||||||
const handleCancelClick = () => {
|
const handleCancelClick = () => {
|
||||||
onViewBarReset?.();
|
onViewBarReset?.();
|
||||||
setIsDraggingAndSelecting(true);
|
|
||||||
setFilters(savedFilters);
|
setFilters(savedFilters);
|
||||||
setSorts(savedSorts);
|
setSorts(savedSorts);
|
||||||
};
|
};
|
||||||
@ -239,7 +233,7 @@ export const ViewBarDetails = ({
|
|||||||
data-testid="cancel-button"
|
data-testid="cancel-button"
|
||||||
onClick={handleCancelClick}
|
onClick={handleCancelClick}
|
||||||
>
|
>
|
||||||
Cancel
|
Reset
|
||||||
</StyledCancelButton>
|
</StyledCancelButton>
|
||||||
)}
|
)}
|
||||||
{rightComponent}
|
{rightComponent}
|
||||||
|
|||||||
@ -152,32 +152,34 @@ export const useTableViewFields = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistColumns = useCallback(async () => {
|
const persistColumns = useCallback(
|
||||||
if (!currentViewId) return;
|
async (nextColumns: ColumnDefinition<ViewFieldMetadata>[]) => {
|
||||||
|
if (!currentViewId) return;
|
||||||
|
|
||||||
const viewFieldsToCreate = tableColumns.filter(
|
const viewFieldsToCreate = nextColumns.filter(
|
||||||
(column) => !savedTableColumnsByKey[column.key],
|
(column) => !savedTableColumnsByKey[column.key],
|
||||||
);
|
);
|
||||||
await createViewFields(viewFieldsToCreate);
|
await createViewFields(viewFieldsToCreate);
|
||||||
|
|
||||||
const viewFieldsToUpdate = tableColumns.filter(
|
const viewFieldsToUpdate = nextColumns.filter(
|
||||||
(column) =>
|
(column) =>
|
||||||
savedTableColumnsByKey[column.key] &&
|
savedTableColumnsByKey[column.key] &&
|
||||||
(savedTableColumnsByKey[column.key].size !== column.size ||
|
(savedTableColumnsByKey[column.key].size !== column.size ||
|
||||||
savedTableColumnsByKey[column.key].index !== column.index ||
|
savedTableColumnsByKey[column.key].index !== column.index ||
|
||||||
savedTableColumnsByKey[column.key].isVisible !== column.isVisible),
|
savedTableColumnsByKey[column.key].isVisible !== column.isVisible),
|
||||||
);
|
);
|
||||||
await updateViewFields(viewFieldsToUpdate);
|
await updateViewFields(viewFieldsToUpdate);
|
||||||
|
|
||||||
return refetch();
|
await refetch();
|
||||||
}, [
|
},
|
||||||
createViewFields,
|
[
|
||||||
currentViewId,
|
createViewFields,
|
||||||
refetch,
|
currentViewId,
|
||||||
savedTableColumnsByKey,
|
refetch,
|
||||||
tableColumns,
|
savedTableColumnsByKey,
|
||||||
updateViewFields,
|
updateViewFields,
|
||||||
]);
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return { createViewFields, persistColumns };
|
return { createViewFields, persistColumns };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -58,10 +58,15 @@ export const useTableViews = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const submitCurrentView = async () => {
|
const submitCurrentView = async () => {
|
||||||
await persistColumns();
|
|
||||||
await persistFilters();
|
await persistFilters();
|
||||||
await persistSorts();
|
await persistSorts();
|
||||||
};
|
};
|
||||||
|
|
||||||
return { createView, deleteView, submitCurrentView, updateView };
|
return {
|
||||||
|
createView,
|
||||||
|
deleteView,
|
||||||
|
persistColumns,
|
||||||
|
submitCurrentView,
|
||||||
|
updateView,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user