Feat/view groups fast follow (#9513)

Fix #9512 

- 🟠 [Icon should be
lighter](https://discord.com/channels/1130383047699738754/1326487470895923222)
The current weight is the same as in Figma, waiting for confirmation
- 🟠 [None has an unwanted margin
left](https://discord.com/channels/1130383047699738754/1326493647796961323)
This component is used in lot of places, removing the padding left can
brake other places
- 🟢 [All cells should have a the same right
design](https://discord.com/channels/1130383047699738754/1326489001926066176)
- 🔴 [Group Sorting should not "freeze" when mouse is
release](https://discord.com/channels/1130383047699738754/1326494381795966996)
Can't find a good way to fix it, seems more related to the fact it's
running in debug mode.
- 🟢 [Alignment
issue](https://discord.com/channels/1130383047699738754/1326486523822084140)
- 🟢 [View record count
error](https://discord.com/channels/1130383047699738754/1326491489466978365)
- 🟢 [Vertically align tags and
numbers/count](https://discord.com/channels/1130383047699738754/1326490661800902728)
- 🟢 [Display "Calculate" only on hover in view
groups](https://discord.com/channels/1130383047699738754/1326490411929436191)
- 🟢 [Aggregates height in view groups is 28px instead of
32px](https://discord.com/channels/1130383047699738754/1326489587127943188)
- 🟠 [Picker under the
aggregate](https://discord.com/channels/1130383047699738754/1326487940557439039)
Can't reproduce the issue
- 🟢 [Icon should not be
hoverable](https://discord.com/channels/1130383047699738754/1326477402360123425)
- 🟢 [Crop long view
titles](https://discord.com/channels/1130383047699738754/1326477009576136755)
- 🟢 [Removing the group by on opportunities (group by none) give an
white
screen](https://discord.com/channels/1130383047699738754/1324651927962910750)
This commit is contained in:
Jérémy M
2025-01-09 19:12:57 +01:00
committed by GitHub
parent 1f1cac3b00
commit 0a798a6671
25 changed files with 225 additions and 303 deletions

View File

@ -24,6 +24,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType';
import { useLocation } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -33,6 +34,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
const { getIcon } = useIcons();
const {
viewType,
currentContentId,
recordIndexId,
objectMetadataItem,
@ -121,11 +123,13 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
onChange={(event) => setRecordGroupFieldSearchInput(event.target.value)}
/>
<DropdownMenuItemsContainer>
<MenuItemSelect
text="None"
selected={!isDefined(recordGroupFieldMetadata)}
onClick={handleResetRecordGroupField}
/>
{viewType === ViewType.Table && (
<MenuItemSelect
text="None"
selected={!isDefined(recordGroupFieldMetadata)}
onClick={handleResetRecordGroupField}
/>
)}
{filteredRecordGroupFieldMetadataItems.map((fieldMetadataItem) => (
<MenuItemSelect
key={fieldMetadataItem.id}

View File

@ -7,7 +7,6 @@ import { RecordIndexBoardContainer } from '@/object-record/record-index/componen
import { RecordIndexBoardDataLoader } from '@/object-record/record-index/components/RecordIndexBoardDataLoader';
import { RecordIndexBoardDataLoaderEffect } from '@/object-record/record-index/components/RecordIndexBoardDataLoaderEffect';
import { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer';
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
@ -28,6 +27,7 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';

View File

@ -5,10 +5,10 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useSetRecordIndexEntityCount } from '@/object-record/record-index/hooks/useSetRecordIndexEntityCount';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
import { ViewField } from '@/views/types/ViewField';
import { useRecoilCallback } from 'recoil';
@ -33,8 +33,7 @@ export const RecordIndexTableContainerEffect = () => {
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { setRecordCountInCurrentView } =
useSetRecordCountInCurrentView(viewBarId);
const { setRecordIndexEntityCount } = useSetRecordIndexEntityCount(viewBarId);
useEffect(() => {
setAvailableTableColumns(columnDefinitions);
@ -68,9 +67,10 @@ export const RecordIndexTableContainerEffect = () => {
useEffect(() => {
setOnEntityCountChange(
() => (entityCount: number) => setRecordCountInCurrentView(entityCount),
() => (entityCount: number, currentRecordGroupId?: string) =>
setRecordIndexEntityCount(entityCount, currentRecordGroupId),
);
}, [setRecordCountInCurrentView, setOnEntityCountChange]);
}, [setRecordIndexEntityCount, setOnEntityCountChange]);
const setViewFieldAggregateOperation = useRecoilCallback(
({ set, snapshot }) =>

View File

@ -1,123 +0,0 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useSetRecordBoardRecordIds } from '@/object-record/record-board/hooks/useSetRecordBoardRecordIds';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
type UseLoadRecordIndexBoardProps = {
objectNameSingular: string;
viewBarId: string;
recordBoardId: string;
};
export const useLoadRecordIndexBoard = ({
objectNameSingular,
viewBarId,
recordBoardId,
}: UseLoadRecordIndexBoardProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const setRecordBoardFieldDefinitions = useSetRecoilComponentStateV2(
recordBoardFieldDefinitionsComponentState,
recordBoardId,
);
const { setRecordIds: setRecordIdsInBoard } =
useSetRecordBoardRecordIds(recordBoardId);
const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore();
const recordIndexFieldDefinitions = useRecoilValue(
recordIndexFieldDefinitionsState,
);
useEffect(() => {
setRecordBoardFieldDefinitions(recordIndexFieldDefinitions);
}, [recordIndexFieldDefinitions, setRecordBoardFieldDefinitions]);
const recordIndexViewFilterGroups = useRecoilValue(
recordIndexViewFilterGroupsState,
);
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
const { filterValueDependencies } = useFilterValueDependencies();
const requestFilters = computeViewRecordGqlOperationFilter(
filterValueDependencies,
recordIndexFilters,
objectMetadataItem?.fields ?? [],
recordIndexViewFilterGroups,
);
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, recordIndexSorts);
const recordIndexIsCompactModeActive = useRecoilValue(
recordIndexIsCompactModeActiveState,
);
const recordGqlFields = useRecordBoardRecordGqlFields({
objectMetadataItem,
recordBoardId,
});
const {
records,
totalCount,
loading,
fetchMoreRecords,
queryStateIdentifier,
} = useFindManyRecords({
objectNameSingular,
filter: requestFilters,
orderBy,
recordGqlFields,
});
const { setRecordCountInCurrentView } =
useSetRecordCountInCurrentView(viewBarId);
const setIsCompactModeActive = useSetRecoilComponentStateV2(
isRecordBoardCompactModeActiveComponentState,
recordBoardId,
);
useEffect(() => {
setRecordIdsInBoard(records);
}, [records, setRecordIdsInBoard]);
useEffect(() => {
upsertRecordsInStore(records);
}, [records, upsertRecordsInStore]);
useEffect(() => {
setRecordCountInCurrentView(totalCount);
}, [totalCount, setRecordCountInCurrentView]);
useEffect(() => {
setIsCompactModeActive(recordIndexIsCompactModeActive);
}, [recordIndexIsCompactModeActive, setIsCompactModeActive]);
return {
records,
loading,
fetchMoreRecords,
queryStateIdentifier,
};
};

View File

@ -0,0 +1,49 @@
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
import { recordIndexEntityCountByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexEntityCountByGroupComponentFamilyState';
import { recordIndexEntityCountNoGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexEntityCountNoGroupComponentFamilyState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilCallback } from 'recoil';
import { isDefined } from '~/utils/isDefined';
export const useSetRecordIndexEntityCount = (viewBarComponentId?: string) => {
const hasRecordGroup = useRecoilComponentValueV2(
hasRecordGroupsComponentSelector,
);
const recordIndexEntityCountNoGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordIndexEntityCountNoGroupComponentFamilyState,
viewBarComponentId,
);
const recordIndexEntityCountByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordIndexEntityCountByGroupComponentFamilyState,
viewBarComponentId,
);
const setRecordIndexEntityCount = useRecoilCallback(
({ set }) =>
(count: number, recordGroupId?: string) => {
if (hasRecordGroup) {
if (!isDefined(recordGroupId)) {
throw new Error('Record group ID is required');
}
set(recordIndexEntityCountByGroupFamilyState(recordGroupId), count);
} else {
set(recordIndexEntityCountNoGroupFamilyState, count);
}
},
[
hasRecordGroup,
recordIndexEntityCountByGroupFamilyState,
recordIndexEntityCountNoGroupFamilyState,
],
);
return {
setRecordIndexEntityCount,
};
};

View File

@ -0,0 +1,12 @@
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const recordIndexEntityCountByGroupComponentFamilyState =
createComponentFamilyStateV2<number | undefined, RecordGroupDefinition['id']>(
{
key: 'recordIndexEntityCountByGroupComponentFamilyState',
defaultValue: undefined,
componentInstanceContext: ViewComponentInstanceContext,
},
);

View File

@ -0,0 +1,9 @@
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const recordIndexEntityCountNoGroupComponentFamilyState =
createComponentStateV2<number | undefined>({
key: 'recordIndexEntityCountNoGroupComponentFamilyState',
defaultValue: undefined,
componentInstanceContext: ViewComponentInstanceContext,
});

View File

@ -0,0 +1,39 @@
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
import { recordIndexEntityCountByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexEntityCountByGroupComponentFamilyState';
import { recordIndexEntityCountNoGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexEntityCountNoGroupComponentFamilyState';
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const recordIndexEntityCountComponentSelector =
createComponentSelectorV2<number | undefined>({
key: 'recordIndexEntityCountComponentSelector',
get:
({ instanceId }) =>
({ get }) => {
const recordGroupIds = get(
recordGroupIdsComponentState.atomFamily({
instanceId,
}),
);
if (recordGroupIds.length === 0) {
return get(
recordIndexEntityCountNoGroupComponentFamilyState.atomFamily({
instanceId,
}),
);
}
return recordGroupIds.reduce<number>((acc, recordGroupId) => {
const count = get(
recordIndexEntityCountByGroupComponentFamilyState.atomFamily({
instanceId,
familyKey: recordGroupId,
}),
);
return acc + (count ?? 0);
}, 0);
},
componentInstanceContext: ViewComponentInstanceContext,
});

View File

@ -100,9 +100,7 @@ export const RecordTable = () => {
{isAggregateQueryEnabled &&
!hasRecordGroups &&
!isRecordTableInitialLoading &&
allRecordIds.length > 0 && (
<RecordTableAggregateFooter endOfTableSticky />
)}
allRecordIds.length > 0 && <RecordTableAggregateFooter />}
</StyledTable>
<DragSelect
dragSelectable={tableBodyRef}

View File

@ -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 { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
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,16 @@ 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 { FeatureFlagKey } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
export const RecordTableRecordGroupRows = () => {
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
const currentRecordGroupId = useCurrentRecordGroupId();
const allRecordIds = useRecoilComponentValueV2(
@ -59,6 +66,12 @@ export const RecordTableRecordGroupRows = () => {
<RecordTablePendingRecordGroupRow />
<RecordTableRecordGroupSectionAddNew />
<RecordTableRecordGroupSectionLoadMore />
{isAggregateQueryEnabled && (
<RecordTableAggregateFooter
key={currentRecordGroupId}
currentRecordGroupId={currentRecordGroupId}
/>
)}
</>
);
};

View File

@ -13,7 +13,10 @@ import { isDefined } from '~/utils/isDefined';
type useSetRecordTableDataProps = {
recordTableId?: string;
onEntityCountChange: (entityCount?: number) => void;
onEntityCountChange: (
entityCount?: number,
currentRecordGroupId?: string,
) => void;
};
export const useSetRecordTableData = ({
@ -93,7 +96,7 @@ export const useSetRecordTableData = ({
set(recordIndexAllRecordIdsSelector, recordIds);
}
onEntityCountChange(totalCount);
onEntityCountChange(totalCount, currentRecordGroupId);
}
},
[

View File

@ -156,13 +156,13 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const onEntityCountChange = useRecoilCallback(
({ snapshot }) =>
(count?: number) => {
(count?: number, currentRecordGroupId?: string) => {
const onEntityCountChange = getSnapshotValue(
snapshot,
onEntityCountChangeState,
);
onEntityCountChange?.(count);
onEntityCountChange?.(count, currentRecordGroupId);
},
[onEntityCountChangeState],
);

View File

@ -33,7 +33,7 @@ const StyledTbody = styled.tbody`
}
}
&::after {
&:not(.disable-shadow)::after {
content: '';
position: absolute;
top: -1px;

View File

@ -6,19 +6,13 @@ import { RecordTableRecordGroupRows } from '@/object-record/record-table/compone
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 { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider';
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';
export const RecordTableRecordGroupsBody = () => {
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
const allRecordIds = useRecoilComponentValueV2(
recordIndexAllRecordIdsComponentSelector,
);
@ -49,12 +43,6 @@ export const RecordTableRecordGroupsBody = () => {
<RecordTableRecordGroupSection />
<RecordTableRecordGroupRows />
</RecordTableBodyDroppable>
{isAggregateQueryEnabled && (
<RecordTableAggregateFooter
key={recordGroupId}
currentRecordGroupId={recordGroupId}
/>
)}
</RecordGroupContext.Provider>
</RecordTableRecordGroupBodyContextProvider>
))}

View File

@ -1,124 +1,41 @@
import styled from '@emotion/styled';
import { MOBILE_VIEWPORT } from 'twenty-ui';
import { RecordTableAggregateFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell';
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { FIRST_TH_WIDTH } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
const StyledTableFoot = styled.thead<{ endOfTableSticky?: boolean }>`
cursor: pointer;
th:nth-of-type(1) {
width: ${FIRST_TH_WIDTH};
left: 0;
border-right-color: ${({ theme }) => theme.background.primary};
}
th:nth-of-type(2) {
border-right-color: ${({ theme }) => theme.background.primary};
}
&.first-columns-sticky {
th:nth-of-type(1) {
position: sticky;
left: 0;
z-index: 5;
transition: 0.3s ease;
}
th:nth-of-type(2) {
position: sticky;
left: 11px;
z-index: 5;
transition: 0.3s ease;
}
th:nth-of-type(3) {
position: sticky;
left: 43px;
z-index: 5;
transition: 0.3s ease;
&::after {
content: '';
position: absolute;
top: -1px;
height: calc(100% + 2px);
width: 4px;
right: 0px;
box-shadow: ${({ theme }) => theme.boxShadow.light};
clip-path: inset(0px -4px 0px 0px);
}
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 34px;
max-width: 34px;
}
}
}
tr {
position: sticky;
z-index: 5;
background: ${({ theme }) => theme.background.primary};
${({ endOfTableSticky }) =>
endOfTableSticky &&
`
bottom: 10px;
&::after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
right: 0;
height: 10px;
background: inherit;
}
`}
}
`;
const StyledTh = styled.th`
background-color: ${({ theme }) => theme.background.primary};
`;
export const RecordTableAggregateFooter = ({
currentRecordGroupId,
endOfTableSticky,
}: {
currentRecordGroupId?: string;
endOfTableSticky?: boolean;
}) => {
const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector,
);
return (
<StyledTableFoot
id={`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
data-select-disable
endOfTableSticky={endOfTableSticky}
>
<tr>
<StyledTh />
<StyledTh />
{visibleTableColumns.map((column, index) => (
<RecordTableColumnAggregateFooterCellContext.Provider
key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
value={{
viewFieldId: column.viewFieldId || '',
fieldMetadataId: column.fieldMetadataId,
}}
>
<RecordTableAggregateFooterCell
currentRecordGroupId={currentRecordGroupId}
isFirstCell={index === 0}
/>
</RecordTableColumnAggregateFooterCellContext.Provider>
))}
</tr>
</StyledTableFoot>
<tr>
<StyledTh />
<StyledTh />
{visibleTableColumns.map((column, index) => (
<RecordTableColumnAggregateFooterCellContext.Provider
key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
value={{
viewFieldId: column.viewFieldId || '',
fieldMetadataId: column.fieldMetadataId,
}}
>
<RecordTableAggregateFooterCell
currentRecordGroupId={currentRecordGroupId}
isFirstCell={index === 0}
/>
</RecordTableColumnAggregateFooterCellContext.Provider>
))}
</tr>
);
};

View File

@ -34,6 +34,7 @@ const StyledColumnFooterCell = styled.th<{
};
`;
}};
height: 32px;
user-select: none;
overflow: auto;

View File

@ -1,7 +1,9 @@
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { RecordTableColumnAggregateFooterValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue';
import { hasAggregateOperationForViewFieldFamilySelector } from '@/object-record/record-table/record-table-footer/states/hasAggregateOperationForViewFieldFamilySelector';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useContext, useState } from 'react';
@ -53,6 +55,10 @@ export const RecordTableColumnAggregateFooterValueCell = ({
}),
);
const hasRecordGroups = useRecoilComponentValueV2(
hasRecordGroupsComponentSelector,
);
return (
<div
onMouseEnter={() => {
@ -64,7 +70,7 @@ export const RecordTableColumnAggregateFooterValueCell = ({
{isHovered ||
isDropdownOpen ||
hasAggregateOperationForViewField ||
isFirstCell ? (
(isFirstCell && !hasRecordGroups) ? (
<>
<RecordTableColumnAggregateFooterValue
fieldMetadataId={fieldMetadataId}

View File

@ -13,9 +13,10 @@ const StyledRecordTableDraggableTr = styled(RecordTableDraggableTr)`
const StyledIconContainer = styled(RecordTableTd)`
border-right: none;
color: ${({ theme }) => theme.font.color.secondary};
text-align: center;
vertical-align: middle;
padding-top: 3px;
display: flex;
height: 32px;
align-items: center;
justify-content: center;
`;
const StyledRecordTableTdTextContainer = styled(RecordTableTd)`

View File

@ -38,6 +38,8 @@ const StyledTotalRow = styled.span`
const StyledRecordGroupSection = styled(RecordTableTd)`
border-right: none;
height: 32px;
display: flex;
align-items: center;
`;
const StyledEmptyTd = styled.td`
@ -92,7 +94,7 @@ export const RecordTableRecordGroupSection = () => {
<IconChevronDown size={theme.icon.size.md} />
</motion.span>
</StyledChevronContainer>
<StyledRecordGroupSection>
<StyledRecordGroupSection className="disable-shadow">
<Tag
variant={
recordGroup.type !== RecordGroupDefinitionType.NoValue

View File

@ -2,7 +2,7 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-tabl
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const onEntityCountChangeComponentState = createComponentStateV2<
((entityCount?: number) => void) | undefined
((entityCount?: number, currentRecordGroupId?: string) => void) | undefined
>({
key: 'onEntityCountChangeComponentState',
defaultValue: undefined,

View File

@ -2,13 +2,13 @@ import { useEffect } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useSetRecordIndexEntityCount } from '@/object-record/record-index/hooks/useSetRecordIndexEntityCount';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions';
import { SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions';
import { SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions';
import { SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS } from '@/sign-in-background-mock/constants/SignInBackgroundMockViewFields';
import { useInitViewBar } from '@/views/hooks/useInitViewBar';
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
type SignInBackgroundMockContainerEffectProps = {
@ -42,8 +42,7 @@ export const SignInBackgroundMockContainerEffect = ({
setViewObjectMetadataId,
} = useInitViewBar(viewId);
const { setRecordCountInCurrentView } =
useSetRecordCountInCurrentView(viewId);
const { setRecordIndexEntityCount } = useSetRecordIndexEntityCount(viewId);
useEffect(() => {
setViewObjectMetadataId?.(objectMetadataItem.id);
@ -72,9 +71,9 @@ export const SignInBackgroundMockContainerEffect = ({
useEffect(() => {
setOnEntityCountChange(
() => (entityCount: number) => setRecordCountInCurrentView(entityCount),
() => (entityCount: number) => setRecordIndexEntityCount(entityCount),
);
}, [setRecordCountInCurrentView, setOnEntityCountChange]);
}, [setRecordIndexEntityCount, setOnEntityCountChange]);
return <></>;
};

View File

@ -1,7 +1,7 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ComponentProps, MouseEvent } from 'react';
import { IconComponent, LightIconButton } from 'twenty-ui';
import { IconComponent, isDefined, LightIconButton } from 'twenty-ui';
const StyledHeader = styled.li`
align-items: center;
@ -41,6 +41,26 @@ const StyledEndIcon = styled.div`
const StyledChildrenWrapper = styled.span`
overflow: hidden;
padding: 0 ${({ theme }) => theme.spacing(1)};
white-space: nowrap;
text-overflow: ellipsis;
`;
const StyledNonClickableStartIcon = styled.div`
align-items: center;
background: transparent;
border: none;
display: flex;
flex-direction: row;
font-family: ${({ theme }) => theme.font.family};
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
white-space: nowrap;
height: 24px;
width: 24px;
`;
type DropdownMenuHeaderProps = ComponentProps<'li'> & {
@ -76,13 +96,22 @@ export const DropdownMenuHeader = ({
)}
{StartIcon && (
<StyledHeader data-testid={testId} className={className}>
<LightIconButton
testId="dropdown-menu-header-end-icon"
Icon={StartIcon}
accent="tertiary"
size="small"
onClick={onClick}
/>
{isDefined(onClick) ? (
<LightIconButton
testId="dropdown-menu-header-end-icon"
Icon={StartIcon}
accent="tertiary"
size="small"
onClick={onClick}
/>
) : (
<StyledNonClickableStartIcon>
<StartIcon
size={theme.icon.size.sm}
color={theme.font.color.tertiary}
/>
</StyledNonClickableStartIcon>
)}
<StyledChildrenWrapper>{children}</StyledChildrenWrapper>
</StyledHeader>
)}

View File

@ -1,13 +0,0 @@
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
export const useSetRecordCountInCurrentView = (viewBarComponentId?: string) => {
const setEntityCountInCurrentView = useSetRecoilComponentStateV2(
entityCountInCurrentViewComponentState,
viewBarComponentId,
);
return {
setRecordCountInCurrentView: setEntityCountInCurrentView,
};
};

View File

@ -1,10 +0,0 @@
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const entityCountInCurrentViewComponentState = createComponentStateV2<
number | undefined
>({
key: 'entityCountInCurrentViewComponentState',
defaultValue: undefined,
componentInstanceContext: ViewComponentInstanceContext,
});

View File

@ -7,12 +7,12 @@ import {
useIcons,
} from 'twenty-ui';
import { recordIndexEntityCountComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexEntityCountComponentSelector';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
import { ViewPickerContentCreateMode } from '@/views/view-picker/components/ViewPickerContentCreateMode';
import { ViewPickerContentEditMode } from '@/views/view-picker/components/ViewPickerContentEditMode';
@ -55,8 +55,8 @@ export const ViewPickerDropdown = () => {
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
const entityCountInCurrentView = useRecoilComponentValueV2(
entityCountInCurrentViewComponentState,
const entityCount = useRecoilComponentValueV2(
recordIndexEntityCountComponentSelector,
);
const { isDropdownOpen: isViewsListDropdownOpen } = useDropdown(
@ -94,9 +94,7 @@ export const ViewPickerDropdown = () => {
{currentViewWithCombinedFiltersAndSorts?.name ?? 'All'}
</StyledViewName>
<StyledDropdownLabelAdornments>
{isDefined(entityCountInCurrentView) && (
<>· {entityCountInCurrentView} </>
)}
{isDefined(entityCount) && <>· {entityCount} </>}
<IconChevronDown size={theme.icon.size.sm} />
</StyledDropdownLabelAdornments>
</StyledDropdownButtonContainer>