Show Header in RecordTable on empty state and show groups in Group By views (#11416)

Closes #11298

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Harshit Rai Verma
2025-04-08 01:35:59 +05:30
committed by GitHub
parent 07b25a2aad
commit 6bc18960c9
6 changed files with 152 additions and 56 deletions

View File

@ -1,36 +1,17 @@
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useRef } from 'react';
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableStickyBottomEffect } from '@/object-record/record-table/components/RecordTableStickyBottomEffect';
import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect';
import { RecordTableBodyEffectsWrapper } from '@/object-record/record-table/components/RecordTableBodyEffectsWrapper';
import { RecordTableContent } from '@/object-record/record-table/components/RecordTableContent';
import { RecordTableEmpty } from '@/object-record/record-table/components/RecordTableEmpty';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
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 { 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';
import { useRef } from 'react';
const StyledTable = styled.table`
border-radius: ${({ theme }) => theme.border.radius.sm};
border-spacing: 0;
table-layout: fixed;
width: 100%;
.footer-sticky tr:nth-last-of-type(2) td {
border-bottom-color: ${({ theme }) => theme.background.transparent};
}
`;
export const RecordTable = () => {
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
@ -56,51 +37,46 @@ export const RecordTable = () => {
recordTableId,
);
const recordTableIsEmpty =
!isRecordTableInitialLoading && allRecordIds.length === 0;
const { resetTableRowSelection, setRowSelected } = useRecordTable({
recordTableId,
});
const recordTableIsEmpty =
!isRecordTableInitialLoading && allRecordIds.length === 0;
if (!isNonEmptyString(objectNameSingular)) {
return <></>;
}
const handleDragSelectionStart = () => {
resetTableRowSelection();
toggleClickOutsideListener(false);
};
const handleDragSelectionEnd = () => {
toggleClickOutsideListener(true);
};
return (
<>
{!hasRecordGroups ? (
<RecordTableNoRecordGroupBodyEffect />
) : (
<RecordTableRecordGroupBodyEffects />
)}
<RecordTableBodyUnselectEffect tableBodyRef={tableBodyRef} />
<RecordTableBodyEffectsWrapper
hasRecordGroups={hasRecordGroups}
tableBodyRef={tableBodyRef}
/>
{recordTableIsEmpty ? (
<RecordTableEmptyState />
<RecordTableEmpty
tableBodyRef={tableBodyRef}
hasRecordGroups={hasRecordGroups}
/>
) : (
<>
<StyledTable ref={tableBodyRef}>
<RecordTableHeader />
{!hasRecordGroups ? (
<RecordTableNoRecordGroupBody />
) : (
<RecordTableRecordGroupsBody />
)}
<RecordTableStickyEffect />
<RecordTableStickyBottomEffect />
</StyledTable>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={() => {
resetTableRowSelection();
toggleClickOutsideListener(false);
}}
onDragSelectionChange={setRowSelected}
onDragSelectionEnd={() => {
toggleClickOutsideListener(true);
}}
/>
</>
<RecordTableContent
tableBodyRef={tableBodyRef}
handleDragSelectionStart={handleDragSelectionStart}
handleDragSelectionEnd={handleDragSelectionEnd}
setRowSelected={setRowSelected}
hasRecordGroups={hasRecordGroups}
/>
)}
</>
);

View File

@ -0,0 +1,22 @@
import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect';
import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect';
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
export interface RecordTableBodyEffectsWrapperProps {
hasRecordGroups: boolean;
tableBodyRef: React.RefObject<HTMLTableElement>;
}
export const RecordTableBodyEffectsWrapper = ({
hasRecordGroups,
tableBodyRef,
}: RecordTableBodyEffectsWrapperProps) => (
<>
{hasRecordGroups ? (
<RecordTableRecordGroupBodyEffects />
) : (
<RecordTableNoRecordGroupBodyEffect />
)}
<RecordTableBodyUnselectEffect tableBodyRef={tableBodyRef} />
</>
);

View File

@ -0,0 +1,42 @@
import { RecordTableStickyBottomEffect } from '@/object-record/record-table/components/RecordTableStickyBottomEffect';
import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect';
import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles';
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
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 { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
export interface RecordTableContentProps {
tableBodyRef: React.RefObject<HTMLTableElement>;
handleDragSelectionStart: () => void;
handleDragSelectionEnd: () => void;
setRowSelected: (rowId: string, selected: boolean) => void;
hasRecordGroups: boolean;
}
export const RecordTableContent = ({
tableBodyRef,
handleDragSelectionStart,
handleDragSelectionEnd,
setRowSelected,
hasRecordGroups,
}: RecordTableContentProps) => (
<>
<StyledTable ref={tableBodyRef}>
<RecordTableHeader />
{hasRecordGroups ? (
<RecordTableRecordGroupsBody />
) : (
<RecordTableNoRecordGroupBody />
)}
<RecordTableStickyEffect />
<RecordTableStickyBottomEffect />
</StyledTable>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={handleDragSelectionStart}
onDragSelectionChange={setRowSelected}
onDragSelectionEnd={handleDragSelectionEnd}
/>
</>
);

View File

@ -0,0 +1,25 @@
import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles';
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
export interface RecordTableEmptyProps {
tableBodyRef: React.RefObject<HTMLTableElement>;
hasRecordGroups: boolean;
}
export const RecordTableEmpty = ({
tableBodyRef,
hasRecordGroups,
}: RecordTableEmptyProps) => (
<>
<StyledTable ref={tableBodyRef}>
<RecordTableHeader />
</StyledTable>
{hasRecordGroups ? (
<RecordTableRecordGroupsBody />
) : (
<RecordTableEmptyState />
)}
</>
);

View File

@ -0,0 +1,12 @@
import styled from '@emotion/styled';
export const StyledTable = styled.table`
border-radius: ${({ theme }) => theme.border.radius.sm};
border-spacing: 0;
table-layout: fixed;
width: 100%;
.footer-sticky tr:nth-last-of-type(2) td {
border-bottom-color: ${({ theme }) => theme.background.transparent};
}
`;

View File

@ -1,6 +1,9 @@
import styled from '@emotion/styled';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Checkbox } from 'twenty-ui/input';
@ -30,6 +33,21 @@ export const RecordTableHeaderCheckboxColumn = () => {
allRowsSelectedStatus === 'all' || allRowsSelectedStatus === 'some';
const indeterminate = allRowsSelectedStatus === 'some';
const { recordTableId } = useRecordTableContextOrThrow();
const isRecordTableInitialLoading = useRecoilComponentValueV2(
isRecordTableInitialLoadingComponentState,
recordTableId,
);
const allRecordIds = useRecoilComponentValueV2(
recordIndexAllRecordIdsComponentSelector,
recordTableId,
);
const recordTableIsEmpty =
!isRecordTableInitialLoading && allRecordIds.length === 0;
const onChange = () => {
if (checked) {
setHasUserSelectedAllRows(false);
@ -48,6 +66,7 @@ export const RecordTableHeaderCheckboxColumn = () => {
checked={checked}
onChange={onChange}
indeterminate={indeterminate}
disabled={recordTableIsEmpty}
/>
</StyledContainer>
</StyledColumnHeaderCell>