Refacto scroll + Aggregate queries for view groups (#9089)
Closes https://github.com/twentyhq/private-issues/issues/217. Refactoring scroll not to cause table-wide re-render when opening a dropdown (triggering a scroll lock) in the table.
This commit is contained in:
@ -194,7 +194,10 @@ export const RecordBoard = () => {
|
||||
<RecordBoardComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordBoardId }}
|
||||
>
|
||||
<ScrollWrapper contextProviderName="recordBoard">
|
||||
<ScrollWrapper
|
||||
contextProviderName="recordBoard"
|
||||
componentInstanceId={`scroll-wrapper-record-board-${recordBoardId}`}
|
||||
>
|
||||
<RecordBoardStickyHeaderEffect />
|
||||
<StyledContainerContainer>
|
||||
<RecordBoardHeader />
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
||||
|
||||
export const RecordBoardStickyHeaderEffect = () => {
|
||||
const scrollTop = useScrollTopValue('recordBoard');
|
||||
const scrollTop = useRecoilComponentValueV2(
|
||||
scrollWrapperScrollTopComponentState,
|
||||
);
|
||||
|
||||
// TODO: move this outside because it might cause way too many re-renders for other hooks
|
||||
useEffect(() => {
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useRecordGroupFilter = (fields: FieldMetadataItem[]) => {
|
||||
const currentRecordGroupDefinition = useCurrentRecordGroupDefinition();
|
||||
|
||||
const recordGroupFilter = useMemo(() => {
|
||||
if (isDefined(currentRecordGroupDefinition)) {
|
||||
const fieldMetadataItem = fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === currentRecordGroupDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(
|
||||
`Field metadata item with id ${currentRecordGroupDefinition.fieldMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(currentRecordGroupDefinition.value)) {
|
||||
return { [fieldMetadataItem.name]: { is: 'NULL' } };
|
||||
}
|
||||
|
||||
return {
|
||||
[fieldMetadataItem.name]: {
|
||||
eq: currentRecordGroupDefinition.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}, [currentRecordGroupDefinition, fields]);
|
||||
|
||||
return { recordGroupFilter };
|
||||
};
|
||||
@ -2,12 +2,11 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
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 { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
|
||||
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
|
||||
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const useFindManyRecordIndexTableParams = (
|
||||
objectNameSingular: string,
|
||||
@ -17,6 +16,10 @@ export const useFindManyRecordIndexTableParams = (
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { recordGroupFilter } = useRecordGroupFilter(
|
||||
objectMetadataItem?.fields,
|
||||
);
|
||||
|
||||
const currentRecordGroupDefinition = useCurrentRecordGroupDefinition();
|
||||
|
||||
const tableViewFilterGroups = useRecoilComponentValueV2(
|
||||
@ -38,33 +41,6 @@ export const useFindManyRecordIndexTableParams = (
|
||||
tableViewFilterGroups,
|
||||
);
|
||||
|
||||
const recordGroupFilter = useMemo(() => {
|
||||
if (isDefined(currentRecordGroupDefinition)) {
|
||||
const fieldMetadataItem = objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === currentRecordGroupDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItem) {
|
||||
throw new Error(
|
||||
`Field metadata item with id ${currentRecordGroupDefinition.fieldMetadataId} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(currentRecordGroupDefinition.value)) {
|
||||
return { [fieldMetadataItem.name]: { is: 'NULL' } };
|
||||
}
|
||||
|
||||
return {
|
||||
[fieldMetadataItem.name]: {
|
||||
eq: currentRecordGroupDefinition.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}, [objectMetadataItem.fields, currentRecordGroupDefinition]);
|
||||
|
||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, tableSorts);
|
||||
|
||||
return {
|
||||
|
||||
@ -96,7 +96,9 @@ export const RecordTable = () => {
|
||||
<RecordTableRecordGroupsBody />
|
||||
)}
|
||||
<RecordTableStickyEffect />
|
||||
{isAggregateQueryEnabled && <RecordTableFooter />}
|
||||
{isAggregateQueryEnabled && !hasRecordGroups && (
|
||||
<RecordTableFooter />
|
||||
)}
|
||||
</StyledTable>
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RecordTableFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableFooter';
|
||||
import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow';
|
||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
||||
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
|
||||
@ -8,10 +9,14 @@ import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-ta
|
||||
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const RecordTableRecordGroupRows = () => {
|
||||
const isAggregateQueryEnabled = useIsFeatureEnabled(
|
||||
'IS_AGGREGATE_QUERY_ENABLED',
|
||||
);
|
||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
const allRecordIds = useRecoilComponentValueV2(
|
||||
@ -57,8 +62,14 @@ export const RecordTableRecordGroupRows = () => {
|
||||
);
|
||||
})}
|
||||
<RecordTablePendingRecordGroupRow />
|
||||
<RecordTableRecordGroupSectionLoadMore />
|
||||
<RecordTableRecordGroupSectionAddNew />
|
||||
{isAggregateQueryEnabled && (
|
||||
<RecordTableFooter
|
||||
key={currentRecordGroupId}
|
||||
currentRecordGroupId={currentRecordGroupId}
|
||||
/>
|
||||
)}
|
||||
<RecordTableRecordGroupSectionLoadMore />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
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 { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
export const RecordTableStickyEffect = () => {
|
||||
const scrollTop = useScrollTopValue('recordTableWithWrappers');
|
||||
const scrollTop = useRecoilComponentValueV2(
|
||||
scrollWrapperScrollTopComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollTop > 0) {
|
||||
@ -20,7 +23,9 @@ export const RecordTableStickyEffect = () => {
|
||||
}
|
||||
}, [scrollTop]);
|
||||
|
||||
const scrollLeft = useScrollLeftValue('recordTableWithWrappers');
|
||||
const scrollLeft = useRecoilComponentValueV2(
|
||||
scrollWrapperScrollLeftComponentState,
|
||||
);
|
||||
|
||||
const setIsRecordTableScrolledLeft = useSetRecoilComponentStateV2(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
|
||||
@ -10,8 +10,6 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||
|
||||
import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { useRecordTable } from '../hooks/useRecordTable';
|
||||
|
||||
@ -50,11 +48,6 @@ export const RecordTableWithWrappers = ({
|
||||
recordTableId,
|
||||
viewBarId,
|
||||
}: RecordTableWithWrappersProps) => {
|
||||
const isScrollEnabledForRecordTable = useRecoilComponentValueV2(
|
||||
isScrollEnabledForRecordTableState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { resetTableRowSelection, selectAllRows, setHasUserSelectedAllRows } =
|
||||
useRecordTable({
|
||||
recordTableId,
|
||||
@ -109,9 +102,8 @@ export const RecordTableWithWrappers = ({
|
||||
>
|
||||
<EntityDeleteContext.Provider value={deleteOneRecord}>
|
||||
<ScrollWrapper
|
||||
enableXScroll={isScrollEnabledForRecordTable.enableXScroll}
|
||||
enableYScroll={isScrollEnabledForRecordTable.enableYScroll}
|
||||
contextProviderName="recordTableWithWrappers"
|
||||
componentInstanceId={`record-table-scroll-${recordTableId}`}
|
||||
>
|
||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||
<StyledTableWithHeader>
|
||||
|
||||
@ -53,6 +53,11 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const tableColumnsState = useRecoilComponentCallbackStateV2(
|
||||
tableColumnsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const setAvailableTableColumns = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||
@ -69,6 +74,19 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
||||
[availableTableColumnsState],
|
||||
);
|
||||
|
||||
const setTableColumns = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||
const tableColumns = getSnapshotValue(snapshot, tableColumnsState);
|
||||
|
||||
if (isDeeplyEqual(tableColumns, columns)) {
|
||||
return;
|
||||
}
|
||||
set(tableColumnsState, columns);
|
||||
},
|
||||
[tableColumnsState],
|
||||
);
|
||||
|
||||
const setOnEntityCountChange = useSetRecoilComponentStateV2(
|
||||
onEntityCountChangeComponentState,
|
||||
recordTableId,
|
||||
@ -89,11 +107,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const setTableColumns = useSetRecoilComponentStateV2(
|
||||
tableColumnsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const setOnColumnsChange = useSetRecoilComponentStateV2(
|
||||
onColumnsChangeComponentState,
|
||||
recordTableId,
|
||||
|
||||
@ -6,13 +6,15 @@ import { RecordTableTd } from '@/object-record/record-table/record-table-cell/co
|
||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
export const TABLE_CELL_CHECKBOX_MIN_WIDTH = '24px';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
min-width: ${TABLE_CELL_CHECKBOX_MIN_WIDTH};
|
||||
`;
|
||||
|
||||
export const RecordTableCellCheckbox = () => {
|
||||
|
||||
@ -6,12 +6,14 @@ import { RecordTableTd } from '@/object-record/record-table/record-table-cell/co
|
||||
import { css } from '@emotion/react';
|
||||
import { IconListViewGrip } from 'twenty-ui';
|
||||
|
||||
export const TABLE_CELL_GRIP_WIDTH = '16px';
|
||||
|
||||
const StyledContainer = styled.div<{ isPendingRow?: boolean }>`
|
||||
height: 32px;
|
||||
width: ${TABLE_CELL_GRIP_WIDTH};
|
||||
border-color: transparent;
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
width: 16px;
|
||||
${({ isPendingRow }) =>
|
||||
!isPendingRow
|
||||
? css`
|
||||
|
||||
@ -50,8 +50,10 @@ export const RecordTableColumnFooterAggregateValue = ({
|
||||
dropdownId,
|
||||
aggregateValue,
|
||||
aggregateLabel,
|
||||
isFirstCell,
|
||||
}: {
|
||||
dropdownId: string;
|
||||
isFirstCell: boolean;
|
||||
aggregateValue?: string | number | null;
|
||||
aggregateLabel?: string;
|
||||
}) => {
|
||||
@ -66,13 +68,13 @@ export const RecordTableColumnFooterAggregateValue = ({
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<StyledCell>
|
||||
{isHovered || isDefined(aggregateValue) ? (
|
||||
{isHovered || isDefined(aggregateValue) || isFirstCell ? (
|
||||
<>
|
||||
<StyledText id={sanitizedId}>
|
||||
{aggregateValue ?? 'Calculate'}
|
||||
</StyledText>
|
||||
<StyledIcon fontWeight={'light'} size={theme.icon.size.sm} />
|
||||
{aggregateValue && isDefined(aggregateLabel) && (
|
||||
{isDefined(aggregateValue) && isDefined(aggregateLabel) && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#${sanitizedId}`}
|
||||
content={aggregateLabel}
|
||||
|
||||
@ -16,10 +16,12 @@ import { MenuItem } from 'twenty-ui';
|
||||
|
||||
export const RecordTableColumnFooterDropdown = ({
|
||||
column,
|
||||
dropdownId,
|
||||
}: {
|
||||
column: ColumnDefinition<FieldMetadata>;
|
||||
dropdownId: string;
|
||||
}) => {
|
||||
const { closeDropdown } = useDropdown(column.fieldMetadataId + '-footer');
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
@ -67,6 +69,7 @@ export const RecordTableColumnFooterDropdown = ({
|
||||
key={aggregation}
|
||||
onClick={() => {
|
||||
handleAggregationChange(aggregation);
|
||||
closeDropdown();
|
||||
}}
|
||||
text={getAggregateOperationLabel(aggregation)}
|
||||
/>
|
||||
|
||||
@ -2,53 +2,44 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { RecordTableColumnFooterAggregateValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue';
|
||||
import { RecordTableColumnFooterDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown';
|
||||
import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter';
|
||||
import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
type RecordTableColumnFooterWithDropdownProps = {
|
||||
column: ColumnDefinition<FieldMetadata>;
|
||||
isFirstCell: boolean;
|
||||
currentRecordGroupId?: string;
|
||||
};
|
||||
|
||||
const StyledDropdown = styled(Dropdown)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||
|
||||
transition: opacity 150ms ease-in-out;
|
||||
`;
|
||||
|
||||
export const RecordTableColumnFooterWithDropdown = ({
|
||||
column,
|
||||
currentRecordGroupId,
|
||||
isFirstCell,
|
||||
}: RecordTableColumnFooterWithDropdownProps) => {
|
||||
const setIsScrollEnabledForRecordTable = useSetRecoilComponentStateV2(
|
||||
isScrollEnabledForRecordTableState,
|
||||
);
|
||||
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||
useToggleScrollWrapper();
|
||||
|
||||
const handleDropdownOpen = useCallback(() => {
|
||||
setIsScrollEnabledForRecordTable({
|
||||
enableXScroll: false,
|
||||
enableYScroll: false,
|
||||
});
|
||||
}, [setIsScrollEnabledForRecordTable]);
|
||||
toggleScrollXWrapper(false);
|
||||
toggleScrollYWrapper(false);
|
||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
setIsScrollEnabledForRecordTable({
|
||||
enableXScroll: true,
|
||||
enableYScroll: true,
|
||||
});
|
||||
}, [setIsScrollEnabledForRecordTable]);
|
||||
toggleScrollXWrapper(true);
|
||||
toggleScrollYWrapper(true);
|
||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
|
||||
const { aggregateValue, aggregateLabel } =
|
||||
useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
|
||||
|
||||
const dropdownId = column.fieldMetadataId + '-footer';
|
||||
const dropdownId = currentRecordGroupId
|
||||
? `${column.fieldMetadataId}-footer-${currentRecordGroupId}`
|
||||
: `${column.fieldMetadataId}-footer`;
|
||||
|
||||
return (
|
||||
<StyledDropdown
|
||||
<Dropdown
|
||||
onOpen={handleDropdownOpen}
|
||||
onClose={handleDropdownClose}
|
||||
dropdownId={dropdownId}
|
||||
@ -57,9 +48,15 @@ export const RecordTableColumnFooterWithDropdown = ({
|
||||
aggregateLabel={aggregateLabel}
|
||||
aggregateValue={aggregateValue}
|
||||
dropdownId={dropdownId}
|
||||
isFirstCell={isFirstCell}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<RecordTableColumnFooterDropdown
|
||||
column={column}
|
||||
dropdownId={dropdownId}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={<RecordTableColumnFooterDropdown column={column} />}
|
||||
dropdownOffset={{ x: -1 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
import { TABLE_CELL_CHECKBOX_MIN_WIDTH } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||
import { TABLE_CELL_GRIP_WIDTH } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
|
||||
import { RecordTableFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableFooterCell';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -75,21 +77,33 @@ const StyledTableFoot = styled.thead`
|
||||
`;
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
width: 30px;
|
||||
width: calc(${TABLE_CELL_GRIP_WIDTH} + ${TABLE_CELL_CHECKBOX_MIN_WIDTH});
|
||||
`;
|
||||
|
||||
export const RecordTableFooter = () => {
|
||||
export const RecordTableFooter = ({
|
||||
currentRecordGroupId,
|
||||
}: {
|
||||
currentRecordGroupId?: string;
|
||||
}) => {
|
||||
const visibleTableColumns = useRecoilComponentValueV2(
|
||||
visibleTableColumnsComponentSelector,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTableFoot id="record-table-footer" data-select-disable>
|
||||
<StyledTableFoot
|
||||
id={`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||
data-select-disable
|
||||
>
|
||||
<tr>
|
||||
<th />
|
||||
<StyledDiv />
|
||||
{visibleTableColumns.map((column) => (
|
||||
<RecordTableFooterCell key={column.fieldMetadataId} column={column} />
|
||||
{visibleTableColumns.map((column, index) => (
|
||||
<RecordTableFooterCell
|
||||
key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||
column={column}
|
||||
currentRecordGroupId={currentRecordGroupId}
|
||||
isFirstCell={index === 0}
|
||||
/>
|
||||
))}
|
||||
</tr>
|
||||
</StyledTableFoot>
|
||||
|
||||
@ -63,8 +63,12 @@ const StyledColumnFootContainer = styled.div`
|
||||
|
||||
export const RecordTableFooterCell = ({
|
||||
column,
|
||||
isFirstCell = false,
|
||||
currentRecordGroupId,
|
||||
}: {
|
||||
column: ColumnDefinition<FieldMetadata>;
|
||||
isFirstCell?: boolean;
|
||||
currentRecordGroupId?: string;
|
||||
}) => {
|
||||
const tableColumns = useRecoilComponentValueV2(tableColumnsComponentState);
|
||||
const tableColumnsByKey = useMemo(
|
||||
@ -82,7 +86,11 @@ export const RecordTableFooterCell = ({
|
||||
)}
|
||||
>
|
||||
<StyledColumnFootContainer>
|
||||
<RecordTableColumnFooterWithDropdown column={column} />
|
||||
<RecordTableColumnFooterWithDropdown
|
||||
column={column}
|
||||
currentRecordGroupId={currentRecordGroupId}
|
||||
isFirstCell={isFirstCell}
|
||||
/>
|
||||
</StyledColumnFootContainer>
|
||||
</StyledColumnFooterCell>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
@ -16,8 +17,9 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
||||
const isAggregateQueryEnabled = useIsFeatureEnabled(
|
||||
'IS_AGGREGATE_QUERY_ENABLED',
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields);
|
||||
|
||||
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
||||
const recordIndexViewFilterGroups = useRecoilValue(
|
||||
recordIndexViewFilterGroupsState,
|
||||
@ -56,7 +58,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
||||
const { data } = useAggregateRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordGqlFieldsAggregate,
|
||||
filter: { ...requestFilters },
|
||||
filter: { ...requestFilters, ...recordGroupFilter },
|
||||
skip:
|
||||
!isAggregateQueryEnabled || !isDefined(aggregateOperationForViewField),
|
||||
});
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback } from 'react';
|
||||
import { RecordTableColumnHead } from './RecordTableColumnHead';
|
||||
@ -21,23 +20,18 @@ const StyledDropdown = styled(Dropdown)`
|
||||
export const RecordTableColumnHeadWithDropdown = ({
|
||||
column,
|
||||
}: RecordTableColumnHeadWithDropdownProps) => {
|
||||
const setIsScrollEnabledForRecordTable = useSetRecoilComponentStateV2(
|
||||
isScrollEnabledForRecordTableState,
|
||||
);
|
||||
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||
useToggleScrollWrapper();
|
||||
|
||||
const handleDropdownOpen = useCallback(() => {
|
||||
setIsScrollEnabledForRecordTable({
|
||||
enableXScroll: false,
|
||||
enableYScroll: false,
|
||||
});
|
||||
}, [setIsScrollEnabledForRecordTable]);
|
||||
toggleScrollXWrapper(false);
|
||||
toggleScrollYWrapper(false);
|
||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
setIsScrollEnabledForRecordTable({
|
||||
enableXScroll: true,
|
||||
enableYScroll: true,
|
||||
});
|
||||
}, [setIsScrollEnabledForRecordTable]);
|
||||
toggleScrollXWrapper(true);
|
||||
toggleScrollYWrapper(true);
|
||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
|
||||
return (
|
||||
<StyledDropdown
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export type ScrollEnabled = {
|
||||
enableXScroll: boolean;
|
||||
enableYScroll: boolean;
|
||||
};
|
||||
|
||||
export const isScrollEnabledForRecordTableState =
|
||||
createComponentStateV2<ScrollEnabled>({
|
||||
key: 'isScrollEnabledForRecordTableState',
|
||||
defaultValue: {
|
||||
enableXScroll: true,
|
||||
enableYScroll: true,
|
||||
},
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
@ -1,6 +1,5 @@
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
|
||||
export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
|
||||
size: number;
|
||||
@ -10,5 +9,4 @@ export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
|
||||
viewFieldId?: string;
|
||||
isFilterable?: boolean;
|
||||
isSortable?: boolean;
|
||||
aggregateOperation?: AGGREGATE_OPERATIONS | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user