feat: add Table and TableSection components (#1849)
* refactor: rename ui/table to ui/data-table * feat: add Table and TableSection components Closes #1806
This commit is contained in:
@ -0,0 +1,48 @@
|
||||
import { useCurrentTableCellEditMode } from '../table-cell/hooks/useCurrentTableCellEditMode';
|
||||
import { useTableCell } from '../table-cell/hooks/useTableCell';
|
||||
|
||||
import { useMoveSoftFocus } from './useMoveSoftFocus';
|
||||
|
||||
export const useCellInputEventHandlers = <T>({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: {
|
||||
onSubmit?: (newValue: T) => void;
|
||||
onCancel?: () => void;
|
||||
}) => {
|
||||
const { closeTableCell: closeEditableCell } = useTableCell();
|
||||
const { isCurrentTableCellInEditMode: isCurrentCellInEditMode } =
|
||||
useCurrentTableCellEditMode();
|
||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||
|
||||
return {
|
||||
handleClickOutside: (event: MouseEvent | TouchEvent, newValue: T) => {
|
||||
if (isCurrentCellInEditMode) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
onSubmit?.(newValue);
|
||||
|
||||
closeEditableCell();
|
||||
}
|
||||
},
|
||||
handleEscape: () => {
|
||||
closeEditableCell();
|
||||
onCancel?.();
|
||||
},
|
||||
handleEnter: (newValue: T) => {
|
||||
onSubmit?.(newValue);
|
||||
closeEditableCell();
|
||||
moveDown();
|
||||
},
|
||||
handleTab: (newValue: T) => {
|
||||
onSubmit?.(newValue);
|
||||
closeEditableCell();
|
||||
moveRight();
|
||||
},
|
||||
handleShiftTab: (newValue: T) => {
|
||||
onSubmit?.(newValue);
|
||||
closeEditableCell();
|
||||
moveLeft();
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentTableCellInEditModePositionState } from '../states/currentTableCellInEditModePositionState';
|
||||
import { isTableCellInEditModeFamilyState } from '../states/isTableCellInEditModeFamilyState';
|
||||
|
||||
export const useCloseCurrentTableCellInEditMode = () =>
|
||||
useRecoilCallback(({ set, snapshot }) => {
|
||||
return async () => {
|
||||
const currentTableCellInEditModePosition = await snapshot.getPromise(
|
||||
currentTableCellInEditModePositionState,
|
||||
);
|
||||
|
||||
set(
|
||||
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
||||
false,
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
@ -0,0 +1,9 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { RowIdContext } from '../contexts/RowIdContext';
|
||||
|
||||
export const useCurrentRowEntityId = () => {
|
||||
const currentEntityId = useContext(RowIdContext);
|
||||
|
||||
return currentEntityId;
|
||||
};
|
||||
@ -0,0 +1,36 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||
|
||||
import { RowIdContext } from '../contexts/RowIdContext';
|
||||
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
|
||||
|
||||
export const useCurrentRowSelected = () => {
|
||||
const currentRowId = useContext(RowIdContext);
|
||||
|
||||
const [isRowSelected] = useRecoilState(
|
||||
isRowSelectedFamilyState(currentRowId ?? ''),
|
||||
);
|
||||
|
||||
const setCurrentRowSelected = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(newSelectedState: boolean) => {
|
||||
if (!currentRowId) return;
|
||||
|
||||
const isRowSelected = snapshot
|
||||
.getLoadable(isRowSelectedFamilyState(currentRowId))
|
||||
.valueOrThrow();
|
||||
|
||||
if (newSelectedState && !isRowSelected) {
|
||||
set(isRowSelectedFamilyState(currentRowId), true);
|
||||
} else if (!newSelectedState && isRowSelected) {
|
||||
set(isRowSelectedFamilyState(currentRowId), false);
|
||||
}
|
||||
},
|
||||
[currentRowId],
|
||||
);
|
||||
|
||||
return {
|
||||
currentRowSelected: isRowSelected,
|
||||
setCurrentRowSelected,
|
||||
};
|
||||
};
|
||||
18
front/src/modules/ui/data-table/hooks/useDisableSoftFocus.ts
Normal file
18
front/src/modules/ui/data-table/hooks/useDisableSoftFocus.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
|
||||
import { isSoftFocusOnTableCellFamilyState } from '../states/isSoftFocusOnTableCellFamilyState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
|
||||
export const useDisableSoftFocus = () =>
|
||||
useRecoilCallback(({ set, snapshot }) => {
|
||||
return () => {
|
||||
const currentPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(isSoftFocusActiveState, false);
|
||||
|
||||
set(isSoftFocusOnTableCellFamilyState(currentPosition), false);
|
||||
};
|
||||
}, []);
|
||||
39
front/src/modules/ui/data-table/hooks/useLeaveTableFocus.ts
Normal file
39
front/src/modules/ui/data-table/hooks/useLeaveTableFocus.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
|
||||
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
|
||||
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
||||
|
||||
import { useCloseCurrentTableCellInEditMode } from './useCloseCurrentTableCellInEditMode';
|
||||
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
||||
|
||||
export const useLeaveTableFocus = () => {
|
||||
const disableSoftFocus = useDisableSoftFocus();
|
||||
const closeCurrentCellInEditMode = useCloseCurrentTableCellInEditMode();
|
||||
|
||||
return useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const isSoftFocusActive = snapshot
|
||||
.getLoadable(isSoftFocusActiveState)
|
||||
.valueOrThrow();
|
||||
|
||||
const currentHotkeyScope = snapshot
|
||||
.getLoadable(currentHotkeyScopeState)
|
||||
.valueOrThrow();
|
||||
|
||||
if (!isSoftFocusActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentHotkeyScope?.scope === TableHotkeyScope.Table) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeCurrentCellInEditMode();
|
||||
disableSoftFocus();
|
||||
},
|
||||
[closeCurrentCellInEditMode, disableSoftFocus],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,62 @@
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
|
||||
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
||||
|
||||
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
||||
import { useMoveSoftFocus } from './useMoveSoftFocus';
|
||||
|
||||
export const useMapKeyboardToSoftFocus = () => {
|
||||
const { moveDown, moveLeft, moveRight, moveUp } = useMoveSoftFocus();
|
||||
|
||||
const disableSoftFocus = useDisableSoftFocus();
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
|
||||
() => {
|
||||
moveUp();
|
||||
},
|
||||
TableHotkeyScope.TableSoftFocus,
|
||||
[moveUp],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.ArrowDown,
|
||||
() => {
|
||||
moveDown();
|
||||
},
|
||||
TableHotkeyScope.TableSoftFocus,
|
||||
[moveDown],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
|
||||
() => {
|
||||
moveLeft();
|
||||
},
|
||||
TableHotkeyScope.TableSoftFocus,
|
||||
[moveLeft],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.ArrowRight, Key.Tab],
|
||||
() => {
|
||||
moveRight();
|
||||
},
|
||||
TableHotkeyScope.TableSoftFocus,
|
||||
[moveRight],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
setHotkeyScope(TableHotkeyScope.Table, { goto: true });
|
||||
disableSoftFocus();
|
||||
},
|
||||
TableHotkeyScope.TableSoftFocus,
|
||||
[disableSoftFocus],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentTableCellInEditModePositionState } from '../states/currentTableCellInEditModePositionState';
|
||||
import { isTableCellInEditModeFamilyState } from '../states/isTableCellInEditModeFamilyState';
|
||||
import { TableCellPosition } from '../types/TableCellPosition';
|
||||
|
||||
export const useMoveEditModeToTableCellPosition = () =>
|
||||
useRecoilCallback(({ set, snapshot }) => {
|
||||
return (newPosition: TableCellPosition) => {
|
||||
const currentTableCellInEditModePosition = snapshot
|
||||
.getLoadable(currentTableCellInEditModePositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(
|
||||
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
||||
false,
|
||||
);
|
||||
|
||||
set(currentTableCellInEditModePositionState, newPosition);
|
||||
|
||||
set(isTableCellInEditModeFamilyState(newPosition), true);
|
||||
};
|
||||
}, []);
|
||||
159
front/src/modules/ui/data-table/hooks/useMoveSoftFocus.ts
Normal file
159
front/src/modules/ui/data-table/hooks/useMoveSoftFocus.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
|
||||
|
||||
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 { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
|
||||
|
||||
// TODO: stories
|
||||
|
||||
export const useMoveSoftFocus = () => {
|
||||
const tableScopeId = useRecoilScopeId(TableRecoilScopeContext);
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
|
||||
const moveUp = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
let newRowNumber = softFocusPosition.row - 1;
|
||||
|
||||
if (newRowNumber < 0) {
|
||||
newRowNumber = 0;
|
||||
}
|
||||
|
||||
setSoftFocusPosition({
|
||||
...softFocusPosition,
|
||||
row: newRowNumber,
|
||||
});
|
||||
},
|
||||
[setSoftFocusPosition],
|
||||
);
|
||||
|
||||
const moveDown = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableRows = snapshot
|
||||
.getLoadable(numberOfTableRowsState)
|
||||
.valueOrThrow();
|
||||
|
||||
let newRowNumber = softFocusPosition.row + 1;
|
||||
|
||||
if (newRowNumber >= numberOfTableRows) {
|
||||
newRowNumber = numberOfTableRows - 1;
|
||||
}
|
||||
|
||||
setSoftFocusPosition({
|
||||
...softFocusPosition,
|
||||
row: newRowNumber,
|
||||
});
|
||||
},
|
||||
[setSoftFocusPosition],
|
||||
);
|
||||
|
||||
const moveRight = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableColumns = snapshot
|
||||
.getLoadable(numberOfTableColumnsScopedSelector(tableScopeId))
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableRows = snapshot
|
||||
.getLoadable(numberOfTableRowsState)
|
||||
.valueOrThrow();
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
const currentRowNumber = softFocusPosition.row;
|
||||
|
||||
const isLastRowAndLastColumn =
|
||||
currentColumnNumber === numberOfTableColumns - 1 &&
|
||||
currentRowNumber === numberOfTableRows - 1;
|
||||
|
||||
const isLastColumnButNotLastRow =
|
||||
currentColumnNumber === numberOfTableColumns - 1 &&
|
||||
currentRowNumber !== numberOfTableRows - 1;
|
||||
|
||||
const isNotLastColumn =
|
||||
currentColumnNumber !== numberOfTableColumns - 1;
|
||||
|
||||
if (isLastRowAndLastColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotLastColumn) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber,
|
||||
column: currentColumnNumber + 1,
|
||||
});
|
||||
} else if (isLastColumnButNotLastRow) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber + 1,
|
||||
column: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSoftFocusPosition, tableScopeId],
|
||||
);
|
||||
|
||||
const moveLeft = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableColumns = snapshot
|
||||
.getLoadable(numberOfTableColumnsScopedSelector(tableScopeId))
|
||||
.valueOrThrow();
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
const currentRowNumber = softFocusPosition.row;
|
||||
|
||||
const isFirstRowAndFirstColumn =
|
||||
currentColumnNumber === 0 && currentRowNumber === 0;
|
||||
|
||||
const isFirstColumnButNotFirstRow =
|
||||
currentColumnNumber === 0 && currentRowNumber > 0;
|
||||
|
||||
const isNotFirstColumn = currentColumnNumber > 0;
|
||||
|
||||
if (isFirstRowAndFirstColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotFirstColumn) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber,
|
||||
column: currentColumnNumber - 1,
|
||||
});
|
||||
} else if (isFirstColumnButNotFirstRow) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber - 1,
|
||||
column: numberOfTableColumns - 1,
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSoftFocusPosition, tableScopeId],
|
||||
);
|
||||
|
||||
return {
|
||||
moveDown,
|
||||
moveLeft,
|
||||
moveRight,
|
||||
moveUp,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
export const useResetTableRowSelection = () =>
|
||||
useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const tableRowIds = snapshot
|
||||
.getLoadable(tableRowIdsState)
|
||||
.valueOrThrow();
|
||||
|
||||
for (const rowId of tableRowIds) {
|
||||
set(isRowSelectedFamilyState(rowId), false);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
41
front/src/modules/ui/data-table/hooks/useSelectAllRows.ts
Normal file
41
front/src/modules/ui/data-table/hooks/useSelectAllRows.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
|
||||
import { allRowsSelectedStatusSelector } from '../states/selectors/allRowsSelectedStatusSelector';
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
export const useSelectAllRows = () => {
|
||||
const allRowsSelectedStatus = useRecoilValue(allRowsSelectedStatusSelector);
|
||||
|
||||
const selectAllRows = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
const allRowsSelectedStatus = snapshot
|
||||
.getLoadable(allRowsSelectedStatusSelector)
|
||||
.valueOrThrow();
|
||||
|
||||
const tableRowIds = snapshot
|
||||
.getLoadable(tableRowIdsState)
|
||||
.valueOrThrow();
|
||||
|
||||
if (
|
||||
allRowsSelectedStatus === 'none' ||
|
||||
allRowsSelectedStatus === 'some'
|
||||
) {
|
||||
for (const rowId of tableRowIds) {
|
||||
set(isRowSelectedFamilyState(rowId), true);
|
||||
}
|
||||
} else {
|
||||
for (const rowId of tableRowIds) {
|
||||
set(isRowSelectedFamilyState(rowId), false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
allRowsSelectedStatus,
|
||||
selectAllRows,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,70 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState';
|
||||
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
|
||||
import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState';
|
||||
import { availableSortsScopedState } from '@/ui/view-bar/states/availableSortsScopedState';
|
||||
import { entityCountInCurrentViewState } from '@/ui/view-bar/states/entityCountInCurrentViewState';
|
||||
import { FilterDefinition } from '@/ui/view-bar/types/FilterDefinition';
|
||||
import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
|
||||
|
||||
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
|
||||
import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
|
||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
import { useResetTableRowSelection } from './useResetTableRowSelection';
|
||||
|
||||
export const useSetEntityTableData = () => {
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
const tableContextScopeId = useRecoilScopeId(TableRecoilScopeContext);
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends { id: string }>(
|
||||
newEntityArray: T[],
|
||||
filterDefinitionArray: FilterDefinition[],
|
||||
sortDefinitionArray: SortDefinition[],
|
||||
) => {
|
||||
for (const entity of newEntityArray) {
|
||||
const currentEntity = snapshot
|
||||
.getLoadable(entityFieldsFamilyState(entity.id))
|
||||
.valueOrThrow();
|
||||
|
||||
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
|
||||
set(entityFieldsFamilyState(entity.id), entity);
|
||||
}
|
||||
}
|
||||
|
||||
const entityIds = newEntityArray.map((entity) => entity.id);
|
||||
|
||||
set(tableRowIdsState, (currentRowIds) => {
|
||||
if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
return currentRowIds;
|
||||
});
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
set(numberOfTableRowsState, entityIds.length);
|
||||
|
||||
set(entityCountInCurrentViewState, entityIds.length);
|
||||
|
||||
set(
|
||||
availableFiltersScopedState(tableContextScopeId),
|
||||
filterDefinitionArray,
|
||||
);
|
||||
|
||||
set(
|
||||
availableSortsScopedState(tableContextScopeId),
|
||||
sortDefinitionArray,
|
||||
);
|
||||
|
||||
set(isFetchingEntityTableDataState, false);
|
||||
},
|
||||
[resetTableRowSelection, tableContextScopeId],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
|
||||
|
||||
export const useSetRowSelectedState = () =>
|
||||
useRecoilCallback(({ set }) => (rowId: string, selected: boolean) => {
|
||||
set(isRowSelectedFamilyState(rowId), selected);
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
|
||||
import { isSoftFocusOnTableCellFamilyState } from '../states/isSoftFocusOnTableCellFamilyState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
import { TableCellPosition } from '../types/TableCellPosition';
|
||||
|
||||
export const useSetSoftFocusPosition = () =>
|
||||
useRecoilCallback(({ set, snapshot }) => {
|
||||
return (newPosition: TableCellPosition) => {
|
||||
const currentPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(isSoftFocusActiveState, true);
|
||||
|
||||
set(isSoftFocusOnTableCellFamilyState(currentPosition), false);
|
||||
|
||||
set(softFocusPositionState, newPosition);
|
||||
|
||||
set(isSoftFocusOnTableCellFamilyState(newPosition), true);
|
||||
};
|
||||
}, []);
|
||||
115
front/src/modules/ui/data-table/hooks/useTableColumns.ts
Normal file
115
front/src/modules/ui/data-table/hooks/useTableColumns.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { FieldMetadata } from '@/ui/field/types/FieldMetadata';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||
import { ViewFieldForVisibility } from '@/ui/view-bar/types/ViewFieldForVisibility';
|
||||
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
|
||||
|
||||
import { TableContext } from '../contexts/TableContext';
|
||||
import { availableTableColumnsScopedState } from '../states/availableTableColumnsScopedState';
|
||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { savedTableColumnsFamilyState } from '../states/savedTableColumnsFamilyState';
|
||||
import { tableColumnsScopedState } from '../states/tableColumnsScopedState';
|
||||
import { ColumnDefinition } from '../types/ColumnDefinition';
|
||||
|
||||
export const useTableColumns = () => {
|
||||
const { onColumnsChange } = useContext(TableContext);
|
||||
|
||||
const [availableTableColumns] = useRecoilScopedState(
|
||||
availableTableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentViewIdScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setSavedTableColumns = useSetRecoilState(
|
||||
savedTableColumnsFamilyState(currentViewId),
|
||||
);
|
||||
const [tableColumns, setTableColumns] = useRecoilScopedState(
|
||||
tableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const { handleColumnMove } = useMoveViewColumns();
|
||||
|
||||
const handleColumnsChange = useCallback(
|
||||
async (columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||
setSavedTableColumns(columns);
|
||||
setTableColumns(columns);
|
||||
|
||||
await onColumnsChange?.(columns);
|
||||
},
|
||||
[onColumnsChange, setSavedTableColumns, setTableColumns],
|
||||
);
|
||||
|
||||
const handleColumnReorder = useCallback(
|
||||
async (columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||
const updatedColumns = columns.map((column, index) => ({
|
||||
...column,
|
||||
index,
|
||||
}));
|
||||
|
||||
await handleColumnsChange(updatedColumns);
|
||||
},
|
||||
[handleColumnsChange],
|
||||
);
|
||||
|
||||
const handleColumnVisibilityChange = useCallback(
|
||||
async (viewField: ViewFieldForVisibility) => {
|
||||
const isNewColumn = !tableColumns.some(
|
||||
(tableColumns) => tableColumns.key === viewField.key,
|
||||
);
|
||||
|
||||
if (isNewColumn) {
|
||||
const newColumn = availableTableColumns.find(
|
||||
(availableTableColumn) => availableTableColumn.key === viewField.key,
|
||||
);
|
||||
if (!newColumn) return;
|
||||
|
||||
const nextColumns = [
|
||||
...tableColumns,
|
||||
{ ...newColumn, isVisible: true },
|
||||
];
|
||||
|
||||
await handleColumnsChange(nextColumns);
|
||||
} else {
|
||||
const nextColumns = tableColumns.map((previousColumn) =>
|
||||
previousColumn.key === viewField.key
|
||||
? { ...previousColumn, isVisible: !viewField.isVisible }
|
||||
: previousColumn,
|
||||
);
|
||||
|
||||
await handleColumnsChange(nextColumns);
|
||||
}
|
||||
},
|
||||
[tableColumns, availableTableColumns, handleColumnsChange],
|
||||
);
|
||||
|
||||
const handleMoveTableColumn = useCallback(
|
||||
(direction: 'left' | 'right', column: ColumnDefinition<FieldMetadata>) => {
|
||||
const currentColumnArrayIndex = tableColumns.findIndex(
|
||||
(tableColumn) => tableColumn.key === column.key,
|
||||
);
|
||||
const columns = handleColumnMove(
|
||||
direction,
|
||||
currentColumnArrayIndex,
|
||||
tableColumns,
|
||||
);
|
||||
|
||||
setTableColumns(columns);
|
||||
},
|
||||
[tableColumns, setTableColumns, handleColumnMove],
|
||||
);
|
||||
|
||||
return {
|
||||
handleColumnVisibilityChange,
|
||||
handleMoveTableColumn,
|
||||
handleColumnReorder,
|
||||
handleColumnsChange,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState';
|
||||
|
||||
export const useUpsertEntityTableItem = () =>
|
||||
useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends { id: string }>(entity: T) => {
|
||||
const currentEntity = snapshot
|
||||
.getLoadable(entityFieldsFamilyState(entity.id))
|
||||
.valueOrThrow();
|
||||
|
||||
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
|
||||
set(entityFieldsFamilyState(entity.id), entity);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
@ -0,0 +1,33 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState';
|
||||
|
||||
export const useUpsertEntityTableItems = () =>
|
||||
useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends { id: string }>(entities: T[]) => {
|
||||
// Create a map of new entities for quick lookup.
|
||||
const newEntityMap = new Map(
|
||||
entities.map((entity) => [entity.id, entity]),
|
||||
);
|
||||
|
||||
// Filter out entities that are already the same in the state.
|
||||
const entitiesToUpdate = entities.filter((entity) => {
|
||||
const currentEntity = snapshot
|
||||
.getLoadable(entityFieldsFamilyState(entity.id))
|
||||
.valueMaybe();
|
||||
|
||||
return (
|
||||
!currentEntity ||
|
||||
JSON.stringify(currentEntity) !==
|
||||
JSON.stringify(newEntityMap.get(entity.id))
|
||||
);
|
||||
});
|
||||
|
||||
// Batch set state for the filtered entities.
|
||||
for (const entity of entitiesToUpdate) {
|
||||
set(entityFieldsFamilyState(entity.id), entity);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
16
front/src/modules/ui/data-table/hooks/useUpsertTableRowId.ts
Normal file
16
front/src/modules/ui/data-table/hooks/useUpsertTableRowId.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
export const useUpsertTableRowId = () =>
|
||||
useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(rowId: string) => {
|
||||
const currentRowIds = snapshot
|
||||
.getLoadable(tableRowIdsState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(tableRowIdsState, Array.from(new Set([rowId, ...currentRowIds])));
|
||||
},
|
||||
[],
|
||||
);
|
||||
@ -0,0 +1,17 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
export const useUpsertTableRowIds = () =>
|
||||
useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(rowIds: string[]) => {
|
||||
const currentRowIds = snapshot
|
||||
.getLoadable(tableRowIdsState)
|
||||
.valueOrThrow();
|
||||
|
||||
const uniqueRowIds = Array.from(new Set([...rowIds, ...currentRowIds]));
|
||||
set(tableRowIdsState, uniqueRowIds);
|
||||
},
|
||||
[],
|
||||
);
|
||||
Reference in New Issue
Block a user