fix: view group followup (#9162)
This PR fixes all followup that @Bonapara add on Discord. - [x] When no group by is set, clicking on group by should open the "field selection" menu - [x] When closed, chevron should be "chevron-right" instead of "chevron-up" - [x] Sort : Add ability to switch from alphabetical to manual when moving a option in sort alphabetical - [x] Add subtext for group by and sort - [x] Group by menu display bug - [x] Changing the sort should not close the menu - [x] Group by Activation -> shows empty state + is slow - [x] Switching from Kanban view Settings to Table Options menu displays an empty menu - [x] Unnecessary spacing under groups - [x] When no "select" are set on an object, redirect the user directly to the new Select field page - [x] Sort : Default should be manual - [x] Hidding "no value" displays all options and remove the "hide empty group" toggle - [x] Hide Empty group option disappeared - [x] Group by should not be persisted on "Locked/Main view" (**For now we just disable the group by on main view**) - [x] Hide Empty group should not be activated by default on Opportunities Kanban view - [ ] Animate the group opening/closing (**We'll be done later**) Performance improvement: https://github.com/user-attachments/assets/fd2acf66-0e56-45d0-8b2f-99c62e57d6f7 https://github.com/user-attachments/assets/80f1a2e1-9f77-4923-b85d-acb9cad96886 Also fix #9036 --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -330,7 +330,6 @@ export enum FeatureFlagKey {
|
|||||||
IsSsoEnabled = 'IsSSOEnabled',
|
IsSsoEnabled = 'IsSSOEnabled',
|
||||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||||
IsViewGroupsEnabled = 'IsViewGroupsEnabled',
|
|
||||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export const triggerAttachRelationOptimisticEffect = ({
|
|||||||
id: targetRecordCacheId,
|
id: targetRecordCacheId,
|
||||||
fields: {
|
fields: {
|
||||||
[fieldNameOnTargetRecord]: (targetRecordFieldValue, { toReference }) => {
|
[fieldNameOnTargetRecord]: (targetRecordFieldValue, { toReference }) => {
|
||||||
const fieldValueisObjectRecordConnectionWithRefs =
|
const fieldValueIsObjectRecordConnectionWithRefs =
|
||||||
isObjectRecordConnectionWithRefs(
|
isObjectRecordConnectionWithRefs(
|
||||||
sourceObjectNameSingular,
|
sourceObjectNameSingular,
|
||||||
targetRecordFieldValue,
|
targetRecordFieldValue,
|
||||||
@ -47,7 +47,7 @@ export const triggerAttachRelationOptimisticEffect = ({
|
|||||||
return targetRecordFieldValue;
|
return targetRecordFieldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldValueisObjectRecordConnectionWithRefs) {
|
if (fieldValueIsObjectRecordConnectionWithRefs) {
|
||||||
const nextEdges: RecordGqlRefEdge[] = [
|
const nextEdges: RecordGqlRefEdge[] = [
|
||||||
...targetRecordFieldValue.edges,
|
...targetRecordFieldValue.edges,
|
||||||
{
|
{
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export const ObjectOptionsDropdown = ({
|
|||||||
clickableComponent={
|
clickableComponent={
|
||||||
<StyledHeaderDropdownButton>Options</StyledHeaderDropdownButton>
|
<StyledHeaderDropdownButton>Options</StyledHeaderDropdownButton>
|
||||||
}
|
}
|
||||||
|
onClose={handleResetContent}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<ObjectOptionsDropdownContext.Provider
|
<ObjectOptionsDropdownContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { useSetRecoilState } from 'recoil';
|
|||||||
|
|
||||||
export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
||||||
const {
|
const {
|
||||||
|
viewType,
|
||||||
currentContentId,
|
currentContentId,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
@ -47,6 +48,7 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
|||||||
const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
|
const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
|
||||||
useRecordGroupVisibility({
|
useRecordGroupVisibility({
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
|
viewType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewGroupSettingsUrl = getSettingsPagePath(
|
const viewGroupSettingsUrl = getSettingsPagePath(
|
||||||
|
|||||||
@ -30,8 +30,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { FeatureFlagKey } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export const ObjectOptionsDropdownMenuContent = () => {
|
export const ObjectOptionsDropdownMenuContent = () => {
|
||||||
const {
|
const {
|
||||||
@ -42,10 +41,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
closeDropdown,
|
closeDropdown,
|
||||||
} = useOptionsDropdown();
|
} = useOptionsDropdown();
|
||||||
|
|
||||||
const isViewGroupEnabled = useIsFeatureEnabled(
|
|
||||||
FeatureFlagKey.IsViewGroupsEnabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const { currentViewWithCombinedFiltersAndSorts: currentView } =
|
const { currentViewWithCombinedFiltersAndSorts: currentView } =
|
||||||
useGetCurrentView();
|
useGetCurrentView();
|
||||||
@ -120,9 +115,13 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
contextualText={`${visibleBoardFields.length} shown`}
|
contextualText={`${visibleBoardFields.length} shown`}
|
||||||
hasSubMenu
|
hasSubMenu
|
||||||
/>
|
/>
|
||||||
{(viewType === ViewType.Kanban || isViewGroupEnabled) && (
|
{viewType === ViewType.Kanban && currentView?.key !== 'INDEX' && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => onContentChange('recordGroups')}
|
onClick={() =>
|
||||||
|
isDefined(recordGroupFieldMetadata)
|
||||||
|
? onContentChange('recordGroups')
|
||||||
|
: onContentChange('recordGroupFields')
|
||||||
|
}
|
||||||
LeftIcon={IconLayoutList}
|
LeftIcon={IconLayoutList}
|
||||||
text="Group by"
|
text="Group by"
|
||||||
contextualText={recordGroupFieldMetadata?.label}
|
contextualText={recordGroupFieldMetadata?.label}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||||
@ -36,6 +37,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
|||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
onContentChange,
|
onContentChange,
|
||||||
|
resetContent,
|
||||||
closeDropdown,
|
closeDropdown,
|
||||||
} = useOptionsDropdown();
|
} = useOptionsDropdown();
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
|||||||
hiddenRecordGroupIdsComponentSelector,
|
hiddenRecordGroupIdsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordGroupFieldMetadataItem = useRecoilComponentValueV2(
|
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||||
recordGroupFieldMetadataComponentState,
|
recordGroupFieldMetadataComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -64,11 +66,14 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
|||||||
viewBarComponentId: recordIndexId,
|
viewBarComponentId: recordIndexId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newFieldSettingsUrl = getSettingsPagePath(
|
const newSelectFieldSettingsUrl = getSettingsPagePath(
|
||||||
SettingsPath.ObjectNewFieldSelect,
|
SettingsPath.ObjectNewFieldConfigure,
|
||||||
{
|
{
|
||||||
objectSlug: objectNamePlural,
|
objectSlug: objectNamePlural,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldType: FieldMetadataType.Select,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -101,7 +106,11 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
|||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
StartIcon={IconChevronLeft}
|
StartIcon={IconChevronLeft}
|
||||||
onClick={() => onContentChange('recordGroups')}
|
onClick={() =>
|
||||||
|
isDefined(recordGroupFieldMetadata)
|
||||||
|
? onContentChange('recordGroups')
|
||||||
|
: resetContent()
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Group by
|
Group by
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
@ -114,13 +123,13 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
|||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<MenuItemSelect
|
<MenuItemSelect
|
||||||
text="None"
|
text="None"
|
||||||
selected={!isDefined(recordGroupFieldMetadataItem)}
|
selected={!isDefined(recordGroupFieldMetadata)}
|
||||||
onClick={handleResetRecordGroupField}
|
onClick={handleResetRecordGroupField}
|
||||||
/>
|
/>
|
||||||
{filteredRecordGroupFieldMetadataItems.map((fieldMetadataItem) => (
|
{filteredRecordGroupFieldMetadataItems.map((fieldMetadataItem) => (
|
||||||
<MenuItemSelect
|
<MenuItemSelect
|
||||||
key={fieldMetadataItem.id}
|
key={fieldMetadataItem.id}
|
||||||
selected={fieldMetadataItem.id === recordGroupFieldMetadataItem?.id}
|
selected={fieldMetadataItem.id === recordGroupFieldMetadata?.id}
|
||||||
onClick={() => handleRecordGroupFieldChange(fieldMetadataItem)}
|
onClick={() => handleRecordGroupFieldChange(fieldMetadataItem)}
|
||||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||||
text={fieldMetadataItem.label}
|
text={fieldMetadataItem.label}
|
||||||
@ -130,7 +139,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<UndecoratedLink
|
<UndecoratedLink
|
||||||
to={newFieldSettingsUrl}
|
to={newSelectFieldSettingsUrl}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setNavigationMemorizedUrl(location.pathname + location.search);
|
setNavigationMemorizedUrl(location.pathname + location.search);
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
|
|||||||
@ -17,8 +17,7 @@ import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||||
const { currentContentId, onContentChange, closeDropdown } =
|
const { currentContentId, onContentChange } = useOptionsDropdown();
|
||||||
useOptionsDropdown();
|
|
||||||
|
|
||||||
const hiddenRecordGroupIds = useRecoilComponentValueV2(
|
const hiddenRecordGroupIds = useRecoilComponentValueV2(
|
||||||
hiddenRecordGroupIdsComponentSelector,
|
hiddenRecordGroupIdsComponentSelector,
|
||||||
@ -30,7 +29,6 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
|||||||
|
|
||||||
const handleRecordGroupSortChange = (sort: RecordGroupSort) => {
|
const handleRecordGroupSortChange = (sort: RecordGroupSort) => {
|
||||||
setRecordGroupSort(sort);
|
setRecordGroupSort(sort);
|
||||||
closeDropdown();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -11,47 +11,54 @@ import {
|
|||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||||
|
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
|
||||||
import { RecordGroupsVisibilityDropdownSection } from '@/object-record/record-group/components/RecordGroupsVisibilityDropdownSection';
|
import { RecordGroupsVisibilityDropdownSection } from '@/object-record/record-group/components/RecordGroupsVisibilityDropdownSection';
|
||||||
import { useRecordGroupReorder } from '@/object-record/record-group/hooks/useRecordGroupReorder';
|
import { useRecordGroupReorderConfirmationModal } from '@/object-record/record-group/hooks/useRecordGroupReorderConfirmationModal';
|
||||||
import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility';
|
import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility';
|
||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { recordIndexRecordGroupHideComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentState';
|
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||||
import { recordIndexRecordGroupIsDraggableSortComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexRecordGroupIsDraggableSortComponentSelector';
|
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
import { FeatureFlagKey } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||||
const isViewGroupEnabled = useIsFeatureEnabled(
|
const {
|
||||||
FeatureFlagKey.IsViewGroupsEnabled,
|
viewType,
|
||||||
);
|
currentContentId,
|
||||||
|
recordIndexId,
|
||||||
|
onContentChange,
|
||||||
|
resetContent,
|
||||||
|
} = useOptionsDropdown();
|
||||||
|
|
||||||
const { currentContentId, recordIndexId, onContentChange, resetContent } =
|
const { currentViewWithCombinedFiltersAndSorts: currentView } =
|
||||||
useOptionsDropdown();
|
useGetCurrentView();
|
||||||
|
|
||||||
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||||
recordGroupFieldMetadataComponentState,
|
recordGroupFieldMetadataComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
|
viewType,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hiddenRecordGroupIds = useRecoilComponentValueV2(
|
const hiddenRecordGroupIds = useRecoilComponentValueV2(
|
||||||
hiddenRecordGroupIdsComponentSelector,
|
hiddenRecordGroupIdsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDragableSortRecordGroup = useRecoilComponentValueV2(
|
const hideEmptyRecordGroup = useRecoilComponentFamilyValueV2(
|
||||||
recordIndexRecordGroupIsDraggableSortComponentSelector,
|
recordIndexRecordGroupHideComponentFamilyState,
|
||||||
|
viewType,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hideEmptyRecordGroup = useRecoilComponentValueV2(
|
const recordGroupSort = useRecoilComponentValueV2(
|
||||||
recordIndexRecordGroupHideComponentState,
|
recordIndexRecordGroupSortComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -59,12 +66,16 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
|||||||
handleHideEmptyRecordGroupChange,
|
handleHideEmptyRecordGroupChange,
|
||||||
} = useRecordGroupVisibility({
|
} = useRecordGroupVisibility({
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
|
viewType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { handleOrderChange: handleRecordGroupOrderChange } =
|
const {
|
||||||
useRecordGroupReorder({
|
handleRecordGroupOrderChangeWithModal,
|
||||||
viewBarId: recordIndexId,
|
handleRecordGroupReorderConfirmClick,
|
||||||
});
|
} = useRecordGroupReorderConfirmationModal({
|
||||||
|
recordIndexId,
|
||||||
|
viewType,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -81,22 +92,20 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
|||||||
Group by
|
Group by
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{isViewGroupEnabled && (
|
{currentView?.key !== 'INDEX' && (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => onContentChange('recordGroupFields')}
|
onClick={() => onContentChange('recordGroupFields')}
|
||||||
LeftIcon={IconLayoutList}
|
LeftIcon={IconLayoutList}
|
||||||
text={
|
text="Group by"
|
||||||
!recordGroupFieldMetadata
|
contextualText={recordGroupFieldMetadata?.label}
|
||||||
? 'Group by'
|
|
||||||
: `Group by "${recordGroupFieldMetadata.label}"`
|
|
||||||
}
|
|
||||||
hasSubMenu
|
hasSubMenu
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => onContentChange('recordGroupSort')}
|
onClick={() => onContentChange('recordGroupSort')}
|
||||||
LeftIcon={IconSortDescending}
|
LeftIcon={IconSortDescending}
|
||||||
text="Sort"
|
text="Sort"
|
||||||
|
contextualText={recordGroupSort}
|
||||||
hasSubMenu
|
hasSubMenu
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -115,9 +124,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
|||||||
<RecordGroupsVisibilityDropdownSection
|
<RecordGroupsVisibilityDropdownSection
|
||||||
title="Visible groups"
|
title="Visible groups"
|
||||||
recordGroupIds={visibleRecordGroupIds}
|
recordGroupIds={visibleRecordGroupIds}
|
||||||
onDragEnd={handleRecordGroupOrderChange}
|
onDragEnd={handleRecordGroupOrderChangeWithModal}
|
||||||
onVisibilityChange={handleRecordGroupVisibilityChange}
|
onVisibilityChange={handleRecordGroupVisibilityChange}
|
||||||
isDraggable={isDragableSortRecordGroup}
|
isDraggable={true}
|
||||||
showDragGrip={true}
|
showDragGrip={true}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -134,6 +143,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<RecordGroupReorderConfirmationModal
|
||||||
|
onConfirmClick={handleRecordGroupReorderConfirmClick}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoar
|
|||||||
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
|
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
|
||||||
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
|
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
@ -26,8 +26,9 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
|
|||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -64,8 +65,9 @@ export const RecordBoard = () => {
|
|||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
const boardRef = useRef<HTMLDivElement>(null);
|
const boardRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
|
ViewType.Kanban,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordIndexRecordIdsByGroupFamilyState =
|
const recordIndexRecordIdsByGroupFamilyState =
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { RecordBoardColumnHeaderWrapper } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper';
|
import { RecordBoardColumnHeaderWrapper } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
const StyledHeaderContainer = styled.div`
|
const StyledHeaderContainer = styled.div`
|
||||||
@ -23,8 +24,9 @@ const StyledHeaderContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordBoardHeader = () => {
|
export const RecordBoardHeader = () => {
|
||||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
|
ViewType.Kanban,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -2,18 +2,19 @@ import { useRecoilCallback } from 'recoil';
|
|||||||
|
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { sortRecordsByPosition } from '@/object-record/utils/sortRecordsByPosition';
|
import { sortRecordsByPosition } from '@/object-record/utils/sortRecordsByPosition';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
||||||
const visibleRecordGroupIdsSelector = useRecoilComponentCallbackStateV2(
|
const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordGroupFieldMetadataState = useRecoilComponentCallbackStateV2(
|
const recordGroupFieldMetadataState = useRecoilComponentCallbackStateV2(
|
||||||
@ -32,7 +33,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
|||||||
(records: ObjectRecord[]) => {
|
(records: ObjectRecord[]) => {
|
||||||
const recordGroupIds = getSnapshotValue(
|
const recordGroupIds = getSnapshotValue(
|
||||||
snapshot,
|
snapshot,
|
||||||
visibleRecordGroupIdsSelector,
|
visibleRecordGroupIdsFamilySelector(ViewType.Kanban),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const recordGroupId of recordGroupIds) {
|
for (const recordGroupId of recordGroupIds) {
|
||||||
@ -72,7 +73,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
visibleRecordGroupIdsSelector,
|
visibleRecordGroupIdsFamilySelector,
|
||||||
recordIndexRecordIdsByGroupFamilyState,
|
recordIndexRecordIdsByGroupFamilyState,
|
||||||
recordGroupFieldMetadataState,
|
recordGroupFieldMetadataState,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -4,9 +4,10 @@ import { useCallback, useRef } from 'react';
|
|||||||
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';
|
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';
|
||||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
|
||||||
import { MenuItem } from 'twenty-ui';
|
|
||||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||||
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
import { MenuItem } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledMenuContainer = styled.div`
|
const StyledMenuContainer = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -27,7 +28,9 @@ export const RecordBoardColumnDropdownMenu = ({
|
|||||||
}: RecordBoardColumnDropdownMenuProps) => {
|
}: RecordBoardColumnDropdownMenuProps) => {
|
||||||
const boardColumnMenuRef = useRef<HTMLDivElement>(null);
|
const boardColumnMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const recordGroupActions = useRecordGroupActions();
|
const recordGroupActions = useRecordGroupActions({
|
||||||
|
viewType: ViewType.Kanban,
|
||||||
|
});
|
||||||
|
|
||||||
const closeMenu = useCallback(() => {
|
const closeMenu = useCallback(() => {
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { isRecordGroupReorderConfirmationModalVisibleState } from '@/object-record/record-group/states/isRecordGroupReorderConfirmationModalVisibleState';
|
||||||
|
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||||
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
type RecordGroupReorderConfirmationModalProps = {
|
||||||
|
onConfirmClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordGroupReorderConfirmationModal = ({
|
||||||
|
onConfirmClick,
|
||||||
|
}: RecordGroupReorderConfirmationModalProps) => {
|
||||||
|
const [
|
||||||
|
isRecordGroupReorderConfirmationModalVisible,
|
||||||
|
setIsRecordGroupReorderConfirmationModalVisible,
|
||||||
|
] = useRecoilState(isRecordGroupReorderConfirmationModalVisibleState);
|
||||||
|
|
||||||
|
const recordGroupSort = useRecoilComponentValueV2(
|
||||||
|
recordIndexRecordGroupSortComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isRecordGroupReorderConfirmationModalVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<ConfirmationModal
|
||||||
|
isOpen={isRecordGroupReorderConfirmationModalVisible}
|
||||||
|
setIsOpen={setIsRecordGroupReorderConfirmationModalVisible}
|
||||||
|
title="Group sorting"
|
||||||
|
subtitle={`Would you like to remove ${recordGroupSort} group sorting ?`}
|
||||||
|
onConfirmClick={onConfirmClick}
|
||||||
|
deleteButtonText="Remove"
|
||||||
|
/>,
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -8,12 +8,19 @@ import { RecordGroupAction } from '@/object-record/record-group/types/RecordGrou
|
|||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useCallback, useContext, useMemo } from 'react';
|
import { useCallback, useContext, useMemo } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import { IconEyeOff, IconSettings, isDefined } from 'twenty-ui';
|
import { IconEyeOff, IconSettings, isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
export const useRecordGroupActions = () => {
|
type UseRecordGroupActionsParams = {
|
||||||
|
viewType: ViewType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRecordGroupActions = ({
|
||||||
|
viewType,
|
||||||
|
}: UseRecordGroupActionsParams) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@ -34,6 +41,7 @@ export const useRecordGroupActions = () => {
|
|||||||
const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
|
const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
|
||||||
useRecordGroupVisibility({
|
useRecordGroupVisibility({
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
|
viewType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setNavigationMemorizedUrl = useSetRecoilState(
|
const setNavigationMemorizedUrl = useSetRecoilState(
|
||||||
|
|||||||
@ -2,11 +2,12 @@ import { OnDragEndResponder } from '@hello-pangea/dnd';
|
|||||||
|
|
||||||
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
|
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
|
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { mapRecordGroupDefinitionsToViewGroups } from '@/views/utils/mapRecordGroupDefinitionsToViewGroups';
|
import { mapRecordGroupDefinitionsToViewGroups } from '@/views/utils/mapRecordGroupDefinitionsToViewGroups';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||||
@ -15,15 +16,17 @@ import { isDefined } from '~/utils/isDefined';
|
|||||||
|
|
||||||
type UseRecordGroupHandlersParams = {
|
type UseRecordGroupHandlersParams = {
|
||||||
viewBarId: string;
|
viewBarId: string;
|
||||||
|
viewType: ViewType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRecordGroupReorder = ({
|
export const useRecordGroupReorder = ({
|
||||||
viewBarId,
|
viewBarId,
|
||||||
|
viewType,
|
||||||
}: UseRecordGroupHandlersParams) => {
|
}: UseRecordGroupHandlersParams) => {
|
||||||
const setRecordGroup = useSetRecordGroup(viewBarId);
|
const setRecordGroup = useSetRecordGroup(viewBarId);
|
||||||
|
|
||||||
const visibleRecordGroupIdsSelector = useRecoilComponentCallbackStateV2(
|
const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);
|
const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);
|
||||||
@ -37,7 +40,7 @@ export const useRecordGroupReorder = ({
|
|||||||
|
|
||||||
const visibleRecordGroupIds = getSnapshotValue(
|
const visibleRecordGroupIds = getSnapshotValue(
|
||||||
snapshot,
|
snapshot,
|
||||||
visibleRecordGroupIdsSelector,
|
visibleRecordGroupIdsFamilySelector(viewType),
|
||||||
);
|
);
|
||||||
|
|
||||||
const reorderedVisibleRecordGroupIds = moveArrayItem(
|
const reorderedVisibleRecordGroupIds = moveArrayItem(
|
||||||
@ -80,7 +83,12 @@ export const useRecordGroupReorder = ({
|
|||||||
mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups),
|
mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[saveViewGroups, setRecordGroup, visibleRecordGroupIdsSelector],
|
[
|
||||||
|
saveViewGroups,
|
||||||
|
setRecordGroup,
|
||||||
|
viewType,
|
||||||
|
visibleRecordGroupIdsFamilySelector,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
import { useRecordGroupReorder } from '@/object-record/record-group/hooks/useRecordGroupReorder';
|
||||||
|
import { isRecordGroupReorderConfirmationModalVisibleState } from '@/object-record/record-group/states/isRecordGroupReorderConfirmationModalVisibleState';
|
||||||
|
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||||
|
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||||
|
import { recordIndexRecordGroupIsDraggableSortComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexRecordGroupIsDraggableSortComponentSelector';
|
||||||
|
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||||
|
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
type UseRecordGroupReorderConfirmationModalParams = {
|
||||||
|
recordIndexId: string;
|
||||||
|
viewType: ViewType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRecordGroupReorderConfirmationModal = ({
|
||||||
|
recordIndexId,
|
||||||
|
viewType,
|
||||||
|
}: UseRecordGroupReorderConfirmationModalParams) => {
|
||||||
|
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||||
|
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||||
|
const { goBackToPreviousDropdownFocusId } =
|
||||||
|
useGoBackToPreviousDropdownFocusId();
|
||||||
|
|
||||||
|
const setIsRecordGroupReorderConfirmationModalVisible = useSetRecoilState(
|
||||||
|
isRecordGroupReorderConfirmationModalVisibleState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [pendingDragEndReorder, setPendingDragEndReorder] =
|
||||||
|
useState<Parameters<OnDragEndResponder> | null>(null);
|
||||||
|
|
||||||
|
const { handleOrderChange: handleRecordGroupOrderChange } =
|
||||||
|
useRecordGroupReorder({
|
||||||
|
viewBarId: recordIndexId,
|
||||||
|
viewType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDragableSortRecordGroup = useRecoilComponentValueV2(
|
||||||
|
recordIndexRecordGroupIsDraggableSortComponentSelector,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setRecordGroupSort = useSetRecoilComponentStateV2(
|
||||||
|
recordIndexRecordGroupSortComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRecordGroupOrderChangeWithModal: OnDragEndResponder = (
|
||||||
|
result,
|
||||||
|
provided,
|
||||||
|
) => {
|
||||||
|
if (!isDragableSortRecordGroup) {
|
||||||
|
setIsRecordGroupReorderConfirmationModalVisible(true);
|
||||||
|
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
||||||
|
setPendingDragEndReorder([result, provided]);
|
||||||
|
} else {
|
||||||
|
handleRecordGroupOrderChange(result, provided);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmClick = () => {
|
||||||
|
if (!pendingDragEndReorder) {
|
||||||
|
throw new Error('pendingDragEndReorder is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
setRecordGroupSort(RecordGroupSort.Manual);
|
||||||
|
setPendingDragEndReorder(null);
|
||||||
|
handleRecordGroupOrderChange(...pendingDragEndReorder);
|
||||||
|
goBackToPreviousDropdownFocusId();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleRecordGroupOrderChangeWithModal,
|
||||||
|
handleRecordGroupReorderConfirmClick: handleConfirmClick,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,20 +1,25 @@
|
|||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { recordIndexRecordGroupHideComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentState';
|
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
|
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { recordGroupDefinitionToViewGroup } from '@/views/utils/recordGroupDefinitionToViewGroup';
|
import { recordGroupDefinitionToViewGroup } from '@/views/utils/recordGroupDefinitionToViewGroup';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
type UseRecordGroupVisibilityParams = {
|
type UseRecordGroupVisibilityParams = {
|
||||||
viewBarId: string;
|
viewBarId: string;
|
||||||
|
viewType: ViewType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRecordGroupVisibility = ({
|
export const useRecordGroupVisibility = ({
|
||||||
viewBarId,
|
viewBarId,
|
||||||
|
viewType,
|
||||||
}: UseRecordGroupVisibilityParams) => {
|
}: UseRecordGroupVisibilityParams) => {
|
||||||
const objectOptionsDropdownRecordGroupHideState =
|
const objectOptionsDropdownRecordGroupHideFamilyState =
|
||||||
useRecoilComponentCallbackStateV2(recordIndexRecordGroupHideComponentState);
|
useRecoilComponentCallbackStateV2(
|
||||||
|
recordIndexRecordGroupHideComponentFamilyState,
|
||||||
|
);
|
||||||
|
|
||||||
const { saveViewGroup } = useSaveCurrentViewGroups(viewBarId);
|
const { saveViewGroup } = useSaveCurrentViewGroups(viewBarId);
|
||||||
|
|
||||||
@ -27,22 +32,19 @@ export const useRecordGroupVisibility = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
saveViewGroup(recordGroupDefinitionToViewGroup(updatedRecordGroup));
|
saveViewGroup(recordGroupDefinitionToViewGroup(updatedRecordGroup));
|
||||||
|
|
||||||
// If visibility is manually toggled, we should reset the hideEmptyRecordGroup state
|
|
||||||
set(objectOptionsDropdownRecordGroupHideState, false);
|
|
||||||
},
|
},
|
||||||
[saveViewGroup, objectOptionsDropdownRecordGroupHideState],
|
[saveViewGroup],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleHideEmptyRecordGroupChange = useRecoilCallback(
|
const handleHideEmptyRecordGroupChange = useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
async () => {
|
async () => {
|
||||||
set(
|
set(
|
||||||
objectOptionsDropdownRecordGroupHideState,
|
objectOptionsDropdownRecordGroupHideFamilyState(viewType),
|
||||||
(currentHideState) => !currentHideState,
|
(currentHideState) => !currentHideState,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[objectOptionsDropdownRecordGroupHideState],
|
[viewType, objectOptionsDropdownRecordGroupHideFamilyState],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const isRecordGroupReorderConfirmationModalVisibleState = atom<boolean>({
|
||||||
|
key: 'isRecordGroupReorderConfirmationModalVisibleState',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const recordGroupPendingDragEndReorderState =
|
||||||
|
atom<Parameters<OnDragEndResponder> | null>({
|
||||||
|
key: 'recordGroupPendingDragEndReorderState',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
|
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||||
|
import {
|
||||||
|
RecordGroupDefinition,
|
||||||
|
RecordGroupDefinitionType,
|
||||||
|
} from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
|
import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
|
||||||
|
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||||
|
|
||||||
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const availableRecordGroupIdsComponentSelector =
|
||||||
|
createComponentSelectorV2<RecordGroupDefinition['id'][]>({
|
||||||
|
key: 'availableRecordGroupIdsComponentSelector',
|
||||||
|
componentInstanceContext: ViewComponentInstanceContext,
|
||||||
|
get:
|
||||||
|
({ instanceId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const recordGroupIds = get(
|
||||||
|
recordGroupIdsComponentState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result: RecordGroupDefinition[] = [];
|
||||||
|
|
||||||
|
for (const recordGroupId of recordGroupIds) {
|
||||||
|
const recordGroupDefinition = get(
|
||||||
|
recordGroupDefinitionFamilyState(recordGroupId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(recordGroupDefinition)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
recordGroupDefinition.type === RecordGroupDefinitionType.NoValue
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordGroupSortedInsert(result, recordGroupDefinition, (a, b) =>
|
||||||
|
a.title.localeCompare(b.title),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map(({ id }) => id);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
|
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||||
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
|
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||||
|
import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
|
||||||
|
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||||
|
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||||
|
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||||
|
import { createComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2';
|
||||||
|
|
||||||
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const visibleRecordGroupIdsComponentFamilySelector =
|
||||||
|
createComponentFamilySelectorV2<RecordGroupDefinition['id'][], ViewType>({
|
||||||
|
key: 'visibleRecordGroupIdsComponentFamilySelector',
|
||||||
|
componentInstanceContext: ViewComponentInstanceContext,
|
||||||
|
get:
|
||||||
|
({ instanceId, familyKey }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const recordGroupSort = get(
|
||||||
|
recordIndexRecordGroupSortComponentState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const recordGroupIds = get(
|
||||||
|
recordGroupIdsComponentState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const hideEmptyRecordGroup = get(
|
||||||
|
recordIndexRecordGroupHideComponentFamilyState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
familyKey,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result: RecordGroupDefinition[] = [];
|
||||||
|
|
||||||
|
const comparator = (
|
||||||
|
a: RecordGroupDefinition,
|
||||||
|
b: RecordGroupDefinition,
|
||||||
|
) => {
|
||||||
|
switch (recordGroupSort) {
|
||||||
|
case RecordGroupSort.Alphabetical:
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
case RecordGroupSort.ReverseAlphabetical:
|
||||||
|
return b.title.localeCompare(a.title);
|
||||||
|
case RecordGroupSort.Manual:
|
||||||
|
default:
|
||||||
|
return a.position - b.position;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const recordGroupId of recordGroupIds) {
|
||||||
|
const recordGroupDefinition = get(
|
||||||
|
recordGroupDefinitionFamilyState(recordGroupId),
|
||||||
|
);
|
||||||
|
const recordIds = get(
|
||||||
|
recordIndexRecordIdsByGroupComponentFamilyState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
familyKey: recordGroupId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(recordGroupDefinition)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideEmptyRecordGroup && recordIds.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recordGroupDefinition.isVisible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordGroupSortedInsert(result, recordGroupDefinition, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map(({ id }) => id);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,83 +0,0 @@
|
|||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
|
||||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
|
||||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
|
||||||
import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
|
|
||||||
import { recordIndexRecordGroupHideComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentState';
|
|
||||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
|
||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
|
||||||
|
|
||||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
|
||||||
|
|
||||||
export const visibleRecordGroupIdsComponentSelector = createComponentSelectorV2<
|
|
||||||
RecordGroupDefinition['id'][]
|
|
||||||
>({
|
|
||||||
key: 'visibleRecordGroupIdsComponentSelector',
|
|
||||||
componentInstanceContext: ViewComponentInstanceContext,
|
|
||||||
get:
|
|
||||||
({ instanceId }) =>
|
|
||||||
({ get }) => {
|
|
||||||
const recordGroupSort = get(
|
|
||||||
recordIndexRecordGroupSortComponentState.atomFamily({
|
|
||||||
instanceId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const recordGroupIds = get(
|
|
||||||
recordGroupIdsComponentState.atomFamily({
|
|
||||||
instanceId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const hideEmptyRecordGroup = get(
|
|
||||||
recordIndexRecordGroupHideComponentState.atomFamily({
|
|
||||||
instanceId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const result: RecordGroupDefinition[] = [];
|
|
||||||
|
|
||||||
const comparator = (
|
|
||||||
a: RecordGroupDefinition,
|
|
||||||
b: RecordGroupDefinition,
|
|
||||||
) => {
|
|
||||||
switch (recordGroupSort) {
|
|
||||||
case RecordGroupSort.Alphabetical:
|
|
||||||
return a.title.localeCompare(b.title);
|
|
||||||
case RecordGroupSort.ReverseAlphabetical:
|
|
||||||
return b.title.localeCompare(a.title);
|
|
||||||
case RecordGroupSort.Manual:
|
|
||||||
default:
|
|
||||||
return a.position - b.position;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const recordGroupId of recordGroupIds) {
|
|
||||||
const recordGroupDefinition = get(
|
|
||||||
recordGroupDefinitionFamilyState(recordGroupId),
|
|
||||||
);
|
|
||||||
const recordIds = get(
|
|
||||||
recordIndexRecordIdsByGroupComponentFamilyState.atomFamily({
|
|
||||||
instanceId,
|
|
||||||
familyKey: recordGroupId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isDefined(recordGroupDefinition)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hideEmptyRecordGroup && recordIds.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!recordGroupDefinition.isVisible) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
recordGroupSortedInsert(result, recordGroupDefinition, comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.map(({ id }) => id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { availableRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/availableRecordGroupIdsComponentSelector';
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
@ -8,18 +8,27 @@ import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/
|
|||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
export const RecordIndexPageTableAddButtonInGroup = () => {
|
type RecordIndexAddRecordInGroupDropdownProps = {
|
||||||
const dropdownId = `record-index-page-table-add-button-dropdown`;
|
dropdownId: string;
|
||||||
|
clickableComponent: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordIndexAddRecordInGroupDropdown = ({
|
||||||
|
dropdownId,
|
||||||
|
clickableComponent,
|
||||||
|
}: RecordIndexAddRecordInGroupDropdownProps) => {
|
||||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||||
|
|
||||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||||
visibleRecordGroupIdsComponentSelector,
|
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||||
|
|
||||||
|
const recordGroupIds = useRecoilComponentValueV2(
|
||||||
|
availableRecordGroupIdsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||||
@ -44,11 +53,13 @@ export const RecordIndexPageTableAddButtonInGroup = () => {
|
|||||||
(recordGroup: RecordGroupDefinition) => {
|
(recordGroup: RecordGroupDefinition) => {
|
||||||
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
|
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
|
||||||
createNewTableRecordInGroup(recordGroup.id);
|
createNewTableRecordInGroup(recordGroup.id);
|
||||||
|
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
closeDropdown,
|
closeDropdown,
|
||||||
createNewTableRecordInGroup,
|
createNewTableRecordInGroup,
|
||||||
|
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||||
isRecordGroupTableSectionToggledState,
|
isRecordGroupTableSectionToggledState,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -61,11 +72,11 @@ export const RecordIndexPageTableAddButtonInGroup = () => {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownMenuWidth="200px"
|
dropdownMenuWidth="200px"
|
||||||
dropdownPlacement="bottom-start"
|
dropdownPlacement="bottom-start"
|
||||||
clickableComponent={<PageAddButton />}
|
clickableComponent={clickableComponent}
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{visibleRecordGroupIds.map((recordGroupId) => (
|
{recordGroupIds.map((recordGroupId) => (
|
||||||
<RecordIndexPageKanbanAddMenuItem
|
<RecordIndexPageKanbanAddMenuItem
|
||||||
key={recordGroupId}
|
key={recordGroupId}
|
||||||
columnId={recordGroupId}
|
columnId={recordGroupId}
|
||||||
@ -2,7 +2,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
||||||
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
||||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
@ -11,7 +11,9 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
@ -20,8 +22,9 @@ export const RecordIndexPageKanbanAddButton = () => {
|
|||||||
|
|
||||||
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
|
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||||
|
|
||||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
|
ViewType.Kanban,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordIndexKanbanFieldMetadataId = useRecoilValue(
|
const recordIndexKanbanFieldMetadataId = useRecoilValue(
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||||
import { RecordIndexPageTableAddButtonInGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonInGroup';
|
import { RecordIndexAddRecordInGroupDropdown } from '@/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown';
|
||||||
import { RecordIndexPageTableAddButtonNoGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonNoGroup';
|
import { RecordIndexPageTableAddButtonNoGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonNoGroup';
|
||||||
|
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordIndexPageTableAddButton = () => {
|
export const RecordIndexPageTableAddButton = () => {
|
||||||
@ -12,5 +13,10 @@ export const RecordIndexPageTableAddButton = () => {
|
|||||||
return <RecordIndexPageTableAddButtonNoGroup />;
|
return <RecordIndexPageTableAddButtonNoGroup />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <RecordIndexPageTableAddButtonInGroup />;
|
return (
|
||||||
|
<RecordIndexAddRecordInGroupDropdown
|
||||||
|
dropdownId="record-index-page-table-add-button-dropdown"
|
||||||
|
clickableComponent={<PageAddButton />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -61,6 +61,8 @@ export const useHandleRecordGroupField = ({
|
|||||||
(option) =>
|
(option) =>
|
||||||
!existingGroupKeys.has(`${fieldMetadataItem.id}:${option.value}`),
|
!existingGroupKeys.has(`${fieldMetadataItem.id}:${option.value}`),
|
||||||
)
|
)
|
||||||
|
// Alphabetically sort the options by default
|
||||||
|
.sort((a, b) => a.value.localeCompare(b.value))
|
||||||
.map(
|
.map(
|
||||||
(option, index) =>
|
(option, index) =>
|
||||||
({
|
({
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||||
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
|
||||||
|
export const recordIndexRecordGroupHideComponentFamilyState =
|
||||||
|
createComponentFamilyStateV2<boolean, ViewType>({
|
||||||
|
key: 'recordIndexRecordGroupHideComponentFamilyState',
|
||||||
|
defaultValue: ({ familyKey }) => {
|
||||||
|
switch (familyKey) {
|
||||||
|
case ViewType.Kanban:
|
||||||
|
return false;
|
||||||
|
case ViewType.Table:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentInstanceContext: ViewComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
|
||||||
|
|
||||||
export const recordIndexRecordGroupHideComponentState =
|
|
||||||
createComponentStateV2<boolean>({
|
|
||||||
key: 'recordIndexRecordGroupHideComponentState',
|
|
||||||
defaultValue: false,
|
|
||||||
componentInstanceContext: ViewComponentInstanceContext,
|
|
||||||
});
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
@ -16,7 +16,7 @@ import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record
|
|||||||
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
||||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
import { hasPendingRecordComponentSelector } from '@/object-record/record-table/states/selectors/hasPendingRecordComponentSelector';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
@ -54,8 +54,8 @@ export const RecordTable = () => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const pendingRecordId = useRecoilComponentValueV2(
|
const hasPendingRecord = useRecoilComponentValueV2(
|
||||||
recordTablePendingRecordIdComponentState,
|
hasPendingRecordComponentSelector,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ export const RecordTable = () => {
|
|||||||
const recordTableIsEmpty =
|
const recordTableIsEmpty =
|
||||||
!isRecordTableInitialLoading &&
|
!isRecordTableInitialLoading &&
|
||||||
allRecordIds.length === 0 &&
|
allRecordIds.length === 0 &&
|
||||||
isNull(pendingRecordId);
|
!hasPendingRecord;
|
||||||
|
|
||||||
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll';
|
import { RecordTableEmptyStateByGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateByGroupNoRecordAtAll';
|
||||||
|
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
||||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||||
@ -11,6 +13,10 @@ export const RecordTableEmptyState = () => {
|
|||||||
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||||
useRecordTableContextOrThrow();
|
useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const hasRecordGroups = useRecoilComponentValueV2(
|
||||||
|
hasRecordGroupsComponentSelector,
|
||||||
|
);
|
||||||
|
|
||||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||||
const noRecordAtAll = totalCount === 0;
|
const noRecordAtAll = totalCount === 0;
|
||||||
|
|
||||||
@ -26,7 +32,11 @@ export const RecordTableEmptyState = () => {
|
|||||||
} else if (isSoftDeleteActive === true) {
|
} else if (isSoftDeleteActive === true) {
|
||||||
return <RecordTableEmptyStateSoftDelete />;
|
return <RecordTableEmptyStateSoftDelete />;
|
||||||
} else if (noRecordAtAll) {
|
} else if (noRecordAtAll) {
|
||||||
return <RecordTableEmptyStateNoRecordAtAll />;
|
if (hasRecordGroups) {
|
||||||
|
return <RecordTableEmptyStateByGroupNoRecordAtAll />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <RecordTableEmptyStateNoGroupNoRecordAtAll />;
|
||||||
} else {
|
} else {
|
||||||
return <RecordTableEmptyStateNoRecordFoundForFilter />;
|
return <RecordTableEmptyStateNoRecordFoundForFilter />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { Button, IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
|
||||||
|
import { RecordIndexAddRecordInGroupDropdown } from '@/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown';
|
||||||
|
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||||
|
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
|
||||||
|
export const RecordTableEmptyStateByGroupNoRecordAtAll = () => {
|
||||||
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const setHideEmptyRecordGroup = useSetRecoilComponentFamilyStateV2(
|
||||||
|
recordIndexRecordGroupHideComponentFamilyState,
|
||||||
|
ViewType.Table,
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectLabel = useObjectLabel(objectMetadataItem);
|
||||||
|
|
||||||
|
const buttonTitle = `Add a ${objectLabel}`;
|
||||||
|
|
||||||
|
const title = `Add your first ${objectLabel}`;
|
||||||
|
|
||||||
|
const subTitle = `Use our API or add your first ${objectLabel} manually`;
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
// When we have no records in the group, we want to show the empty state
|
||||||
|
setHideEmptyRecordGroup(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordTableEmptyStateDisplay
|
||||||
|
title={title}
|
||||||
|
subTitle={subTitle}
|
||||||
|
animatedPlaceholderType="noRecord"
|
||||||
|
buttonComponent={
|
||||||
|
<RecordIndexAddRecordInGroupDropdown
|
||||||
|
dropdownId="record-table-empty-state-add-button-dropdown"
|
||||||
|
clickableComponent={
|
||||||
|
<Button
|
||||||
|
Icon={IconPlus}
|
||||||
|
title={buttonTitle}
|
||||||
|
variant={'secondary'}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -11,41 +11,49 @@ import {
|
|||||||
IconComponent,
|
IconComponent,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
type RecordTableEmptyStateDisplayProps = {
|
type RecordTableEmptyStateDisplayButtonComponentProps = {
|
||||||
animatedPlaceholderType: AnimatedPlaceholderType;
|
buttonComponent?: React.ReactNode;
|
||||||
title: string;
|
};
|
||||||
subTitle: string;
|
|
||||||
Icon: IconComponent;
|
type RecordTableEmptyStateDisplayButtonProps = {
|
||||||
|
ButtonIcon: IconComponent;
|
||||||
buttonTitle: string;
|
buttonTitle: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableEmptyStateDisplay = ({
|
type RecordTableEmptyStateDisplayProps = {
|
||||||
Icon,
|
animatedPlaceholderType: AnimatedPlaceholderType;
|
||||||
animatedPlaceholderType,
|
title: string;
|
||||||
buttonTitle,
|
subTitle: string;
|
||||||
onClick,
|
} & (
|
||||||
subTitle,
|
| RecordTableEmptyStateDisplayButtonComponentProps
|
||||||
title,
|
| RecordTableEmptyStateDisplayButtonProps
|
||||||
}: RecordTableEmptyStateDisplayProps) => {
|
);
|
||||||
|
|
||||||
|
export const RecordTableEmptyStateDisplay = (
|
||||||
|
props: RecordTableEmptyStateDisplayProps,
|
||||||
|
) => {
|
||||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer>
|
||||||
<AnimatedPlaceholder type={animatedPlaceholderType} />
|
<AnimatedPlaceholder type={props.animatedPlaceholderType} />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>{title}</AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
{props.title}
|
||||||
|
</AnimatedPlaceholderEmptyTitle>
|
||||||
<AnimatedPlaceholderEmptySubTitle>
|
<AnimatedPlaceholderEmptySubTitle>
|
||||||
{subTitle}
|
{props.subTitle}
|
||||||
</AnimatedPlaceholderEmptySubTitle>
|
</AnimatedPlaceholderEmptySubTitle>
|
||||||
</AnimatedPlaceholderEmptyTextContainer>
|
</AnimatedPlaceholderEmptyTextContainer>
|
||||||
{!isReadOnly && (
|
{'buttonComponent' in props && props.buttonComponent}
|
||||||
|
{'buttonTitle' in props && !isReadOnly && (
|
||||||
<Button
|
<Button
|
||||||
Icon={Icon}
|
Icon={props.ButtonIcon}
|
||||||
title={buttonTitle}
|
title={props.buttonTitle}
|
||||||
variant={'secondary'}
|
variant={'secondary'}
|
||||||
onClick={onClick}
|
onClick={props.onClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatedPlaceholderEmptyContainer>
|
</AnimatedPlaceholderEmptyContainer>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
|||||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
||||||
|
|
||||||
export const RecordTableEmptyStateNoRecordAtAll = () => {
|
export const RecordTableEmptyStateNoGroupNoRecordAtAll = () => {
|
||||||
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
|
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
|
const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
|
||||||
@ -27,7 +27,7 @@ export const RecordTableEmptyStateNoRecordAtAll = () => {
|
|||||||
buttonTitle={buttonTitle}
|
buttonTitle={buttonTitle}
|
||||||
subTitle={subTitle}
|
subTitle={subTitle}
|
||||||
title={title}
|
title={title}
|
||||||
Icon={IconPlus}
|
ButtonIcon={IconPlus}
|
||||||
animatedPlaceholderType="noRecord"
|
animatedPlaceholderType="noRecord"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
/>
|
/>
|
||||||
@ -27,7 +27,7 @@ export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
|
|||||||
buttonTitle={buttonTitle}
|
buttonTitle={buttonTitle}
|
||||||
subTitle={subTitle}
|
subTitle={subTitle}
|
||||||
title={title}
|
title={title}
|
||||||
Icon={IconPlus}
|
ButtonIcon={IconPlus}
|
||||||
animatedPlaceholderType="noMatchRecord"
|
animatedPlaceholderType="noMatchRecord"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const RecordTableEmptyStateRemote = () => {
|
|||||||
buttonTitle={'Go to Settings'}
|
buttonTitle={'Go to Settings'}
|
||||||
subTitle={'If this is unexpected, please verify your settings.'}
|
subTitle={'If this is unexpected, please verify your settings.'}
|
||||||
title={'No Data Available for Remote Table'}
|
title={'No Data Available for Remote Table'}
|
||||||
Icon={IconSettings}
|
ButtonIcon={IconSettings}
|
||||||
animatedPlaceholderType="noRecord"
|
animatedPlaceholderType="noRecord"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export const RecordTableEmptyStateSoftDelete = () => {
|
|||||||
buttonTitle={'Remove Deleted filter'}
|
buttonTitle={'Remove Deleted filter'}
|
||||||
subTitle={'No deleted records matching the filter criteria were found.'}
|
subTitle={'No deleted records matching the filter criteria were found.'}
|
||||||
title={`No Deleted ${objectLabel} found`}
|
title={`No Deleted ${objectLabel} found`}
|
||||||
Icon={IconFilterOff}
|
ButtonIcon={IconFilterOff}
|
||||||
animatedPlaceholderType="noDeletedRecord"
|
animatedPlaceholderType="noDeletedRecord"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||||
import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll';
|
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||||
import { ComponentDecorator } from 'twenty-ui';
|
import { ComponentDecorator } from 'twenty-ui';
|
||||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
@ -10,8 +10,9 @@ import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator'
|
|||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoRecordAtAll',
|
title:
|
||||||
component: RecordTableEmptyStateNoRecordAtAll,
|
'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoGroupNoRecordAtAll',
|
||||||
|
component: RecordTableEmptyStateNoGroupNoRecordAtAll,
|
||||||
decorators: [
|
decorators: [
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
MemoryRouterDecorator,
|
MemoryRouterDecorator,
|
||||||
@ -34,6 +35,6 @@ const meta: Meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof RecordTableEmptyStateNoRecordAtAll>;
|
type Story = StoryObj<typeof RecordTableEmptyStateNoGroupNoRecordAtAll>;
|
||||||
|
|
||||||
export const Default: Story = {};
|
export const Default: Story = {};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { RecordTableRecordGroupBodyContextProvider } from '@/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider';
|
import { RecordTableRecordGroupBodyContextProvider } from '@/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider';
|
||||||
import { RecordTableRecordGroupRows } from '@/object-record/record-table/components/RecordTableRecordGroupRows';
|
import { RecordTableRecordGroupRows } from '@/object-record/record-table/components/RecordTableRecordGroupRows';
|
||||||
@ -7,10 +7,11 @@ import { RecordTableBodyDroppable } from '@/object-record/record-table/record-ta
|
|||||||
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
|
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 { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider';
|
||||||
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
||||||
import { RecordTableRecordGroupEmptyRow } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupEmptyRow';
|
|
||||||
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
|
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { FeatureFlagKey } from '~/generated/graphql';
|
import { FeatureFlagKey } from '~/generated/graphql';
|
||||||
|
|
||||||
@ -26,8 +27,9 @@ export const RecordTableRecordGroupsBody = () => {
|
|||||||
isRecordTableInitialLoadingComponentState,
|
isRecordTableInitialLoadingComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||||
visibleRecordGroupIdsComponentSelector,
|
visibleRecordGroupIdsComponentFamilySelector,
|
||||||
|
ViewType.Table,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isRecordTableInitialLoading && allRecordIds.length === 0) {
|
if (isRecordTableInitialLoading && allRecordIds.length === 0) {
|
||||||
@ -37,14 +39,13 @@ export const RecordTableRecordGroupsBody = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RecordTableBodyRecordGroupDragDropContextProvider>
|
<RecordTableBodyRecordGroupDragDropContextProvider>
|
||||||
{visibleRecordGroupIds.map((recordGroupId, index) => (
|
{visibleRecordGroupIds.map((recordGroupId) => (
|
||||||
<RecordTableRecordGroupBodyContextProvider
|
<RecordTableRecordGroupBodyContextProvider
|
||||||
key={recordGroupId}
|
key={recordGroupId}
|
||||||
recordGroupId={recordGroupId}
|
recordGroupId={recordGroupId}
|
||||||
>
|
>
|
||||||
<RecordGroupContext.Provider value={{ recordGroupId }}>
|
<RecordGroupContext.Provider value={{ recordGroupId }}>
|
||||||
<RecordTableBodyDroppable recordGroupId={recordGroupId}>
|
<RecordTableBodyDroppable recordGroupId={recordGroupId}>
|
||||||
{index > 0 && <RecordTableRecordGroupEmptyRow />}
|
|
||||||
<RecordTableRecordGroupSection />
|
<RecordTableRecordGroupSection />
|
||||||
<RecordTableRecordGroupRows />
|
<RecordTableRecordGroupRows />
|
||||||
</RecordTableBodyDroppable>
|
</RecordTableBodyDroppable>
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
const StyledTrContainer = styled.tr`
|
|
||||||
height: 32px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RecordTableRecordGroupEmptyRow = StyledTrContainer;
|
|
||||||
@ -2,7 +2,7 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { IconChevronUp, isDefined, Tag } from 'twenty-ui';
|
import { IconChevronDown, isDefined, Tag } from 'twenty-ui';
|
||||||
|
|
||||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
@ -83,13 +83,13 @@ export const RecordTableRecordGroupSection = () => {
|
|||||||
<td aria-hidden />
|
<td aria-hidden />
|
||||||
<StyledChevronContainer>
|
<StyledChevronContainer>
|
||||||
<motion.span
|
<motion.span
|
||||||
animate={{ rotate: isRecordGroupTableSectionToggled ? 180 : 0 }}
|
animate={{ rotate: !isRecordGroupTableSectionToggled ? -90 : 0 }}
|
||||||
transition={{ duration: theme.animation.duration.normal }}
|
transition={{ duration: theme.animation.duration.normal }}
|
||||||
style={{
|
style={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconChevronUp size={theme.icon.size.md} />
|
<IconChevronDown size={theme.icon.size.md} />
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</StyledChevronContainer>
|
</StyledChevronContainer>
|
||||||
<StyledRecordGroupSection>
|
<StyledRecordGroupSection>
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||||
|
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
||||||
|
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||||
|
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const hasPendingRecordComponentSelector = createComponentSelectorV2({
|
||||||
|
key: 'hasPendingRecordComponentSelector',
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
get:
|
||||||
|
({ instanceId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const hasRecordGroups = get(
|
||||||
|
hasRecordGroupsComponentSelector.selectorFamily({ instanceId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasRecordGroups) {
|
||||||
|
const pendingRecordId = get(
|
||||||
|
recordTablePendingRecordIdComponentState.atomFamily({ instanceId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return !isDefined(pendingRecordId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordGroupIds = get(
|
||||||
|
recordGroupIdsComponentState.atomFamily({ instanceId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const recordGroupId of recordGroupIds) {
|
||||||
|
const pendingRecordId = get(
|
||||||
|
recordTablePendingRecordIdByGroupComponentFamilyState.atomFamily({
|
||||||
|
instanceId,
|
||||||
|
familyKey: recordGroupId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(pendingRecordId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -11,6 +11,7 @@ type Params<V extends string> = {
|
|||||||
export const getSettingsPagePath = <Path extends SettingsPath>(
|
export const getSettingsPagePath = <Path extends SettingsPath>(
|
||||||
path: Path,
|
path: Path,
|
||||||
params?: Params<Path>,
|
params?: Params<Path>,
|
||||||
|
searchParams?: Record<string, string>,
|
||||||
) => {
|
) => {
|
||||||
let resultPath = `/settings/${path}`;
|
let resultPath = `/settings/${path}`;
|
||||||
|
|
||||||
@ -26,5 +27,11 @@ export const getSettingsPagePath = <Path extends SettingsPath>(
|
|||||||
resultPath = `${resultPath}/${params?.id}`;
|
resultPath = `${resultPath}/${params?.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDefined(searchParams)) {
|
||||||
|
const searchParamsString = new URLSearchParams(searchParams).toString();
|
||||||
|
|
||||||
|
resultPath = `${resultPath}?${searchParamsString}`;
|
||||||
|
}
|
||||||
|
|
||||||
return resultPath;
|
return resultPath;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export const DropdownMenuItemsContainer = ({
|
|||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="dropdownMenuItemsContainer"
|
contextProviderName="dropdownMenuItemsContainer"
|
||||||
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
||||||
|
heightMode="fit-content"
|
||||||
>
|
>
|
||||||
<StyledDropdownMenuItemsExternalContainer
|
<StyledDropdownMenuItemsExternalContainer
|
||||||
hasMaxHeight={hasMaxHeight}
|
hasMaxHeight={hasMaxHeight}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { previousDropdownFocusIdState } from '@/ui/layout/dropdown/states/previo
|
|||||||
export const useSetActiveDropdownFocusIdAndMemorizePrevious = () => {
|
export const useSetActiveDropdownFocusIdAndMemorizePrevious = () => {
|
||||||
const setActiveDropdownFocusIdAndMemorizePrevious = useRecoilCallback(
|
const setActiveDropdownFocusIdAndMemorizePrevious = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
(dropdownId: string) => {
|
(dropdownId: string | null) => {
|
||||||
const focusedDropdownId = snapshot
|
const focusedDropdownId = snapshot
|
||||||
.getLoadable(activeDropdownFocusIdState)
|
.getLoadable(activeDropdownFocusIdState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|||||||
@ -142,7 +142,9 @@ export const ConfirmationModal = ({
|
|||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
<StyledCenteredButton
|
<StyledCenteredButton
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
title="Cancel"
|
title="Cancel"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@ -15,9 +15,21 @@ import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/stat
|
|||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
|
||||||
const StyledScrollWrapper = styled.div<{ scrollHide?: boolean }>`
|
type HeightMode = 'full' | 'fit-content';
|
||||||
|
|
||||||
|
const StyledScrollWrapper = styled.div<{
|
||||||
|
scrollHide?: boolean;
|
||||||
|
heightMode: HeightMode;
|
||||||
|
}>`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: ${({ heightMode }) => {
|
||||||
|
switch (heightMode) {
|
||||||
|
case 'full':
|
||||||
|
return '100%';
|
||||||
|
case 'fit-content':
|
||||||
|
return 'fit-content';
|
||||||
|
}
|
||||||
|
}};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.os-scrollbar-handle {
|
.os-scrollbar-handle {
|
||||||
@ -33,6 +45,7 @@ const StyledInnerContainer = styled.div`
|
|||||||
export type ScrollWrapperProps = {
|
export type ScrollWrapperProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
heightMode?: HeightMode;
|
||||||
defaultEnableXScroll?: boolean;
|
defaultEnableXScroll?: boolean;
|
||||||
defaultEnableYScroll?: boolean;
|
defaultEnableYScroll?: boolean;
|
||||||
contextProviderName: ContextProviderName;
|
contextProviderName: ContextProviderName;
|
||||||
@ -44,6 +57,7 @@ export const ScrollWrapper = ({
|
|||||||
componentInstanceId,
|
componentInstanceId,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
heightMode = 'full',
|
||||||
defaultEnableXScroll = true,
|
defaultEnableXScroll = true,
|
||||||
defaultEnableYScroll = true,
|
defaultEnableYScroll = true,
|
||||||
contextProviderName,
|
contextProviderName,
|
||||||
@ -164,6 +178,7 @@ export const ScrollWrapper = ({
|
|||||||
ref={scrollableRef}
|
ref={scrollableRef}
|
||||||
className={className}
|
className={className}
|
||||||
scrollHide={scrollHide}
|
scrollHide={scrollHide}
|
||||||
|
heightMode={heightMode}
|
||||||
>
|
>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||||
</StyledScrollWrapper>
|
</StyledScrollWrapper>
|
||||||
|
|||||||
@ -2,7 +2,14 @@ import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/
|
|||||||
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
||||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||||
import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
|
import {
|
||||||
|
AtomEffect,
|
||||||
|
atomFamily,
|
||||||
|
Loadable,
|
||||||
|
RecoilValue,
|
||||||
|
SerializableParam,
|
||||||
|
WrappedValue,
|
||||||
|
} from 'recoil';
|
||||||
|
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
@ -11,7 +18,16 @@ type CreateComponentFamilyStateArgs<
|
|||||||
FamilyKey extends SerializableParam,
|
FamilyKey extends SerializableParam,
|
||||||
> = {
|
> = {
|
||||||
key: string;
|
key: string;
|
||||||
defaultValue: ValueType;
|
defaultValue:
|
||||||
|
| ValueType
|
||||||
|
| ((
|
||||||
|
param: ComponentFamilyStateKeyV2<FamilyKey>,
|
||||||
|
) =>
|
||||||
|
| ValueType
|
||||||
|
| RecoilValue<ValueType>
|
||||||
|
| Promise<ValueType>
|
||||||
|
| Loadable<ValueType>
|
||||||
|
| WrappedValue<ValueType>);
|
||||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||||
effects?:
|
effects?:
|
||||||
| AtomEffect<ValueType>[]
|
| AtomEffect<ValueType>[]
|
||||||
|
|||||||
@ -1,77 +1,43 @@
|
|||||||
import { useApolloClient } from '@apollo/client';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||||
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
|
import { useDestroyManyRecords } from '@/object-record/hooks/useDestroyManyRecords';
|
||||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||||
import { ViewGroup } from '@/views/types/ViewGroup';
|
import { ViewGroup } from '@/views/types/ViewGroup';
|
||||||
|
import { useApolloClient } from '@apollo/client';
|
||||||
|
|
||||||
export const usePersistViewGroupRecords = () => {
|
export const usePersistViewGroupRecords = () => {
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
const apolloClient = useApolloClient();
|
||||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { createOneRecordMutation } = useCreateOneRecordMutation({
|
const { createManyRecords } = useCreateManyRecords({
|
||||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||||
|
shouldMatchRootQueryFilter: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { updateOneRecordMutation } = useUpdateOneRecordMutation({
|
const { updateOneRecordMutation } = useUpdateOneRecordMutation({
|
||||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { deleteOneRecordMutation } = useDeleteOneRecordMutation({
|
const { destroyManyRecords } = useDestroyManyRecords({
|
||||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
|
||||||
|
|
||||||
const apolloClient = useApolloClient();
|
|
||||||
|
|
||||||
const createViewGroupRecords = useCallback(
|
const createViewGroupRecords = useCallback(
|
||||||
(viewGroupsToCreate: ViewGroup[], view: GraphQLView) => {
|
(viewGroupsToCreate: ViewGroup[], view: GraphQLView) => {
|
||||||
if (!viewGroupsToCreate.length) return;
|
if (!viewGroupsToCreate.length) return;
|
||||||
|
|
||||||
return Promise.all(
|
return createManyRecords(
|
||||||
viewGroupsToCreate.map((viewGroup) =>
|
viewGroupsToCreate.map((viewGroup) => ({
|
||||||
apolloClient.mutate({
|
...viewGroup,
|
||||||
mutation: createOneRecordMutation,
|
view: {
|
||||||
variables: {
|
id: view.id,
|
||||||
input: {
|
},
|
||||||
fieldMetadataId: viewGroup.fieldMetadataId,
|
})),
|
||||||
viewId: view.id,
|
|
||||||
isVisible: viewGroup.isVisible,
|
|
||||||
position: viewGroup.position,
|
|
||||||
id: v4(),
|
|
||||||
fieldValue: viewGroup.fieldValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: (cache, { data }) => {
|
|
||||||
const record = data?.['createViewGroup'];
|
|
||||||
if (!record) return;
|
|
||||||
|
|
||||||
triggerCreateRecordsOptimisticEffect({
|
|
||||||
cache,
|
|
||||||
objectMetadataItem,
|
|
||||||
recordsToCreate: [record],
|
|
||||||
objectMetadataItems,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[createManyRecords],
|
||||||
apolloClient,
|
|
||||||
createOneRecordMutation,
|
|
||||||
objectMetadataItem,
|
|
||||||
objectMetadataItems,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateViewGroupRecords = useCallback(
|
const updateViewGroupRecords = useCallback(
|
||||||
@ -95,7 +61,8 @@ export const usePersistViewGroupRecords = () => {
|
|||||||
|
|
||||||
const mutationResults = await Promise.all(mutationPromises);
|
const mutationResults = await Promise.all(mutationPromises);
|
||||||
|
|
||||||
// FixMe: Using triggerCreateRecordsOptimisticEffect is actaully causing multiple records to be created
|
// FixMe: Using useUpdateOneRecord hook that call triggerUpdateRecordsOptimisticEffect is actaully causing multiple records to be created
|
||||||
|
// This is a temporary fix
|
||||||
mutationResults.forEach(({ data }) => {
|
mutationResults.forEach(({ data }) => {
|
||||||
const record = data?.['updateViewGroup'];
|
const record = data?.['updateViewGroup'];
|
||||||
|
|
||||||
@ -120,33 +87,11 @@ export const usePersistViewGroupRecords = () => {
|
|||||||
async (viewGroupsToDelete: ViewGroup[]) => {
|
async (viewGroupsToDelete: ViewGroup[]) => {
|
||||||
if (!viewGroupsToDelete.length) return;
|
if (!viewGroupsToDelete.length) return;
|
||||||
|
|
||||||
const mutationPromises = viewGroupsToDelete.map((viewGroup) =>
|
return destroyManyRecords(
|
||||||
apolloClient.mutate<{ deleteViewGroup: ViewGroup }>({
|
viewGroupsToDelete.map((viewGroup) => viewGroup.id),
|
||||||
mutation: deleteOneRecordMutation,
|
|
||||||
variables: {
|
|
||||||
idToDelete: viewGroup.id,
|
|
||||||
},
|
|
||||||
// Avoid cache being updated with stale data
|
|
||||||
fetchPolicy: 'no-cache',
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const mutationResults = await Promise.all(mutationPromises);
|
|
||||||
|
|
||||||
mutationResults.forEach(({ data }) => {
|
|
||||||
const record = data?.['deleteViewGroup'];
|
|
||||||
|
|
||||||
if (!record) return;
|
|
||||||
|
|
||||||
apolloClient.cache.evict({
|
|
||||||
id: apolloClient.cache.identify({
|
|
||||||
__typename: 'ViewGroup',
|
|
||||||
id: record.id,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[apolloClient, deleteOneRecordMutation],
|
[destroyManyRecords],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ export enum FeatureFlagKey {
|
|||||||
IsMicrosoftSyncEnabled = 'IS_MICROSOFT_SYNC_ENABLED',
|
IsMicrosoftSyncEnabled = 'IS_MICROSOFT_SYNC_ENABLED',
|
||||||
IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED',
|
IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED',
|
||||||
IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED',
|
IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED',
|
||||||
IsViewGroupsEnabled = 'IS_VIEW_GROUPS_ENABLED',
|
|
||||||
IsPageHeaderV2Enabled = 'IS_PAGE_HEADER_V2_ENABLED',
|
IsPageHeaderV2Enabled = 'IS_PAGE_HEADER_V2_ENABLED',
|
||||||
IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED',
|
IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED',
|
||||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||||
|
|||||||
Reference in New Issue
Block a user