diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index 91fc61c07..fb45fdb08 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -1,5 +1,3 @@ -import { useCallback } from 'react'; - import { companyViewFields } from '@/companies/constants/companyViewFields'; import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries'; 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 { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; -import { useTableViewFields } from '@/views/hooks/useTableViewFields'; -import { useViewFilters } from '@/views/hooks/useViewFilters'; -import { useViews } from '@/views/hooks/useViews'; -import { useViewSorts } from '@/views/hooks/useViewSorts'; +import { useTableViews } from '@/views/hooks/useTableViews'; import { SortOrder, UpdateOneCompanyMutationVariables, @@ -37,31 +32,18 @@ export function CompanyTable() { const [updateEntityMutation] = useUpdateOneCompanyMutation(); const upsertEntityTableItem = useUpsertEntityTableItem(); - const objectId = 'company'; - const { handleViewsChange } = useViews({ + const { handleViewsChange, handleViewSubmit } = useTableViews({ availableFilters: companiesFilters, availableSorts, - objectId, - }); - const { handleColumnsChange } = useTableViewFields({ - objectName: objectId, + objectId: 'company', viewFieldDefinitions: companyViewFields, }); - const { persistFilters } = useViewFilters({ - availableFilters: companiesFilters, - }); - const { persistSorts } = useViewSorts({ availableSorts }); const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport(); const { setContextMenuEntries } = useCompanyTableContextMenuEntries(); const { setActionBarEntries } = useCompanyTableActionBarEntries(); - const handleViewSubmit = useCallback(async () => { - await persistFilters(); - await persistSorts(); - }, [persistFilters, persistSorts]); - function handleImport() { openCompanySpreadsheetImport(); } @@ -80,7 +62,6 @@ export function CompanyTable() { { - await persistFilters(); - await persistSorts(); - }, [persistFilters, persistSorts]); - function handleImport() { openPersonSpreadsheetImport(); } @@ -79,7 +61,6 @@ export function PeopleTable() { = { viewName: string; viewIcon?: React.ReactNode; availableSorts?: Array>; - onColumnsChange?: (columns: ViewFieldDefinition[]) => void; onViewsChange?: (views: TableView[]) => void; onViewSubmit?: () => void; onImport?: () => void; @@ -102,7 +97,6 @@ type OwnProps = { export function EntityTable({ viewName, availableSorts, - onColumnsChange, onViewsChange, onViewSubmit, onImport, @@ -131,7 +125,6 @@ export function EntityTable({ ({
- +
diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx index cf5fb87c6..f7e4e3910 100644 --- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx +++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx @@ -1,7 +1,6 @@ import { cloneElement, ComponentProps, useRef } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { IconButton } from '@/ui/button/components/IconButton'; 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 { IconPlus } from '@/ui/icon'; 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)` font-weight: ${({ theme }) => theme.font.weight.regular}; @@ -29,7 +30,10 @@ export const EntityTableColumnMenu = ({ const ref = useRef(null); const theme = useTheme(); - const hiddenColumns = useRecoilValue(hiddenTableColumnsState); + const hiddenColumns = useRecoilScopedValue( + hiddenTableColumnsScopedSelector, + TableRecoilScopeContext, + ); useListenClickOutside({ refs: [ref], diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx index d5b1b25c2..7ddb90bcc 100644 --- a/front/src/modules/ui/table/components/EntityTableHeader.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx @@ -1,23 +1,20 @@ import { useCallback, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilCallback, useRecoilState } from 'recoil'; import { IconButton } from '@/ui/button/components/IconButton'; -import type { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; import { IconPlus } from '@/ui/icon'; 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 { - hiddenTableColumnsState, - tableColumnsByIdState, - tableColumnsState, - visibleTableColumnsState, -} from '../states/tableColumnsState'; +import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector'; +import { tableColumnsByIdScopedSelector } from '../states/selectors/tableColumnsByIdScopedSelector'; +import { visibleTableColumnsScopedSelector } from '../states/selectors/visibleTableColumnsScopedSelector'; +import { tableColumnsScopedState } from '../states/tableColumnsScopedState'; import { ColumnHead } from './ColumnHead'; import { EntityTableColumnMenu } from './EntityTableColumnMenu'; @@ -78,18 +75,26 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)` z-index: ${({ theme }) => theme.lastLayerZIndex}; `; -export type EntityTableHeaderProps = { - onColumnsChange?: (columns: ViewFieldDefinition[]) => void; -}; - -export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) { +export function EntityTableHeader() { const theme = useTheme(); - const [columns, setColumns] = useRecoilState(tableColumnsState); const [offset, setOffset] = useRecoilState(resizeFieldOffsetState); - const columnsById = useRecoilValue(tableColumnsByIdState); - const hiddenColumns = useRecoilValue(hiddenTableColumnsState); - const visibleColumns = useRecoilValue(visibleTableColumnsState); + const [columns, setColumns] = useRecoilScopedState( + tableColumnsScopedState, + TableRecoilScopeContext, + ); + const columnsById = useRecoilScopedValue( + tableColumnsByIdScopedSelector, + TableRecoilScopeContext, + ); + const hiddenColumns = useRecoilScopedValue( + hiddenTableColumnsScopedSelector, + TableRecoilScopeContext, + ); + const visibleColumns = useRecoilScopedValue( + visibleTableColumnsScopedSelector, + TableRecoilScopeContext, + ); const [initialPointerPositionX, setInitialPointerPositionX] = useState< number | null @@ -129,14 +134,14 @@ export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) { : column, ); - (onColumnsChange ?? setColumns)(nextColumns); + setColumns(nextColumns); } set(resizeFieldOffsetState, 0); setInitialPointerPositionX(null); setResizedFieldId(null); }, - [resizedFieldId, columnsById, columns, onColumnsChange, setColumns], + [resizedFieldId, columnsById, columns, setColumns], ); useTrackPointer({ @@ -158,9 +163,9 @@ export function EntityTableHeader({ onColumnsChange }: EntityTableHeaderProps) { column.id === columnId ? { ...column, isVisible: true } : column, ); - (onColumnsChange ?? setColumns)(nextColumns); + setColumns(nextColumns); }, - [columns, onColumnsChange, setColumns], + [columns, setColumns], ); return ( diff --git a/front/src/modules/ui/table/components/EntityTableRow.tsx b/front/src/modules/ui/table/components/EntityTableRow.tsx index f744faf3c..5b464bc42 100644 --- a/front/src/modules/ui/table/components/EntityTableRow.tsx +++ b/front/src/modules/ui/table/components/EntityTableRow.tsx @@ -1,9 +1,11 @@ import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; + +import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { ViewFieldContext } from '../contexts/ViewFieldContext'; 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 { EntityTableCell } from './EntityTableCell'; @@ -14,7 +16,10 @@ const StyledRow = styled.tr<{ selected: boolean }>` `; export function EntityTableRow({ rowId }: { rowId: string }) { - const columns = useRecoilValue(visibleTableColumnsState); + const columns = useRecoilScopedValue( + visibleTableColumnsScopedSelector, + TableRecoilScopeContext, + ); const { currentRowSelected } = useCurrentRowSelected(); return ( diff --git a/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts b/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts index 9903a1f87..30a1c5db2 100644 --- a/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts +++ b/front/src/modules/ui/table/hooks/useMoveSoftFocus.ts @@ -1,13 +1,17 @@ import { useRecoilCallback } from 'recoil'; +import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; + 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 { numberOfTableColumnsState } from '../states/tableColumnsState'; import { useSetSoftFocusPosition } from './useSetSoftFocusPosition'; // TODO: stories export function useMoveSoftFocus() { + const tableScopeId = useContextScopeId(TableRecoilScopeContext); const setSoftFocusPosition = useSetSoftFocusPosition(); const moveUp = useRecoilCallback( @@ -64,7 +68,7 @@ export function useMoveSoftFocus() { .valueOrThrow(); const numberOfTableColumns = snapshot - .getLoadable(numberOfTableColumnsState) + .getLoadable(numberOfTableColumnsScopedSelector(tableScopeId)) .valueOrThrow(); const numberOfTableRows = snapshot @@ -101,7 +105,7 @@ export function useMoveSoftFocus() { }); } }, - [setSoftFocusPosition], + [setSoftFocusPosition, tableScopeId], ); const moveLeft = useRecoilCallback( @@ -112,7 +116,7 @@ export function useMoveSoftFocus() { .valueOrThrow(); const numberOfTableColumns = snapshot - .getLoadable(numberOfTableColumnsState) + .getLoadable(numberOfTableColumnsScopedSelector(tableScopeId)) .valueOrThrow(); const currentColumnNumber = softFocusPosition.column; @@ -142,7 +146,7 @@ export function useMoveSoftFocus() { }); } }, - [setSoftFocusPosition], + [setSoftFocusPosition, tableScopeId], ); return { diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx index 361443317..abbb8da49 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdown.tsx @@ -1,8 +1,4 @@ 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 { type TableView } from '../../states/tableViewsState'; @@ -11,14 +7,12 @@ import { TableOptionsDropdownButton } from './TableOptionsDropdownButton'; import { TableOptionsDropdownContent } from './TableOptionsDropdownContent'; type TableOptionsDropdownProps = { - onColumnsChange?: (columns: ViewFieldDefinition[]) => void; onViewsChange?: (views: TableView[]) => void; onImport?: () => void; customHotkeyScope: HotkeyScope; }; export function TableOptionsDropdown({ - onColumnsChange, onViewsChange, onImport, customHotkeyScope, @@ -30,7 +24,6 @@ export function TableOptionsDropdown({ dropdownKey="options" dropdownComponents={ diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx index 4c951e0f6..7da1eb61b 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownContent.tsx @@ -1,6 +1,6 @@ import { type FormEvent, useCallback, useRef, useState } from 'react'; import { useTheme } from '@emotion/react'; -import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilCallback, useRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; import { v4 } from 'uuid'; @@ -27,16 +27,16 @@ import { IconPlus, IconTag, } from '@/ui/icon'; -import { - hiddenTableColumnsState, - tableColumnsState, - visibleTableColumnsState, -} from '@/ui/table/states/tableColumnsState'; +import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; 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 { 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 { currentTableViewIdState, type TableView, @@ -49,7 +49,6 @@ import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; import { TableOptionsDropdownSection } from './TableOptionsDropdownSection'; type TableOptionsDropdownButtonProps = { - onColumnsChange?: (columns: ViewFieldDefinition[]) => void; onViewsChange?: (views: TableView[]) => void; onImport?: () => void; }; @@ -59,7 +58,6 @@ enum Option { } export function TableOptionsDropdownContent({ - onColumnsChange, onViewsChange, onImport, }: TableOptionsDropdownButtonProps) { @@ -75,28 +73,37 @@ export function TableOptionsDropdownContent({ const viewEditInputRef = useRef(null); - const [columns, setColumns] = useRecoilState(tableColumnsState); const [viewEditMode, setViewEditMode] = useRecoilState( tableViewEditModeState, ); - const visibleColumns = useRecoilValue(visibleTableColumnsState); - const hiddenColumns = useRecoilValue(hiddenTableColumnsState); + const [columns, setColumns] = useRecoilScopedState( + tableColumnsScopedState, + TableRecoilScopeContext, + ); + const visibleColumns = useRecoilScopedValue( + visibleTableColumnsScopedSelector, + TableRecoilScopeContext, + ); + const hiddenColumns = useRecoilScopedValue( + hiddenTableColumnsScopedSelector, + TableRecoilScopeContext, + ); const viewsById = useRecoilScopedValue( tableViewsByIdState, TableRecoilScopeContext, ); const handleColumnVisibilityChange = useCallback( - (columnId: string, nextIsVisible: boolean) => { + async (columnId: string, nextIsVisible: boolean) => { const nextColumns = columns.map((column) => column.id === columnId ? { ...column, isVisible: nextIsVisible } : column, ); - (onColumnsChange ?? setColumns)(nextColumns); + setColumns(nextColumns); }, - [columns, onColumnsChange, setColumns], + [columns, setColumns], ); const renderFieldActions = useCallback( @@ -144,6 +151,14 @@ export function TableOptionsDropdownContent({ const viewToCreate = { id: v4(), name }; 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( filtersScopedState(tableScopeId), ); diff --git a/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx b/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx index dbc38a0b0..35d008932 100644 --- a/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx +++ b/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; 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 { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; +import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState'; +import { canPersistTableColumnsScopedSelector } from '../../states/selectors/canPersistTableColumnsScopedSelector'; +import { tableColumnsScopedState } from '../../states/tableColumnsScopedState'; import { currentTableViewIdState, tableViewEditModeState, @@ -56,12 +59,38 @@ export const TableUpdateViewButtonGroup = ({ currentTableViewIdState, 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( canPersistFiltersScopedSelector([tableScopeId, currentViewId]), ); + + const selectedSorts = useRecoilScopedValue( + sortsScopedState, + TableRecoilScopeContext, + ); + const setSavedSorts = useSetRecoilState(savedSortsScopedState(currentViewId)); const canPersistSorts = useRecoilValue( canPersistSortsScopedSelector([tableScopeId, currentViewId]), ); + const setViewEditMode = useSetRecoilState(tableViewEditModeState); const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({ @@ -82,23 +111,21 @@ export const TableUpdateViewButtonGroup = ({ setIsDropdownOpen(false); }, []); - const handleViewSubmit = useRecoilCallback( - ({ set, snapshot }) => - async () => { - await Promise.resolve(onViewSubmit?.()); + const handleViewSubmit = useCallback(async () => { + await Promise.resolve(onViewSubmit?.()); - const selectedFilters = await snapshot.getPromise( - filtersScopedState(tableScopeId), - ); - set(savedFiltersScopedState(currentViewId), selectedFilters); - - const selectedSorts = await snapshot.getPromise( - sortsScopedState(tableScopeId), - ); - set(savedSortsScopedState(currentViewId), selectedSorts); - }, - [currentViewId, onViewSubmit, tableScopeId], - ); + setSavedColumns(currentColumns); + setSavedFilters(selectedFilters); + setSavedSorts(selectedSorts); + }, [ + currentColumns, + onViewSubmit, + selectedFilters, + selectedSorts, + setSavedColumns, + setSavedFilters, + setSavedSorts, + ]); useScopedHotkeys( [Key.Enter, Key.Escape], @@ -112,7 +139,10 @@ export const TableUpdateViewButtonGroup = ({