diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx
index 58828525ea..7008bec924 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx
+++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx
@@ -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 (
-
- {children}
-
+
+
+ {children}
+
+
+
);
diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useCurrentRecordGroupDefinition.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useCurrentRecordGroupDefinition.ts
new file mode 100644
index 0000000000..78afc40337
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useCurrentRecordGroupDefinition.ts
@@ -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;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useCurrentRecordGroupId.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useCurrentRecordGroupId.ts
new file mode 100644
index 0000000000..b9960a19c6
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useCurrentRecordGroupId.ts
@@ -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;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupReorder.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupReorder.ts
index 97151a5838..b0c738a0fb 100644
--- a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupReorder.ts
+++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupReorder.ts
@@ -23,7 +23,7 @@ export const useRecordGroupReorder = ({
);
const { visibleRecordGroups } = useRecordGroups({
- objectNameSingular,
+ objectNameSingular: objectNameSingular,
});
const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);
diff --git a/packages/twenty-front/src/modules/object-record/record-group/states/context/RecordGroupContext.ts b/packages/twenty-front/src/modules/object-record/record-group/states/context/RecordGroupContext.ts
new file mode 100644
index 0000000000..402d8743de
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-group/states/context/RecordGroupContext.ts
@@ -0,0 +1,9 @@
+import { createContext } from 'react';
+
+export type RecordGroupContextProps = {
+ recordGroupId: string;
+};
+
+export const RecordGroupContext = createContext(
+ {} as RecordGroupContextProps,
+);
diff --git a/packages/twenty-front/src/modules/object-record/record-group/states/hasRecordGroupDefinitionsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-group/states/hasRecordGroupDefinitionsComponentSelector.ts
new file mode 100644
index 0000000000..bccab902cc
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-group/states/hasRecordGroupDefinitionsComponentSelector.ts
@@ -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({
+ key: 'hasRecordGroupDefinitionsComponentSelector',
+ componentInstanceContext: ViewComponentInstanceContext,
+ get:
+ ({ instanceId }) =>
+ ({ get }) => {
+ const recordGroupDefinitions = get(
+ recordGroupDefinitionsComponentState.atomFamily({
+ instanceId,
+ }),
+ );
+
+ return recordGroupDefinitions.length > 0;
+ },
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts
index a2b0e03480..22f2043cf4 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts
@@ -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) => {
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx
index 485f4b0498..ac2fd976d2 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx
@@ -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(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}
>
-
+ {!hasRecordGroups ? (
+
+ ) : (
+
+ )}
-
-
+
+ {!hasRecordGroups ? (
+
+ ) : (
+
+ )}
+
{
- const tableRowIds = useRecoilComponentValueV2(tableRowIdsComponentState);
+export const RecordTableNoRecordGroupRows = () => {
+ const rowIds = useRecoilComponentValueV2(tableAllRowIdsComponentState);
return (
<>
- {tableRowIds.map((recordId, rowIndex) => {
+ {rowIds.map((recordId, rowIndex) => {
return (
{
+ 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 (
+
+ );
+ });
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx
new file mode 100644
index 0000000000..5ed2670813
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx
@@ -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 <>>;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx
new file mode 100644
index 0000000000..7b28090002
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx
@@ -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 ;
+ }
+
+ return children;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts
index 8780f623ab..82b51a2e16 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts
@@ -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,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSelectAllRows.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSelectAllRows.ts
index 38b8e75c21..24d54fb1ba 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSelectAllRows.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSelectAllRows.ts
@@ -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 {
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts
index 4b23ac749b..7a35ef87a6 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts
@@ -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 }) =>
- (newRecords: T[], totalCount?: number) => {
- for (const record of newRecords) {
+ ({
+ 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,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts
index c6ef772521..7694d5f757 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts
@@ -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,
});
}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx
index 64b33fb374..0bcc4ac78c 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx
@@ -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;
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx
similarity index 69%
rename from packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx
index 8084985a2d..f2a765d6d4 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx
@@ -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 ;
}
@@ -22,7 +24,7 @@ export const RecordTableBody = () => {
-
+
);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx
similarity index 67%
rename from packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEffect.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx
index f66b6643d3..76a29fccab 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect.tsx
@@ -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]);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx
new file mode 100644
index 0000000000..831f1dc4fd
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffect.tsx
@@ -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 <>>;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects.tsx
new file mode 100644
index 0000000000..c3579d2f20
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects.tsx
@@ -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) => (
+
+
+
+ ));
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx
new file mode 100644
index 0000000000..7f807346b5
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx
@@ -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 ;
+ }
+
+ return (
+
+
+
+ {visibleRecordGroups.map((recordGroupDefinition) => (
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
index e119469bf5..9c28df97a9 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
@@ -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 = ({
))}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx
index 0693791994..9b8ea55cca 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx
@@ -95,13 +95,13 @@ const StyledHeaderIcon = styled.div`
export const RecordTableHeaderCell = ({
column,
- objectMetadataNameSingular,
+ objectNameSingular,
}: {
column: ColumnDefinition;
- objectMetadataNameSingular: string;
+ objectNameSingular: string;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular: objectMetadataNameSingular,
+ objectNameSingular,
});
const resizeFieldOffsetState = useRecoilComponentCallbackStateV2(
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector.ts
index 77b3d91f0f..470b8d9dcb 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector.ts
@@ -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;
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/selectedRowIdsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/selectedRowIdsComponentSelector.ts
index 9f99a82cd2..ced5cb600f 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/selectedRowIdsComponentSelector.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/selectedRowIdsComponentSelector.ts
@@ -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) =>
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector.ts
index 3f1a8c9973..1579e41b06 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector.ts
@@ -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) =>
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/tableRowIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/tableAllRowIdsComponentState.ts
similarity index 73%
rename from packages/twenty-front/src/modules/object-record/record-table/states/tableRowIdsComponentState.ts
rename to packages/twenty-front/src/modules/object-record/record-table/states/tableAllRowIdsComponentState.ts
index c919897915..e6f8ef4b24 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/states/tableRowIdsComponentState.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/tableAllRowIdsComponentState.ts
@@ -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({
- key: 'tableRowIdsComponentState',
+export const tableAllRowIdsComponentState = createComponentStateV2({
+ key: 'tableAllRowIdsComponentState',
defaultValue: [],
componentInstanceContext: RecordTableComponentInstanceContext,
});
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/tableRecordGroupIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/tableRecordGroupIdsComponentState.ts
new file mode 100644
index 0000000000..fd6f666a94
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/tableRecordGroupIdsComponentState.ts
@@ -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,
+});
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/tableRowIdsByGroupComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/tableRowIdsByGroupComponentFamilyState.ts
new file mode 100644
index 0000000000..395f7f185b
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/tableRowIdsByGroupComponentFamilyState.ts
@@ -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({
+ key: 'tableRowIdsByGroupComponentFamilyState',
+ defaultValue: [],
+ componentInstanceContext: RecordTableComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2.ts
index 04c9a2d2a8..8662ca1e8c 100644
--- a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2.ts
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2.ts
@@ -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>;
+ componentInstanceContext: ComponentInstanceStateContext | null;
+}): ComponentFamilySelectorV2;
+
+export function createComponentFamilySelectorV2<
+ ValueType,
+ FamilyKey extends SerializableParam,
+>(options: {
+ key: string;
+ get: SelectorGetter>;
+ set: SelectorSetter>;
+ componentInstanceContext: ComponentInstanceStateContext | null;
+}): ComponentFamilyReadOnlySelectorV2;
+
+export function createComponentFamilySelectorV2<
ValueType,
FamilyKey extends SerializableParam,
>({
@@ -24,7 +45,7 @@ export const createComponentFamilySelectorV2 = <
componentInstanceContext: ComponentInstanceStateContext | null;
}):
| ComponentFamilySelectorV2
- | ComponentFamilyReadOnlySelectorV2 => {
+ | ComponentFamilyReadOnlySelectorV2 {
if (isDefined(componentInstanceContext)) {
globalComponentInstanceContextMap.set(key, componentInstanceContext);
}
@@ -55,4 +76,4 @@ export const createComponentFamilySelectorV2 = <
}),
} satisfies ComponentFamilyReadOnlySelectorV2;
}
-};
+}
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilyStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilyStateV2.ts
index 2d1f42e61f..f314c2c4a6 100644
--- a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilyStateV2.ts
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentFamilyStateV2.ts
@@ -6,11 +6,18 @@ import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
import { isDefined } from 'twenty-ui';
-type CreateComponentFamilyStateArgs = {
+type CreateComponentFamilyStateArgs<
+ ValueType,
+ FamilyKey extends SerializableParam,
+> = {
key: string;
defaultValue: ValueType;
componentInstanceContext: ComponentInstanceStateContext | null;
- effects?: AtomEffect[];
+ effects?:
+ | AtomEffect[]
+ | ((
+ param: ComponentFamilyStateKeyV2,
+ ) => ReadonlyArray>);
};
export const createComponentFamilyStateV2 = <
@@ -21,10 +28,10 @@ export const createComponentFamilyStateV2 = <
effects,
defaultValue,
componentInstanceContext,
-}: CreateComponentFamilyStateArgs): ComponentFamilyStateV2<
+}: CreateComponentFamilyStateArgs<
ValueType,
FamilyKey
-> => {
+>): ComponentFamilyStateV2 => {
if (isDefined(componentInstanceContext)) {
globalComponentInstanceContextMap.set(key, componentInstanceContext);
}