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:
@ -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() {
|
||||
<EntityTable
|
||||
viewName="All Companies"
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onViewsChange={handleViewsChange}
|
||||
onViewSubmit={handleViewSubmit}
|
||||
onImport={handleImport}
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
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 { mockedCompaniesData } from './companies-mock-data';
|
||||
|
||||
export function CompanyTableMockData() {
|
||||
const setColumns = useSetRecoilState(tableColumnsState);
|
||||
const [, setColumns] = useRecoilScopedState(
|
||||
tableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
setEntityTableData(mockedCompaniesData, []);
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
|
||||
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 { 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,
|
||||
UpdateOnePersonMutationVariables,
|
||||
@ -38,29 +33,16 @@ export function PeopleTable() {
|
||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
|
||||
|
||||
const objectId = 'person';
|
||||
const { handleViewsChange } = useViews({
|
||||
const { handleViewsChange, handleViewSubmit } = useTableViews({
|
||||
availableFilters: peopleFilters,
|
||||
availableSorts,
|
||||
objectId,
|
||||
});
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: objectId,
|
||||
objectId: 'person',
|
||||
viewFieldDefinitions: peopleViewFields,
|
||||
});
|
||||
const { persistFilters } = useViewFilters({
|
||||
availableFilters: peopleFilters,
|
||||
});
|
||||
const { persistSorts } = useViewSorts({ availableSorts });
|
||||
|
||||
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
|
||||
const { setActionBarEntries } = usePersonTableActionBarEntries();
|
||||
|
||||
const handleViewSubmit = useCallback(async () => {
|
||||
await persistFilters();
|
||||
await persistSorts();
|
||||
}, [persistFilters, persistSorts]);
|
||||
|
||||
function handleImport() {
|
||||
openPersonSpreadsheetImport();
|
||||
}
|
||||
@ -79,7 +61,6 @@ export function PeopleTable() {
|
||||
<EntityTable
|
||||
viewName="All People"
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onViewsChange={handleViewsChange}
|
||||
onViewSubmit={handleViewSubmit}
|
||||
onImport={handleImport}
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import { useRef } from 'react';
|
||||
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 { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
@ -92,7 +88,6 @@ type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: React.ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
onViewSubmit?: () => void;
|
||||
onImport?: () => void;
|
||||
@ -102,7 +97,6 @@ type OwnProps<SortField> = {
|
||||
export function EntityTable<SortField>({
|
||||
viewName,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onViewsChange,
|
||||
onViewSubmit,
|
||||
onImport,
|
||||
@ -131,7 +125,6 @@ export function EntityTable<SortField>({
|
||||
<TableHeader
|
||||
viewName={viewName}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={onColumnsChange}
|
||||
onViewsChange={onViewsChange}
|
||||
onViewSubmit={onViewSubmit}
|
||||
onImport={onImport}
|
||||
@ -139,7 +132,7 @@ export function EntityTable<SortField>({
|
||||
<ScrollWrapper>
|
||||
<div>
|
||||
<StyledTable>
|
||||
<EntityTableHeader onColumnsChange={onColumnsChange} />
|
||||
<EntityTableHeader />
|
||||
<EntityTableBody />
|
||||
</StyledTable>
|
||||
</div>
|
||||
|
||||
@ -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<HTMLDivElement>(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||
const hiddenColumns = useRecoilScopedValue(
|
||||
hiddenTableColumnsScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [ref],
|
||||
|
||||
@ -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<ViewFieldMetadata>[]) => 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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<ViewFieldMetadata>[]) => 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={
|
||||
<TableOptionsDropdownContent
|
||||
onColumnsChange={onColumnsChange}
|
||||
onImport={onImport}
|
||||
onViewsChange={onViewsChange}
|
||||
/>
|
||||
|
||||
@ -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<ViewFieldMetadata>[]) => 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<HTMLInputElement>(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),
|
||||
);
|
||||
|
||||
@ -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 = ({
|
||||
<ButtonGroup size={ButtonSize.Small}>
|
||||
<Button
|
||||
title="Update view"
|
||||
disabled={!currentViewId || (!canPersistFilters && !canPersistSorts)}
|
||||
disabled={
|
||||
!currentViewId ||
|
||||
(!canPersistColumns && !canPersistFilters && !canPersistSorts)
|
||||
}
|
||||
onClick={handleViewSubmit}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@ -33,6 +33,8 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
|
||||
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
|
||||
import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
|
||||
|
||||
const StyledBoldDropdownMenuItemsContainer = styled(
|
||||
@ -95,6 +97,9 @@ export const TableViewsDropdownButton = ({
|
||||
const handleViewSelect = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (viewId?: string) => {
|
||||
const savedColumns = await snapshot.getPromise(
|
||||
savedTableColumnsScopedState(viewId),
|
||||
);
|
||||
const savedFilters = await snapshot.getPromise(
|
||||
savedFiltersScopedState(viewId),
|
||||
);
|
||||
@ -102,6 +107,7 @@ export const TableViewsDropdownButton = ({
|
||||
savedSortsScopedState(viewId),
|
||||
);
|
||||
|
||||
set(tableColumnsScopedState(tableScopeId), savedColumns);
|
||||
set(filtersScopedState(tableScopeId), savedFilters);
|
||||
set(sortsScopedState(tableScopeId), savedSorts);
|
||||
set(currentTableViewIdState(tableScopeId), viewId);
|
||||
|
||||
@ -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: [],
|
||||
});
|
||||
@ -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)),
|
||||
),
|
||||
});
|
||||
@ -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,
|
||||
),
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
@ -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 }), {}),
|
||||
});
|
||||
@ -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 }), {}),
|
||||
});
|
||||
@ -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,
|
||||
),
|
||||
});
|
||||
14
front/src/modules/ui/table/states/tableColumnsScopedState.ts
Normal file
14
front/src/modules/ui/table/states/tableColumnsScopedState.ts
Normal 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: [],
|
||||
});
|
||||
@ -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),
|
||||
});
|
||||
@ -1,10 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
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 SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
|
||||
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
|
||||
@ -26,7 +22,6 @@ import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
|
||||
type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
onViewSubmit?: () => void;
|
||||
onImport?: () => void;
|
||||
@ -35,7 +30,6 @@ type OwnProps<SortField> = {
|
||||
export function TableHeader<SortField>({
|
||||
viewName,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onViewsChange,
|
||||
onViewSubmit,
|
||||
onImport,
|
||||
@ -89,7 +83,6 @@ export function TableHeader<SortField>({
|
||||
/>
|
||||
<TableOptionsDropdown
|
||||
onImport={onImport}
|
||||
onColumnsChange={onColumnsChange}
|
||||
onViewsChange={onViewsChange}
|
||||
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||
/>
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
import { useCallback } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import {
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import {
|
||||
tableColumnsByIdState,
|
||||
tableColumnsState,
|
||||
} from '@/ui/table/states/tableColumnsState';
|
||||
import { savedTableColumnsScopedState } from '@/ui/table/states/savedTableColumnsScopedState';
|
||||
import { savedTableColumnsByIdScopedSelector } from '@/ui/table/states/selectors/savedTableColumnsByIdScopedSelector';
|
||||
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
|
||||
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 {
|
||||
SortOrder,
|
||||
@ -20,8 +19,7 @@ import {
|
||||
useGetViewFieldsQuery,
|
||||
useUpdateViewFieldMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_VIEW_FIELDS } from '../graphql/queries/getViewFields';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = {
|
||||
type: 'text',
|
||||
@ -51,24 +49,34 @@ export const useTableViewFields = ({
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setColumns = useSetRecoilState(tableColumnsState);
|
||||
const columnsById = useRecoilValue(tableColumnsByIdState);
|
||||
const [columns, setColumns] = useRecoilScopedState(
|
||||
tableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setSavedColumns = useSetRecoilState(
|
||||
savedTableColumnsScopedState(currentViewId),
|
||||
);
|
||||
const savedColumnsById = useRecoilValue(
|
||||
savedTableColumnsByIdScopedSelector(currentViewId),
|
||||
);
|
||||
|
||||
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
|
||||
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||
|
||||
const createViewFields = useCallback(
|
||||
(columns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
|
||||
(
|
||||
columns: ViewFieldDefinition<ViewFieldMetadata>[],
|
||||
viewId = currentViewId,
|
||||
) => {
|
||||
if (!columns.length) return;
|
||||
|
||||
return createViewFieldsMutation({
|
||||
variables: {
|
||||
data: columns.map((column) => ({
|
||||
...toViewFieldInput(objectName, column),
|
||||
viewId: currentViewId,
|
||||
viewId,
|
||||
})),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||
});
|
||||
},
|
||||
[createViewFieldsMutation, currentViewId, objectName],
|
||||
@ -88,7 +96,6 @@ export const useTableViewFields = ({
|
||||
},
|
||||
where: { id: column.id },
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -96,7 +103,7 @@ export const useTableViewFields = ({
|
||||
[updateViewFieldMutation],
|
||||
);
|
||||
|
||||
useGetViewFieldsQuery({
|
||||
const { refetch } = useGetViewFieldsQuery({
|
||||
variables: {
|
||||
orderBy: { index: SortOrder.Asc },
|
||||
where: {
|
||||
@ -104,50 +111,44 @@ export const useTableViewFields = ({
|
||||
viewId: { equals: currentViewId ?? null },
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.viewFields.length) {
|
||||
setColumns(
|
||||
data.viewFields.map<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,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
onCompleted: async (data) => {
|
||||
if (!data.viewFields.length) {
|
||||
// Populate if empty
|
||||
await createViewFields(viewFieldDefinitions);
|
||||
return refetch();
|
||||
}
|
||||
|
||||
// Populate if empty
|
||||
createViewFields(viewFieldDefinitions);
|
||||
const nextColumns = data.viewFields.map<
|
||||
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(
|
||||
async (nextColumns: ViewFieldDefinition<ViewFieldMetadata>[]) => {
|
||||
setColumns(nextColumns);
|
||||
const persistColumns = useCallback(async () => {
|
||||
const viewFieldsToUpdate = columns.filter(
|
||||
(column) =>
|
||||
savedColumnsById[column.id] &&
|
||||
(savedColumnsById[column.id].columnSize !== column.columnSize ||
|
||||
savedColumnsById[column.id].isVisible !== column.isVisible),
|
||||
);
|
||||
await updateViewFields(viewFieldsToUpdate);
|
||||
|
||||
const viewFieldsToCreate = nextColumns.filter(
|
||||
(nextColumn) => !columnsById[nextColumn.id],
|
||||
);
|
||||
await createViewFields(viewFieldsToCreate);
|
||||
return refetch();
|
||||
}, [columns, refetch, savedColumnsById, updateViewFields]);
|
||||
|
||||
const viewFieldsToUpdate = nextColumns.filter(
|
||||
(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 };
|
||||
return { createViewFields, persistColumns };
|
||||
};
|
||||
|
||||
92
front/src/modules/views/hooks/useTableViews.ts
Normal file
92
front/src/modules/views/hooks/useTableViews.ts
Normal 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 };
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Context, useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
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 type { Filter } from '@/ui/filter-n-sort/types/Filter';
|
||||
import type { FilterDefinitionByEntity } from '@/ui/filter-n-sort/types/FilterDefinitionByEntity';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
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 {
|
||||
useCreateViewFiltersMutation,
|
||||
useDeleteViewFiltersMutation,
|
||||
@ -20,16 +17,16 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useViewFilters = <Entity>({
|
||||
availableFilters,
|
||||
currentViewId,
|
||||
scopeContext,
|
||||
}: {
|
||||
availableFilters: FilterDefinitionByEntity<Entity>[];
|
||||
currentViewId: string | undefined;
|
||||
scopeContext: Context<string | null>;
|
||||
}) => {
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [filters, setFilters] = useRecoilScopedState(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
scopeContext,
|
||||
);
|
||||
const [, setSavedFilters] = useRecoilState(
|
||||
savedFiltersScopedState(currentViewId),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Context, useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
||||
@ -8,10 +8,7 @@ import type {
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
} 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 { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import {
|
||||
useCreateViewSortsMutation,
|
||||
useDeleteViewSortsMutation,
|
||||
@ -23,16 +20,16 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useViewSorts = <SortField>({
|
||||
availableSorts,
|
||||
currentViewId,
|
||||
scopeContext,
|
||||
}: {
|
||||
availableSorts: SortType<SortField>[];
|
||||
currentViewId: string | undefined;
|
||||
scopeContext: Context<string | null>;
|
||||
}) => {
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [sorts, setSorts] = useRecoilScopedState(
|
||||
sortsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
scopeContext,
|
||||
);
|
||||
const [, setSavedSorts] = useRecoilState(
|
||||
savedSortsScopedState(currentViewId),
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
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 {
|
||||
type TableView,
|
||||
@ -21,17 +17,12 @@ import {
|
||||
} from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
import { useViewFilters } from './useViewFilters';
|
||||
import { useViewSorts } from './useViewSorts';
|
||||
|
||||
export const useViews = <Entity, SortField>({
|
||||
availableFilters,
|
||||
availableSorts,
|
||||
export const useViews = ({
|
||||
objectId,
|
||||
onViewCreate,
|
||||
}: {
|
||||
availableFilters: FilterDefinitionByEntity<Entity>[];
|
||||
availableSorts: SortType<SortField>[];
|
||||
objectId: 'company' | 'person';
|
||||
onViewCreate: (viewId: string) => Promise<void>;
|
||||
}) => {
|
||||
const [views, setViews] = useRecoilScopedState(
|
||||
tableViewsState,
|
||||
@ -41,22 +32,11 @@ export const useViews = <Entity, SortField>({
|
||||
tableViewsByIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const selectedFilters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const selectedSorts = useRecoilScopedValue(
|
||||
sortsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const [createViewsMutation] = useCreateViewsMutation();
|
||||
const [updateViewMutation] = useUpdateViewMutation();
|
||||
const [deleteViewsMutation] = useDeleteViewsMutation();
|
||||
|
||||
const { createViewFilters } = useViewFilters({ availableFilters });
|
||||
const { createViewSorts } = useViewSorts({ availableSorts });
|
||||
|
||||
const createViews = useCallback(
|
||||
async (views: TableView[]) => {
|
||||
if (!views.length) return;
|
||||
@ -71,21 +51,9 @@ export const useViews = <Entity, SortField>({
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
views.flatMap((view) => [
|
||||
createViewFilters(selectedFilters, view.id),
|
||||
createViewSorts(selectedSorts, view.id),
|
||||
]),
|
||||
);
|
||||
await Promise.all(views.map((view) => onViewCreate(view.id)));
|
||||
},
|
||||
[
|
||||
createViewFilters,
|
||||
createViewSorts,
|
||||
createViewsMutation,
|
||||
objectId,
|
||||
selectedFilters,
|
||||
selectedSorts,
|
||||
],
|
||||
[createViewsMutation, objectId, onViewCreate],
|
||||
);
|
||||
|
||||
const updateViews = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user