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:
@ -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}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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 }) =>
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
},
|
||||
);
|
||||
@ -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,
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
@ -100,9 +100,7 @@ export const RecordTable = () => {
|
||||
{isAggregateQueryEnabled &&
|
||||
!hasRecordGroups &&
|
||||
!isRecordTableInitialLoading &&
|
||||
allRecordIds.length > 0 && (
|
||||
<RecordTableAggregateFooter endOfTableSticky />
|
||||
)}
|
||||
allRecordIds.length > 0 && <RecordTableAggregateFooter />}
|
||||
</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 { 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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
[
|
||||
|
||||
@ -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],
|
||||
);
|
||||
|
||||
@ -33,7 +33,7 @@ const StyledTbody = styled.tbody`
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
&:not(.disable-shadow)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -34,6 +34,7 @@ const StyledColumnFooterCell = styled.th<{
|
||||
};
|
||||
`;
|
||||
}};
|
||||
height: 32px;
|
||||
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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)`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 <></>;
|
||||
};
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
});
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user