mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-22 03:17:40 +03:00
feat: default record group table (#8397)
This PR is preparing states to we'll be able to handle view groups correctly with table data. RowIds are now stores in 2 component states, one storing ids by view group and another storing all the rowIds. We're doing that because some other state like focus, or select must not be scoped by view group id.
This commit is contained in:
parent
0125d58ba8
commit
fb0221b4c1
@ -3,8 +3,10 @@ import { renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { mocks } from '@/auth/hooks/__mocks__/useAuth';
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
|
||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
|
||||
const recordTableId = 'people';
|
||||
@ -23,12 +25,18 @@ const Wrapper = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<HookMockWrapper>
|
||||
<ObjectNamePluralSetter>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<RecordTableComponentInstance
|
||||
recordTableId={recordTableId}
|
||||
onColumnsChange={onColumnsChange}
|
||||
>
|
||||
<RecordGroupContext.Provider value={{ recordGroupId: 'default' }}>
|
||||
{children}
|
||||
</RecordGroupContext.Provider>
|
||||
</RecordTableComponentInstance>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</ObjectNamePluralSetter>
|
||||
</HookMockWrapper>
|
||||
);
|
||||
|
@ -0,0 +1,37 @@
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { hasRecordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/hasRecordGroupDefinitionsComponentSelector';
|
||||
import { recordGroupDefinitionsComponentState } from '@/object-record/record-group/states/recordGroupDefinitionsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
export const useCurrentRecordGroupDefinition = (recordTableId?: string) => {
|
||||
const context = useContext(RecordGroupContext);
|
||||
|
||||
const hasRecordGroups = useRecoilComponentValueV2(
|
||||
hasRecordGroupDefinitionsComponentSelector,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const recordGroupDefinitions = useRecoilComponentValueV2(
|
||||
recordGroupDefinitionsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const recordGroupDefinition = useMemo(() => {
|
||||
if (!hasRecordGroups) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useCurrentRecordGroupDefinition must be used within a RecordGroupContextProvider.',
|
||||
);
|
||||
}
|
||||
|
||||
return recordGroupDefinitions.find(
|
||||
({ id }) => id === context.recordGroupId,
|
||||
);
|
||||
}, [context, hasRecordGroups, recordGroupDefinitions]);
|
||||
|
||||
return recordGroupDefinition;
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const useCurrentRecordGroupId = () => {
|
||||
const context = useContext(RecordGroupContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useCurrentRecordGroupId must be used within a RecordGroupContextProvider.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!context.recordGroupId) {
|
||||
throw new Error(
|
||||
'RecordGroupContext is malformed. recordGroupId is missing.',
|
||||
);
|
||||
}
|
||||
|
||||
return context.recordGroupId;
|
||||
};
|
@ -23,7 +23,7 @@ export const useRecordGroupReorder = ({
|
||||
);
|
||||
|
||||
const { visibleRecordGroups } = useRecordGroups({
|
||||
objectNameSingular,
|
||||
objectNameSingular: objectNameSingular,
|
||||
});
|
||||
|
||||
const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type RecordGroupContextProps = {
|
||||
recordGroupId: string;
|
||||
};
|
||||
|
||||
export const RecordGroupContext = createContext<RecordGroupContextProps>(
|
||||
{} as RecordGroupContextProps,
|
||||
);
|
@ -0,0 +1,21 @@
|
||||
import { recordGroupDefinitionsComponentState } from '@/object-record/record-group/states/recordGroupDefinitionsComponentState';
|
||||
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
|
||||
export const hasRecordGroupDefinitionsComponentSelector =
|
||||
createComponentSelectorV2<boolean>({
|
||||
key: 'hasRecordGroupDefinitionsComponentSelector',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const recordGroupDefinitions = get(
|
||||
recordGroupDefinitionsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
return recordGroupDefinitions.length > 0;
|
||||
},
|
||||
});
|
@ -6,6 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||
import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hooks/useRecordTableRecordGqlFields';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
|
||||
@ -14,6 +15,8 @@ import { tableViewFilterGroupsComponentState } from '@/object-record/record-tabl
|
||||
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { WorkspaceActivationStatus } from '~/generated/graphql';
|
||||
|
||||
export const useFindManyParams = (
|
||||
@ -24,6 +27,9 @@ export const useFindManyParams = (
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const currentRecordGroupDefinition =
|
||||
useCurrentRecordGroupDefinition(recordTableId);
|
||||
|
||||
const tableViewFilterGroups = useRecoilComponentValueV2(
|
||||
tableViewFilterGroupsComponentState,
|
||||
recordTableId,
|
||||
@ -37,15 +43,45 @@ export const useFindManyParams = (
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const filter = computeViewRecordGqlOperationFilter(
|
||||
const stateFilter = computeViewRecordGqlOperationFilter(
|
||||
tableFilters,
|
||||
objectMetadataItem?.fields ?? [],
|
||||
tableViewFilterGroups,
|
||||
);
|
||||
|
||||
const recordGroupFilter = useMemo(() => {
|
||||
if (isDefined(currentRecordGroupDefinition)) {
|
||||
const fieldMetadataItem = objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === currentRecordGroupDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
[fieldMetadataItem.name]: {
|
||||
eq: currentRecordGroupDefinition.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Handle case when value is nullable
|
||||
|
||||
return {};
|
||||
}, [objectMetadataItem.fields, currentRecordGroupDefinition]);
|
||||
|
||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, tableSorts);
|
||||
|
||||
return { objectNameSingular, filter, orderBy };
|
||||
return {
|
||||
objectNameSingular,
|
||||
filter: {
|
||||
...stateFilter,
|
||||
...recordGroupFilter,
|
||||
},
|
||||
orderBy,
|
||||
};
|
||||
};
|
||||
|
||||
export const useLoadRecordIndexTable = (objectNameSingular: string) => {
|
||||
|
@ -1,18 +1,22 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
||||
|
||||
import { hasRecordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/hasRecordGroupDefinitionsComponentSelector';
|
||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||
import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect';
|
||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody';
|
||||
import { RecordTableBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEffect';
|
||||
import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect';
|
||||
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
|
||||
import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect';
|
||||
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
|
||||
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
|
||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -40,17 +44,17 @@ export const RecordTable = ({
|
||||
}: RecordTableProps) => {
|
||||
const tableBodyRef = useRef<HTMLTableElement>(null);
|
||||
|
||||
const { toggleClickOutsideListener } = useClickOutsideListener(
|
||||
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
||||
);
|
||||
|
||||
const isRecordTableInitialLoading = useRecoilComponentValueV2(
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { toggleClickOutsideListener } = useClickOutsideListener(
|
||||
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
||||
);
|
||||
|
||||
const tableRowIds = useRecoilComponentValueV2(
|
||||
tableRowIdsComponentState,
|
||||
tableAllRowIdsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
@ -59,15 +63,20 @@ export const RecordTable = ({
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
||||
const hasRecordGroups = useRecoilComponentValueV2(
|
||||
hasRecordGroupDefinitionsComponentSelector,
|
||||
recordTableId,
|
||||
});
|
||||
);
|
||||
|
||||
const recordTableIsEmpty =
|
||||
!isRecordTableInitialLoading &&
|
||||
tableRowIds.length === 0 &&
|
||||
isNull(pendingRecordId);
|
||||
|
||||
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
||||
recordTableId,
|
||||
});
|
||||
|
||||
if (!isNonEmptyString(objectNameSingular)) {
|
||||
return <></>;
|
||||
}
|
||||
@ -82,7 +91,11 @@ export const RecordTable = ({
|
||||
recordTableId={recordTableId}
|
||||
viewBarId={viewBarId}
|
||||
>
|
||||
<RecordTableBodyEffect />
|
||||
{!hasRecordGroups ? (
|
||||
<RecordTableNoRecordGroupBodyEffect />
|
||||
) : (
|
||||
<RecordTableRecordGroupBodyEffects />
|
||||
)}
|
||||
<RecordTableBodyUnselectEffect
|
||||
tableBodyRef={tableBodyRef}
|
||||
recordTableId={recordTableId}
|
||||
@ -92,10 +105,15 @@ export const RecordTable = ({
|
||||
) : (
|
||||
<>
|
||||
<StyledTable className="entity-table-cell" ref={tableBodyRef}>
|
||||
<RecordTableHeader
|
||||
objectMetadataNameSingular={objectNameSingular}
|
||||
<RecordTableHeader objectNameSingular={objectNameSingular} />
|
||||
{!hasRecordGroups ? (
|
||||
<RecordTableNoRecordGroupBody />
|
||||
) : (
|
||||
<RecordTableRecordGroupsBody
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
<RecordTableBody />
|
||||
)}
|
||||
<RecordTableStickyEffect />
|
||||
</StyledTable>
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFetchMoreLoader';
|
||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableRows = () => {
|
||||
const tableRowIds = useRecoilComponentValueV2(tableRowIdsComponentState);
|
||||
export const RecordTableNoRecordGroupRows = () => {
|
||||
const rowIds = useRecoilComponentValueV2(tableAllRowIdsComponentState);
|
||||
|
||||
return (
|
||||
<>
|
||||
{tableRowIds.map((recordId, rowIndex) => {
|
||||
{rowIds.map((recordId, rowIndex) => {
|
||||
return (
|
||||
<RecordTableRow
|
||||
key={recordId}
|
@ -0,0 +1,26 @@
|
||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { tableRowIdsByGroupComponentFamilyState } from '@/object-record/record-table/states/tableRowIdsByGroupComponentFamilyState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableRecordGroupRows = () => {
|
||||
const recordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
const allRowIds = useRecoilComponentValueV2(tableAllRowIdsComponentState);
|
||||
|
||||
const recordGroupRowIds = useRecoilComponentFamilyValueV2(
|
||||
tableRowIdsByGroupComponentFamilyState,
|
||||
recordGroupId,
|
||||
);
|
||||
|
||||
return recordGroupRowIds.map((recordId) => {
|
||||
// Find the index of the recordId in allRowIds
|
||||
const rowIndex = allRowIds.indexOf(recordId);
|
||||
|
||||
return (
|
||||
<RecordTableRow key={recordId} recordId={recordId} rowIndex={rowIndex} />
|
||||
);
|
||||
});
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { useScrollLeftValue } from '@/ui/utilities/scroll/hooks/useScrollLeftValue';
|
||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
export const RecordTableStickyEffect = () => {
|
||||
const scrollTop = useScrollTopValue('recordTableWithWrappers');
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollTop > 0) {
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.add('header-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.remove('header-sticky');
|
||||
}
|
||||
}, [scrollTop]);
|
||||
|
||||
const scrollLeft = useScrollLeftValue('recordTableWithWrappers');
|
||||
|
||||
const setIsRecordTableScrolledLeft = useSetRecoilComponentStateV2(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsRecordTableScrolledLeft(scrollLeft === 0);
|
||||
if (scrollLeft > 0) {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
?.classList.add('first-columns-sticky');
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.add('first-columns-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
?.classList.remove('first-columns-sticky');
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.remove('first-columns-sticky');
|
||||
}
|
||||
}, [scrollLeft, setIsRecordTableScrolledLeft]);
|
||||
|
||||
return <></>;
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
type RecordTableEmptyHandlerProps = {
|
||||
recordTableId: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const RecordTableEmptyHandler = ({
|
||||
recordTableId,
|
||||
children,
|
||||
}: RecordTableEmptyHandlerProps) => {
|
||||
const isRecordTableInitialLoading = useRecoilComponentValueV2(
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const tableRowIds = useRecoilComponentValueV2(
|
||||
tableAllRowIdsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const pendingRecordId = useRecoilComponentValueV2(
|
||||
recordTablePendingRecordIdComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const recordTableIsEmpty =
|
||||
!isRecordTableInitialLoading &&
|
||||
tableRowIds.length === 0 &&
|
||||
isNull(pendingRecordId);
|
||||
|
||||
if (recordTableIsEmpty) {
|
||||
return <RecordTableEmptyState />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
@ -5,7 +5,7 @@ import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionM
|
||||
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
@ -18,8 +18,8 @@ export const useResetTableRowSelection = (recordTableId?: string) => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const tableRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableRowIdsComponentState,
|
||||
const tableAllRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableAllRowIdsComponentState,
|
||||
recordTableIdFromContext,
|
||||
);
|
||||
|
||||
@ -41,9 +41,9 @@ export const useResetTableRowSelection = (recordTableId?: string) => {
|
||||
);
|
||||
|
||||
return useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
const tableRowIds = getSnapshotValue(snapshot, tableRowIdsState);
|
||||
const tableRowIds = getSnapshotValue(snapshot, tableAllRowIdsState);
|
||||
|
||||
for (const rowId of tableRowIds) {
|
||||
set(isRowSelectedFamilyState(rowId), false);
|
||||
@ -54,7 +54,7 @@ export const useResetTableRowSelection = (recordTableId?: string) => {
|
||||
set(isActionMenuDropdownOpenState, false);
|
||||
},
|
||||
[
|
||||
tableRowIdsState,
|
||||
tableAllRowIdsState,
|
||||
hasUserSelectedAllRowsState,
|
||||
isActionMenuDropdownOpenState,
|
||||
isRowSelectedFamilyState,
|
||||
|
@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
|
||||
@ -11,14 +11,14 @@ export const useSelectAllRows = (recordTableId?: string) => {
|
||||
allRowsSelectedStatusComponentSelector,
|
||||
recordTableId,
|
||||
);
|
||||
const tableRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableRowIdsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
const isRowSelectedFamilyState = useRecoilComponentCallbackStateV2(
|
||||
isRowSelectedComponentFamilyState,
|
||||
recordTableId,
|
||||
);
|
||||
const tableAllRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableAllRowIdsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const selectAllRows = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
@ -28,7 +28,7 @@ export const useSelectAllRows = (recordTableId?: string) => {
|
||||
allRowsSelectedStatusSelector,
|
||||
);
|
||||
|
||||
const tableRowIds = getSnapshotValue(snapshot, tableRowIdsState);
|
||||
const tableRowIds = getSnapshotValue(snapshot, tableAllRowIdsState);
|
||||
|
||||
if (
|
||||
allRowsSelectedStatus === 'none' ||
|
||||
@ -43,7 +43,11 @@ export const useSelectAllRows = (recordTableId?: string) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[allRowsSelectedStatusSelector, tableRowIdsState, isRowSelectedFamilyState],
|
||||
[
|
||||
allRowsSelectedStatusSelector,
|
||||
tableAllRowIdsState,
|
||||
isRowSelectedFamilyState,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -1,14 +1,16 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { recordGroupDefinitionsComponentState } from '@/object-record/record-group/states/recordGroupDefinitionsComponentState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { numberOfTableRowsComponentState } from '@/object-record/record-table/states/numberOfTableRowsComponentState';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { tableRowIdsByGroupComponentFamilyState } from '@/object-record/record-table/states/tableRowIdsByGroupComponentFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type useSetRecordTableDataProps = {
|
||||
recordTableId?: string;
|
||||
@ -19,12 +21,12 @@ export const useSetRecordTableData = ({
|
||||
recordTableId,
|
||||
onEntityCountChange,
|
||||
}: useSetRecordTableDataProps) => {
|
||||
const tableRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableRowIdsComponentState,
|
||||
const tableRowIdsByGroupFamilyState = useRecoilComponentCallbackStateV2(
|
||||
tableRowIdsByGroupComponentFamilyState,
|
||||
recordTableId,
|
||||
);
|
||||
const numberOfTableRowsState = useRecoilComponentCallbackStateV2(
|
||||
numberOfTableRowsComponentState,
|
||||
const tableAllRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableAllRowIdsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
const isRowSelectedFamilyState = useRecoilComponentCallbackStateV2(
|
||||
@ -35,11 +37,23 @@ export const useSetRecordTableData = ({
|
||||
hasUserSelectedAllRowsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
const recordGroupDefinitionsState = useRecoilComponentCallbackStateV2(
|
||||
recordGroupDefinitionsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends ObjectRecord>(newRecords: T[], totalCount?: number) => {
|
||||
for (const record of newRecords) {
|
||||
<T extends ObjectRecord>({
|
||||
records,
|
||||
recordGroupId,
|
||||
totalCount,
|
||||
}: {
|
||||
records: T[];
|
||||
recordGroupId?: string;
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
for (const record of records) {
|
||||
// TODO: refactor with scoped state later
|
||||
const currentRecord = snapshot
|
||||
.getLoadable(recordStoreFamilyState(record.id))
|
||||
@ -50,14 +64,24 @@ export const useSetRecordTableData = ({
|
||||
}
|
||||
}
|
||||
|
||||
const currentRowIds = getSnapshotValue(snapshot, tableRowIdsState);
|
||||
const currentRowIds = getSnapshotValue(
|
||||
snapshot,
|
||||
recordGroupId
|
||||
? tableRowIdsByGroupFamilyState(recordGroupId)
|
||||
: tableAllRowIdsState,
|
||||
);
|
||||
|
||||
const hasUserSelectedAllRows = getSnapshotValue(
|
||||
snapshot,
|
||||
hasUserSelectedAllRowsState,
|
||||
);
|
||||
|
||||
const recordIds = newRecords.map((record) => record.id);
|
||||
const recordGroupDefinitions = getSnapshotValue(
|
||||
snapshot,
|
||||
recordGroupDefinitionsState,
|
||||
);
|
||||
|
||||
const recordIds = records.map((record) => record.id);
|
||||
|
||||
if (!isDeeplyEqual(currentRowIds, recordIds)) {
|
||||
if (hasUserSelectedAllRows) {
|
||||
@ -66,14 +90,36 @@ export const useSetRecordTableData = ({
|
||||
}
|
||||
}
|
||||
|
||||
set(tableRowIdsState, recordIds);
|
||||
set(numberOfTableRowsState, totalCount ?? 0);
|
||||
if (isDefined(recordGroupId)) {
|
||||
// TODO: Hack to store all ids in the same order as the record group definitions
|
||||
// Should be replaced by something more efficient
|
||||
const allRowIds: string[] = [];
|
||||
|
||||
set(tableRowIdsByGroupFamilyState(recordGroupId), recordIds);
|
||||
|
||||
for (const recordGroupDefinition of recordGroupDefinitions) {
|
||||
const tableRowIdsByGroup =
|
||||
recordGroupDefinition.id !== recordGroupId
|
||||
? getSnapshotValue(
|
||||
snapshot,
|
||||
tableRowIdsByGroupFamilyState(recordGroupDefinition.id),
|
||||
)
|
||||
: recordIds;
|
||||
|
||||
allRowIds.push(...tableRowIdsByGroup);
|
||||
}
|
||||
set(tableAllRowIdsState, allRowIds);
|
||||
} else {
|
||||
set(tableAllRowIdsState, recordIds);
|
||||
}
|
||||
|
||||
onEntityCountChange(totalCount);
|
||||
}
|
||||
},
|
||||
[
|
||||
numberOfTableRowsState,
|
||||
tableRowIdsState,
|
||||
tableRowIdsByGroupFamilyState,
|
||||
tableAllRowIdsState,
|
||||
recordGroupDefinitionsState,
|
||||
onEntityCountChange,
|
||||
isRowSelectedFamilyState,
|
||||
hasUserSelectedAllRowsState,
|
||||
|
@ -3,9 +3,9 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
|
||||
import { numberOfTableRowsComponentState } from '@/object-record/record-table/states/numberOfTableRowsComponentState';
|
||||
import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
|
||||
import { softFocusPositionComponentState } from '@/object-record/record-table/states/softFocusPositionComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useSetSoftFocusPosition } from './internal/useSetSoftFocusPosition';
|
||||
|
||||
@ -17,6 +17,11 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const tableAllRowIdsState = useRecoilComponentCallbackStateV2(
|
||||
tableAllRowIdsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const moveUp = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
@ -25,50 +30,41 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
softFocusPositionState,
|
||||
);
|
||||
|
||||
let newRowNumber = softFocusPosition.row - 1;
|
||||
let newRowIndex = softFocusPosition.row - 1;
|
||||
|
||||
if (newRowNumber < 0) {
|
||||
newRowNumber = 0;
|
||||
if (newRowIndex < 0) {
|
||||
newRowIndex = 0;
|
||||
}
|
||||
|
||||
setSoftFocusPosition({
|
||||
...softFocusPosition,
|
||||
row: newRowNumber,
|
||||
row: newRowIndex,
|
||||
});
|
||||
},
|
||||
[softFocusPositionState, setSoftFocusPosition],
|
||||
);
|
||||
|
||||
const numberOfTableRowsState = useRecoilComponentCallbackStateV2(
|
||||
numberOfTableRowsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const moveDown = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const allRowIds = getSnapshotValue(snapshot, tableAllRowIdsState);
|
||||
const softFocusPosition = getSnapshotValue(
|
||||
snapshot,
|
||||
softFocusPositionState,
|
||||
);
|
||||
|
||||
const numberOfTableRows = getSnapshotValue(
|
||||
snapshot,
|
||||
numberOfTableRowsState,
|
||||
);
|
||||
let newRowIndex = softFocusPosition.row + 1;
|
||||
|
||||
let newRowNumber = softFocusPosition.row + 1;
|
||||
|
||||
if (newRowNumber >= numberOfTableRows) {
|
||||
newRowNumber = numberOfTableRows - 1;
|
||||
if (newRowIndex >= allRowIds.length) {
|
||||
newRowIndex = allRowIds.length - 1;
|
||||
}
|
||||
|
||||
setSoftFocusPosition({
|
||||
...softFocusPosition,
|
||||
row: newRowNumber,
|
||||
row: newRowIndex,
|
||||
});
|
||||
},
|
||||
[numberOfTableRowsState, setSoftFocusPosition, softFocusPositionState],
|
||||
[tableAllRowIdsState, setSoftFocusPosition, softFocusPositionState],
|
||||
);
|
||||
|
||||
const numberOfTableColumnsSelector = useRecoilComponentCallbackStateV2(
|
||||
@ -79,6 +75,7 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
const moveRight = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const allRowIds = getSnapshotValue(snapshot, tableAllRowIdsState);
|
||||
const softFocusPosition = getSnapshotValue(
|
||||
snapshot,
|
||||
softFocusPositionState,
|
||||
@ -89,24 +86,18 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
numberOfTableColumnsSelector,
|
||||
);
|
||||
|
||||
const numberOfTableRows = getSnapshotValue(
|
||||
snapshot,
|
||||
numberOfTableRowsState,
|
||||
);
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
const currentRowNumber = softFocusPosition.row;
|
||||
const currentColumnIndex = softFocusPosition.column;
|
||||
const currentRowIndex = softFocusPosition.row;
|
||||
|
||||
const isLastRowAndLastColumn =
|
||||
currentColumnNumber === numberOfTableColumns - 1 &&
|
||||
currentRowNumber === numberOfTableRows - 1;
|
||||
currentColumnIndex === numberOfTableColumns - 1 &&
|
||||
currentRowIndex === allRowIds.length - 1;
|
||||
|
||||
const isLastColumnButNotLastRow =
|
||||
currentColumnNumber === numberOfTableColumns - 1 &&
|
||||
currentRowNumber !== numberOfTableRows - 1;
|
||||
currentColumnIndex === numberOfTableColumns - 1 &&
|
||||
currentRowIndex !== allRowIds.length - 1;
|
||||
|
||||
const isNotLastColumn =
|
||||
currentColumnNumber !== numberOfTableColumns - 1;
|
||||
const isNotLastColumn = currentColumnIndex !== numberOfTableColumns - 1;
|
||||
|
||||
if (isLastRowAndLastColumn) {
|
||||
return;
|
||||
@ -114,20 +105,20 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
|
||||
if (isNotLastColumn) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber,
|
||||
column: currentColumnNumber + 1,
|
||||
row: currentRowIndex,
|
||||
column: currentColumnIndex + 1,
|
||||
});
|
||||
} else if (isLastColumnButNotLastRow) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber + 1,
|
||||
row: currentRowIndex + 1,
|
||||
column: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
tableAllRowIdsState,
|
||||
softFocusPositionState,
|
||||
numberOfTableColumnsSelector,
|
||||
numberOfTableRowsState,
|
||||
setSoftFocusPosition,
|
||||
],
|
||||
);
|
||||
@ -145,16 +136,16 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
numberOfTableColumnsSelector,
|
||||
);
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
const currentRowNumber = softFocusPosition.row;
|
||||
const currentColumnIndex = softFocusPosition.column;
|
||||
const currentRowIndex = softFocusPosition.row;
|
||||
|
||||
const isFirstRowAndFirstColumn =
|
||||
currentColumnNumber === 0 && currentRowNumber === 0;
|
||||
currentColumnIndex === 0 && currentRowIndex === 0;
|
||||
|
||||
const isFirstColumnButNotFirstRow =
|
||||
currentColumnNumber === 0 && currentRowNumber > 0;
|
||||
currentColumnIndex === 0 && currentRowIndex > 0;
|
||||
|
||||
const isNotFirstColumn = currentColumnNumber > 0;
|
||||
const isNotFirstColumn = currentColumnIndex > 0;
|
||||
|
||||
if (isFirstRowAndFirstColumn) {
|
||||
return;
|
||||
@ -162,12 +153,12 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
||||
|
||||
if (isNotFirstColumn) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber,
|
||||
column: currentColumnNumber - 1,
|
||||
row: currentRowIndex,
|
||||
column: currentColumnIndex - 1,
|
||||
});
|
||||
} else if (isFirstColumnButNotFirstRow) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber - 1,
|
||||
row: currentRowIndex - 1,
|
||||
column: numberOfTableColumns - 1,
|
||||
});
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useComputeNewRowPosition } from '@/object-record/record-table/hooks/useComputeNewRowPosition';
|
||||
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -22,7 +22,9 @@ export const RecordTableBodyDragDropContext = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const tableRowIds = useRecoilComponentValueV2(tableRowIdsComponentState);
|
||||
const tableAllRowIds = useRecoilComponentValueV2(
|
||||
tableAllRowIdsComponentState,
|
||||
);
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } =
|
||||
useGetCurrentView(recordTableId);
|
||||
@ -41,7 +43,7 @@ export const RecordTableBodyDragDropContext = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const computeResult = computeNewRowPosition(result, tableRowIds);
|
||||
const computeResult = computeNewRowPosition(result, tableAllRowIds);
|
||||
|
||||
if (!isDefined(computeResult)) {
|
||||
return;
|
||||
|
@ -1,20 +1,22 @@
|
||||
import { RecordTableRows } from '@/object-record/record-table/components/RecordTableRows';
|
||||
import { RecordTableNoRecordGroupRows } from '@/object-record/record-table/components/RecordTableNoRecordGroupRows';
|
||||
import { RecordTableBodyDragDropContext } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext';
|
||||
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
|
||||
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
|
||||
import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableBody = () => {
|
||||
const tableRowIds = useRecoilComponentValueV2(tableRowIdsComponentState);
|
||||
export const RecordTableNoRecordGroupBody = () => {
|
||||
const tableAllRowIds = useRecoilComponentValueV2(
|
||||
tableAllRowIdsComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableInitialLoading = useRecoilComponentValueV2(
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
);
|
||||
|
||||
if (isRecordTableInitialLoading && tableRowIds.length === 0) {
|
||||
if (isRecordTableInitialLoading && tableAllRowIds.length === 0) {
|
||||
return <RecordTableBodyLoading />;
|
||||
}
|
||||
|
||||
@ -22,7 +24,7 @@ export const RecordTableBody = () => {
|
||||
<RecordTableBodyDragDropContext>
|
||||
<RecordTableBodyDroppable>
|
||||
<RecordTablePendingRow />
|
||||
<RecordTableRows />
|
||||
<RecordTableNoRecordGroupRows />
|
||||
</RecordTableBodyDroppable>
|
||||
</RecordTableBodyDragDropContext>
|
||||
);
|
@ -7,20 +7,17 @@ import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useL
|
||||
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
|
||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
||||
import { useScrollLeftValue } from '@/ui/utilities/scroll/hooks/useScrollLeftValue';
|
||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
|
||||
|
||||
export const RecordTableBodyEffect = () => {
|
||||
export const RecordTableNoRecordGroupBodyEffect = () => {
|
||||
const { objectNameSingular } = useContext(RecordTableContext);
|
||||
|
||||
const [hasInitializedScroll, setHasInitiazedScroll] = useState(false);
|
||||
const [hasInitializedScroll, setHasInitializedScroll] = useState(false);
|
||||
|
||||
const {
|
||||
fetchMoreRecords,
|
||||
@ -40,51 +37,11 @@ export const RecordTableBodyEffect = () => {
|
||||
tableLastRowVisibleComponentState,
|
||||
);
|
||||
|
||||
const scrollTop = useScrollTopValue('recordTableWithWrappers');
|
||||
|
||||
const setHasRecordTableFetchedAllRecordsComponents =
|
||||
useSetRecoilComponentStateV2(
|
||||
hasRecordTableFetchedAllRecordsComponentStateV2,
|
||||
);
|
||||
|
||||
// TODO: move this outside because it might cause way too many re-renders for other hooks
|
||||
useEffect(() => {
|
||||
if (scrollTop > 0) {
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.add('header-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.remove('header-sticky');
|
||||
}
|
||||
}, [scrollTop]);
|
||||
|
||||
const scrollLeft = useScrollLeftValue('recordTableWithWrappers');
|
||||
|
||||
const setIsRecordTableScrolledLeft = useSetRecoilComponentStateV2(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsRecordTableScrolledLeft(scrollLeft === 0);
|
||||
if (scrollLeft > 0) {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
?.classList.add('first-columns-sticky');
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.add('first-columns-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
?.classList.remove('first-columns-sticky');
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
?.classList.remove('first-columns-sticky');
|
||||
}
|
||||
}, [scrollLeft, setIsRecordTableScrolledLeft]);
|
||||
|
||||
const [lastShowPageRecordId, setLastShowPageRecordId] = useRecoilState(
|
||||
lastShowPageRecordIdState,
|
||||
);
|
||||
@ -106,7 +63,7 @@ export const RecordTableBodyEffect = () => {
|
||||
|
||||
scrollToPosition(positionInPx);
|
||||
|
||||
setHasInitiazedScroll(true);
|
||||
setHasInitializedScroll(true);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@ -120,7 +77,10 @@ export const RecordTableBodyEffect = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setRecordTableData(records, totalCount);
|
||||
setRecordTableData({
|
||||
records,
|
||||
totalCount,
|
||||
});
|
||||
}
|
||||
}, [records, totalCount, setRecordTableData, loading]);
|
||||
|
@ -0,0 +1,79 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
|
||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
|
||||
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
|
||||
|
||||
export const RecordTableRecordGroupBodyEffect = () => {
|
||||
const { objectNameSingular } = useContext(RecordTableContext);
|
||||
|
||||
const recordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
const [hasInitializedScroll, setHasInitializedScroll] = useState(false);
|
||||
|
||||
const { records, totalCount, setRecordTableData, loading, hasNextPage } =
|
||||
useLoadRecordIndexTable(objectNameSingular);
|
||||
|
||||
const setHasRecordTableFetchedAllRecordsComponents =
|
||||
useSetRecoilComponentStateV2(
|
||||
hasRecordTableFetchedAllRecordsComponentStateV2,
|
||||
);
|
||||
|
||||
const [lastShowPageRecordId, setLastShowPageRecordId] = useRecoilState(
|
||||
lastShowPageRecordIdState,
|
||||
);
|
||||
|
||||
const { scrollToPosition } = useScrollToPosition();
|
||||
|
||||
useEffect(() => {
|
||||
if (isNonEmptyString(lastShowPageRecordId) && !hasInitializedScroll) {
|
||||
const isRecordAlreadyFetched = records.some(
|
||||
(record) => record.id === lastShowPageRecordId,
|
||||
);
|
||||
|
||||
if (isRecordAlreadyFetched) {
|
||||
const recordPosition = records.findIndex(
|
||||
(record) => record.id === lastShowPageRecordId,
|
||||
);
|
||||
|
||||
const positionInPx = recordPosition * ROW_HEIGHT;
|
||||
|
||||
scrollToPosition(positionInPx);
|
||||
|
||||
setHasInitializedScroll(true);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
lastShowPageRecordId,
|
||||
records,
|
||||
scrollToPosition,
|
||||
hasInitializedScroll,
|
||||
setLastShowPageRecordId,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setRecordTableData({
|
||||
records,
|
||||
recordGroupId,
|
||||
totalCount,
|
||||
});
|
||||
}
|
||||
}, [records, totalCount, setRecordTableData, loading, recordGroupId]);
|
||||
|
||||
useEffect(() => {
|
||||
const allRecordsHaveBeenFetched = !hasNextPage;
|
||||
|
||||
setHasRecordTableFetchedAllRecordsComponents(allRecordsHaveBeenFetched);
|
||||
}, [hasNextPage, setHasRecordTableFetchedAllRecordsComponents]);
|
||||
|
||||
return <></>;
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { recordGroupDefinitionsComponentState } from '@/object-record/record-group/states/recordGroupDefinitionsComponentState';
|
||||
import { RecordTableRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableRecordGroupBodyEffects = () => {
|
||||
const recordGroupDefinitions = useRecoilComponentValueV2(
|
||||
recordGroupDefinitionsComponentState,
|
||||
);
|
||||
|
||||
return recordGroupDefinitions.map((recordGroupDefinition) => (
|
||||
<RecordGroupContext.Provider
|
||||
value={{ recordGroupId: recordGroupDefinition.id }}
|
||||
>
|
||||
<RecordTableRecordGroupBodyEffect />
|
||||
</RecordGroupContext.Provider>
|
||||
));
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
import { useRecordGroups } from '@/object-record/record-group/hooks/useRecordGroups';
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { RecordTableRecordGroupRows } from '@/object-record/record-table/components/RecordTableRecordGroupRows';
|
||||
import { RecordTableBodyDragDropContext } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext';
|
||||
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
|
||||
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
|
||||
import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
type RecordTableRecordGroupsBodyProps = {
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
export const RecordTableRecordGroupsBody = ({
|
||||
objectNameSingular,
|
||||
}: RecordTableRecordGroupsBodyProps) => {
|
||||
const tableAllRowIds = useRecoilComponentValueV2(
|
||||
tableAllRowIdsComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableInitialLoading = useRecoilComponentValueV2(
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
);
|
||||
|
||||
const { visibleRecordGroups } = useRecordGroups({ objectNameSingular });
|
||||
|
||||
if (isRecordTableInitialLoading && tableAllRowIds.length === 0) {
|
||||
return <RecordTableBodyLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordTableBodyDragDropContext>
|
||||
<RecordTableBodyDroppable>
|
||||
<RecordTablePendingRow />
|
||||
{visibleRecordGroups.map((recordGroupDefinition) => (
|
||||
<RecordGroupContext.Provider
|
||||
value={{ recordGroupId: recordGroupDefinition.id }}
|
||||
>
|
||||
<RecordTableRecordGroupRows />
|
||||
</RecordGroupContext.Provider>
|
||||
))}
|
||||
</RecordTableBodyDroppable>
|
||||
</RecordTableBodyDragDropContext>
|
||||
);
|
||||
};
|
@ -78,9 +78,9 @@ const StyledTableHead = styled.thead`
|
||||
`;
|
||||
|
||||
export const RecordTableHeader = ({
|
||||
objectMetadataNameSingular,
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectMetadataNameSingular: string;
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
const visibleTableColumns = useRecoilComponentValueV2(
|
||||
visibleTableColumnsComponentSelector,
|
||||
@ -95,7 +95,7 @@ export const RecordTableHeader = ({
|
||||
<RecordTableHeaderCell
|
||||
key={column.fieldMetadataId}
|
||||
column={column}
|
||||
objectMetadataNameSingular={objectMetadataNameSingular}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
))}
|
||||
<RecordTableHeaderLastColumn />
|
||||
|
@ -95,13 +95,13 @@ const StyledHeaderIcon = styled.div`
|
||||
|
||||
export const RecordTableHeaderCell = ({
|
||||
column,
|
||||
objectMetadataNameSingular,
|
||||
objectNameSingular,
|
||||
}: {
|
||||
column: ColumnDefinition<FieldMetadata>;
|
||||
objectMetadataNameSingular: string;
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const resizeFieldOffsetState = useRecoilComponentCallbackStateV2(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
import { AllRowsSelectedStatus } from '../../types/AllRowSelectedStatus';
|
||||
|
||||
@ -13,11 +13,15 @@ export const allRowsSelectedStatusComponentSelector =
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const tableRowIds = get(
|
||||
tableRowIdsComponentState.atomFamily({ instanceId }),
|
||||
tableAllRowIdsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const selectedRowIds = get(
|
||||
selectedRowIdsComponentSelector.selectorFamily({ instanceId }),
|
||||
selectedRowIdsComponentSelector.selectorFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const numberOfSelectedRows = selectedRowIds.length;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
|
||||
export const selectedRowIdsComponentSelector = createComponentSelectorV2<
|
||||
@ -11,7 +11,11 @@ export const selectedRowIdsComponentSelector = createComponentSelectorV2<
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const rowIds = get(tableRowIdsComponentState.atomFamily({ instanceId }));
|
||||
const rowIds = get(
|
||||
tableAllRowIdsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
return rowIds.filter(
|
||||
(rowId) =>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||
import { tableAllRowIdsComponentState } from '@/object-record/record-table/states/tableAllRowIdsComponentState';
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
|
||||
export const unselectedRowIdsComponentSelector = createComponentSelectorV2<
|
||||
@ -11,7 +11,11 @@ export const unselectedRowIdsComponentSelector = createComponentSelectorV2<
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const rowIds = get(tableRowIdsComponentState.atomFamily({ instanceId }));
|
||||
const rowIds = get(
|
||||
tableAllRowIdsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
return rowIds.filter(
|
||||
(rowId) =>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const tableRowIdsComponentState = createComponentStateV2<string[]>({
|
||||
key: 'tableRowIdsComponentState',
|
||||
export const tableAllRowIdsComponentState = createComponentStateV2<string[]>({
|
||||
key: 'tableAllRowIdsComponentState',
|
||||
defaultValue: [],
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const tableRecordGroupIdsComponentState = createComponentStateV2<
|
||||
RecordGroupDefinition['id'][]
|
||||
>({
|
||||
key: 'tableRecordGroupIdsComponentState',
|
||||
defaultValue: [],
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
@ -0,0 +1,10 @@
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||
|
||||
export const tableRowIdsByGroupComponentFamilyState =
|
||||
createComponentFamilyStateV2<string[], RecordGroupDefinition['id']>({
|
||||
key: 'tableRowIdsByGroupComponentFamilyState',
|
||||
defaultValue: [],
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-redeclare */
|
||||
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
||||
import { selectorFamily, SerializableParam } from 'recoil';
|
||||
|
||||
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
|
||||
@ -9,7 +11,26 @@ import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
|
||||
import { SelectorSetter } from '@/ui/utilities/state/types/SelectorSetter';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const createComponentFamilySelectorV2 = <
|
||||
export function createComponentFamilySelectorV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(options: {
|
||||
key: string;
|
||||
get: SelectorGetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
}): ComponentFamilySelectorV2<ValueType, FamilyKey>;
|
||||
|
||||
export function createComponentFamilySelectorV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(options: {
|
||||
key: string;
|
||||
get: SelectorGetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
|
||||
set: SelectorSetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
}): ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>;
|
||||
|
||||
export function createComponentFamilySelectorV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>({
|
||||
@ -24,7 +45,7 @@ export const createComponentFamilySelectorV2 = <
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
}):
|
||||
| ComponentFamilySelectorV2<ValueType, FamilyKey>
|
||||
| ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey> => {
|
||||
| ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey> {
|
||||
if (isDefined(componentInstanceContext)) {
|
||||
globalComponentInstanceContextMap.set(key, componentInstanceContext);
|
||||
}
|
||||
@ -55,4 +76,4 @@ export const createComponentFamilySelectorV2 = <
|
||||
}),
|
||||
} satisfies ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -6,11 +6,18 @@ import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
|
||||
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
type CreateComponentFamilyStateArgs<ValueType> = {
|
||||
type CreateComponentFamilyStateArgs<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
> = {
|
||||
key: string;
|
||||
defaultValue: ValueType;
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
effects?: AtomEffect<ValueType>[];
|
||||
effects?:
|
||||
| AtomEffect<ValueType>[]
|
||||
| ((
|
||||
param: ComponentFamilyStateKeyV2<FamilyKey>,
|
||||
) => ReadonlyArray<AtomEffect<ValueType>>);
|
||||
};
|
||||
|
||||
export const createComponentFamilyStateV2 = <
|
||||
@ -21,10 +28,10 @@ export const createComponentFamilyStateV2 = <
|
||||
effects,
|
||||
defaultValue,
|
||||
componentInstanceContext,
|
||||
}: CreateComponentFamilyStateArgs<ValueType>): ComponentFamilyStateV2<
|
||||
}: CreateComponentFamilyStateArgs<
|
||||
ValueType,
|
||||
FamilyKey
|
||||
> => {
|
||||
>): ComponentFamilyStateV2<ValueType, FamilyKey> => {
|
||||
if (isDefined(componentInstanceContext)) {
|
||||
globalComponentInstanceContextMap.set(key, componentInstanceContext);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user