From 601e15f02819b2aa31b74211c10b013d7733c7b5 Mon Sep 17 00:00:00 2001 From: Pacifique LINJANJA Date: Wed, 18 Sep 2024 09:39:39 +0200 Subject: [PATCH] Feat(frontend): improve the soft delete empty state (#6877) # This PR - Fix #6834 ## Demo https://www.loom.com/share/235c4425f3264f429e2064a9d1604a90?sid=02a815c9-3b1a-45e6-b5ce-d5eb3b40e10e ## Notes - There is a missing icon in Figma corresponding to the `noDeletedRecordFound` in the dark mode, thus I used the same icon (different background because we have the correct background image) for both dark / light modes Screenshot 2024-09-03 at 15 04 57 cc: @Bonapara --------- Co-authored-by: Lucas Bordeau --- .../background/no_deleted_record_bg.png | Bin 0 -> 2920 bytes .../dark-background/no_deleted_record_bg.png | Bin 0 -> 2997 bytes .../dark-background/no_match_record_bg.png | Bin 6194 -> 2878 bytes .../dark-moving-image/no_deleted_record.png | Bin 0 -> 7584 bytes .../moving-image/no_deleted_record.png | Bin 0 -> 7617 bytes .../hooks/useObjectIsRemote.ts | 5 + .../object-metadata/hooks/useObjectLabel.ts | 5 + .../components/RecordIndexBoardContainer.tsx | 1 - .../components/RecordIndexContainer.tsx | 107 +++++++----------- .../components/RecordIndexPageHeader.tsx | 31 +++-- .../RecordIndexPageKanbanAddButton.tsx | 17 ++- .../components/RecordIndexTableContainer.tsx | 9 +- .../contexts/RecordIndexRootPropsContext.ts | 13 +++ .../hooks/useHandleToggleTrashColumnFilter.ts | 15 ++- .../RecordIndexOptionsDropdownContent.tsx | 10 +- .../record-table/components/RecordTable.tsx | 28 ++--- .../components/RecordTableEmptyState.tsx | 68 ----------- .../components/RecordTableWithWrappers.tsx | 3 - .../components/RecordTableEmptyState.tsx | 34 ++++++ .../RecordTableEmptyStateDisplay.tsx | 48 ++++++++ .../RecordTableEmptyStateNoRecordAtAll.tsx | 36 ++++++ ...dTableEmptyStateNoRecordFoundForFilter.tsx | 36 ++++++ .../RecordTableEmptyStateRemote.tsx | 24 ++++ .../RecordTableEmptyStateSoftDelete.tsx | 49 ++++++++ ...rdTableEmptyStateNoRecordAtAll.stories.tsx | 39 +++++++ ...ptyStateNoRecordFoundForFilter.stories.tsx | 40 +++++++ .../RecordTableEmptyStateRemote.stories.tsx} | 36 +++--- ...ecordTableEmptyStateSoftDelete.stories.tsx | 39 +++++++ .../hooks/internal/useRecordTableStates.ts | 5 + .../hooks/useCreateNewTableRecords.ts | 33 ++++++ .../components/RecordTableHeader.tsx | 12 +- .../components/RecordTableHeaderCell.tsx | 11 +- .../isSoftDeleteFilterActiveComponentState.ts | 7 ++ .../SignInBackgroundMockContainer.tsx | 1 - .../components/AnimatedPlaceholder.tsx | 8 +- .../constants/Background.ts | 1 + .../constants/DarkBackground.ts | 2 + .../constants/DarkMovingImage.ts | 2 + .../constants/MovingImage.ts | 1 + .../views/components/VariantFilterChip.tsx | 26 ++++- .../views/components/ViewBarDetails.tsx | 2 + .../views/hooks/useCombinedViewFilters.ts | 1 + .../pages/object-record/RecordIndexPage.tsx | 76 ++++++++----- .../decorators/RecordTableDecorator.tsx | 39 +++++++ .../src/utils/createRootPropsContext.ts | 11 ++ 45 files changed, 667 insertions(+), 264 deletions(-) create mode 100644 packages/twenty-front/public/images/placeholders/background/no_deleted_record_bg.png create mode 100644 packages/twenty-front/public/images/placeholders/dark-background/no_deleted_record_bg.png create mode 100644 packages/twenty-front/public/images/placeholders/dark-moving-image/no_deleted_record.png create mode 100644 packages/twenty-front/public/images/placeholders/moving-image/no_deleted_record.png create mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useObjectLabel.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-index/contexts/RecordIndexRootPropsContext.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmptyState.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/__stories__/RecordTableEmptyStateNoRecordAtAll.stories.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/__stories__/RecordTableEmptyStateNoRecordFoundForFilter.stories.tsx rename packages/twenty-front/src/modules/object-record/record-table/{components/__stories__/RecordTableEmptyState.stories.tsx => empty-state/components/__stories__/RecordTableEmptyStateRemote.stories.tsx} (53%) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/__stories__/RecordTableEmptyStateSoftDelete.stories.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/states/isSoftDeleteFilterActiveComponentState.ts create mode 100644 packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx create mode 100644 packages/twenty-front/src/utils/createRootPropsContext.ts diff --git a/packages/twenty-front/public/images/placeholders/background/no_deleted_record_bg.png b/packages/twenty-front/public/images/placeholders/background/no_deleted_record_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..f69676ba702440672deea2fa898ab0f5b7c37db7 GIT binary patch literal 2920 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 literal 0 HcmV?d00001 diff --git a/packages/twenty-front/public/images/placeholders/dark-background/no_match_record_bg.png b/packages/twenty-front/public/images/placeholders/dark-background/no_match_record_bg.png index da554e159f2aab9c3036beb7943cae2623e68e49..bb55a0b9c1df144507952fbbe5c190167e718a4e 100644 GIT binary patch delta 2877 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 literal 0 HcmV?d00001 diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts new file mode 100644 index 000000000..0f3295dc5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts @@ -0,0 +1,5 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const useObjectIsRemote = (objectMetadataItem: ObjectMetadataItem) => { + return objectMetadataItem.isRemote ?? false; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectLabel.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectLabel.ts new file mode 100644 index 000000000..7ef0881d6 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectLabel.ts @@ -0,0 +1,5 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const useObjectLabel = (objectMetadataItem: ObjectMetadataItem) => { + return objectMetadataItem?.labelSingular ?? ''; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx index bd412f910..8bca15108 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx @@ -14,7 +14,6 @@ type RecordIndexBoardContainerProps = { recordBoardId: string; viewBarId: string; objectNameSingular: string; - createRecord: () => Promise; }; export const RecordIndexBoardContainer = ({ diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 833f92060..e8cdaa20f 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -4,14 +4,12 @@ import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; -import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId'; import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer'; import { RecordIndexBoardDataLoader } from '@/object-record/record-index/components/RecordIndexBoardDataLoader'; import { RecordIndexBoardDataLoaderEffect } from '@/object-record/record-index/components/RecordIndexBoardDataLoaderEffect'; import { RecordIndexTableContainer } from '@/object-record/record-index/components/RecordIndexTableContainer'; import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect'; import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect'; -import { RecordIndexEventContext } from '@/object-record/record-index/contexts/RecordIndexEventContext'; import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdown'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; @@ -21,7 +19,7 @@ import { recordIndexSortsState } from '@/object-record/record-index/states/recor import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; -import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick'; +import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider'; @@ -31,6 +29,7 @@ import { ViewType } from '@/views/types/ViewType'; import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; +import { useContext } from 'react'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; const StyledContainer = styled.div` @@ -46,20 +45,15 @@ const StyledContainerWithPadding = styled.div<{ fullHeight?: boolean }>` padding-left: ${({ theme }) => theme.table.horizontalCellPadding}; `; -type RecordIndexContainerProps = { - recordIndexId: string; - objectNamePlural: string; - createRecord: () => Promise; -}; - -export const RecordIndexContainer = ({ - createRecord, - recordIndexId, - objectNamePlural, -}: RecordIndexContainerProps) => { +export const RecordIndexContainer = () => { const [recordIndexViewType, setRecordIndexViewType] = useRecoilState( recordIndexViewTypeState, ); + + const { objectNamePlural, recordIndexId } = useContext( + RecordIndexRootPropsContext, + ); + const { objectNameSingular } = useObjectNameSingularFromPlural({ objectNamePlural, }); @@ -110,20 +104,6 @@ export const RecordIndexContainer = ({ [columnDefinitions, setTableColumns], ); - const { handleIndexIdentifierClick } = useHandleIndexIdentifierClick({ - objectMetadataItem, - recordIndexId, - }); - - const handleIndexRecordsLoaded = useRecoilCallback( - ({ set }) => - () => { - // TODO: find a better way to reset this state ? - set(lastShowPageRecordIdState, null); - }, - [], - ); - return ( @@ -170,46 +150,37 @@ export const RecordIndexContainer = ({ /> - - {recordIndexViewType === ViewType.Table && ( - <> - - - - )} - {recordIndexViewType === ViewType.Kanban && ( - - - - - - )} - + + {recordIndexViewType === ViewType.Table && ( + <> + + + + )} + {recordIndexViewType === ViewType.Kanban && ( + + + + + + )} ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx index b2296a6f3..bb8c0197a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx @@ -3,27 +3,23 @@ import { useIcons } from 'twenty-ui'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton'; +import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { PageAddButton } from '@/ui/layout/page/PageAddButton'; import { PageHeader } from '@/ui/layout/page/PageHeader'; import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect'; import { ViewType } from '@/views/types/ViewType'; +import { useContext } from 'react'; import { capitalize } from '~/utils/string/capitalize'; -type RecordIndexPageHeaderProps = { - createRecord: () => void; - recordIndexId: string; - objectNamePlural: string; -}; - -export const RecordIndexPageHeader = ({ - createRecord, - recordIndexId, - objectNamePlural, -}: RecordIndexPageHeaderProps) => { +export const RecordIndexPageHeader = () => { const { findObjectMetadataItemByNamePlural } = useFilteredObjectMetadataItems(); + const { objectNamePlural, onCreateRecord } = useContext( + RecordIndexRootPropsContext, + ); + const objectMetadataItem = findObjectMetadataItemByNamePlural(objectNamePlural); @@ -40,16 +36,17 @@ export const RecordIndexPageHeader = ({ const pageHeaderTitle = objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural); + const handleAddButtonClick = () => { + onCreateRecord(); + }; + return ( - + {isTable ? ( - + ) : ( - + )} ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx index 6299546e0..679896c32 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx @@ -2,6 +2,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; +import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexPageKanbanAddButton } from '@/object-record/record-index/hooks/useRecordIndexPageKanbanAddButton'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch'; @@ -14,7 +15,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import styled from '@emotion/styled'; -import { useCallback, useState } from 'react'; +import { useCallback, useContext, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { IconPlus, isDefined } from 'twenty-ui'; @@ -26,20 +27,16 @@ const StyledDropDownMenu = styled(DropdownMenu)` width: 200px; `; -type RecordIndexPageKanbanAddButtonProps = { - recordIndexId: string; - objectNamePlural: string; -}; - -export const RecordIndexPageKanbanAddButton = ({ - recordIndexId, - objectNamePlural, -}: RecordIndexPageKanbanAddButtonProps) => { +export const RecordIndexPageKanbanAddButton = () => { const dropdownId = `record-index-page-add-button-dropdown`; const [isSelectingCompany, setIsSelectingCompany] = useState(false); const [selectedColumnDefinition, setSelectedColumnDefinition] = useState(); + const { recordIndexId, objectNamePlural } = useContext( + RecordIndexRootPropsContext, + ); + const { columnIdsState } = useRecordBoardStates(recordIndexId); const columnIds = useRecoilValue(columnIdsState); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx index f5af2e96f..50bb639f5 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx @@ -1,23 +1,23 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext'; import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal'; +import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu'; +import { useContext } from 'react'; type RecordIndexTableContainerProps = { recordTableId: string; viewBarId: string; - objectNameSingular: string; - createRecord: () => Promise; }; export const RecordIndexTableContainer = ({ recordTableId, viewBarId, - objectNameSingular, - createRecord, }: RecordIndexTableContainerProps) => { + const { objectNameSingular } = useContext(RecordIndexRootPropsContext); + const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular, }); @@ -36,7 +36,6 @@ export const RecordIndexTableContainer = ({ objectNameSingular={objectNameSingular} viewBarId={viewBarId} updateRecordMutation={updateEntity} - createRecord={createRecord} /> diff --git a/packages/twenty-front/src/modules/object-record/record-index/contexts/RecordIndexRootPropsContext.ts b/packages/twenty-front/src/modules/object-record/record-index/contexts/RecordIndexRootPropsContext.ts new file mode 100644 index 000000000..6de7cd552 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/contexts/RecordIndexRootPropsContext.ts @@ -0,0 +1,13 @@ +import { createRootPropsContext } from '~/utils/createRootPropsContext'; + +export type RecordIndexRootPropsContextProps = { + onIndexIdentifierClick: (recordId: string) => void; + onIndexRecordsLoaded: () => void; + onCreateRecord: () => void; + objectNamePlural: string; + objectNameSingular: string; + recordIndexId: string; +}; + +export const RecordIndexRootPropsContext = + createRootPropsContext(); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts index 033773b96..bbe862c0a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts @@ -5,8 +5,10 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from '~/utils/isDefined'; type UseHandleToggleTrashColumnFilterProps = { @@ -26,6 +28,7 @@ export const useHandleToggleTrashColumnFilter = ({ useColumnDefinitionsFromFieldMetadata(objectMetadataItem); const { upsertCombinedViewFilter } = useCombinedViewFilters(viewBarId); + const { isSoftDeleteActiveState } = useRecordTableStates(viewBarId); const handleToggleTrashColumnFilter = useCallback(() => { const trashFieldMetadata = objectMetadataItem.fields.find( @@ -63,5 +66,15 @@ export const useHandleToggleTrashColumnFilter = ({ upsertCombinedViewFilter(newFilter); }, [columnDefinitions, objectMetadataItem, upsertCombinedViewFilter]); - return handleToggleTrashColumnFilter; + const toggleSoftDeleteFilterState = useRecoilCallback( + ({ set }) => + (currentState: boolean) => { + set(isSoftDeleteActiveState, currentState); + }, + [isSoftDeleteActiveState], + ); + return { + handleToggleTrashColumnFilter, + toggleSoftDeleteFilterState, + }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx index 2386009de..a884eda85 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx @@ -90,10 +90,11 @@ export const RecordIndexOptionsDropdownContent = ({ hiddenTableColumns, } = useRecordIndexOptionsForTable(recordIndexId); - const handleToggleTrashColumnFilter = useHandleToggleTrashColumnFilter({ - objectNameSingular, - viewBarId: recordIndexId, - }); + const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } = + useHandleToggleTrashColumnFilter({ + objectNameSingular, + viewBarId: recordIndexId, + }); const { visibleBoardFields, @@ -163,6 +164,7 @@ export const RecordIndexOptionsDropdownContent = ({ { handleToggleTrashColumnFilter(); + toggleSoftDeleteFilterState(true); closeDropdown(); }} LeftIcon={IconRotate2} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index 5e3b8c23a..7f76f10a5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -1,9 +1,8 @@ import styled from '@emotion/styled'; import { isNonEmptyString, isNull } from '@sniptt/guards'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; -import { RecordTableEmptyState } from '@/object-record/record-table/components/RecordTableEmptyState'; +import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody'; import { RecordTableBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEffect'; @@ -25,7 +24,6 @@ type RecordTableProps = { recordTableId: string; objectNameSingular: string; onColumnsChange: (columns: any) => void; - createRecord: () => void; }; export const RecordTable = ({ @@ -33,7 +31,6 @@ export const RecordTable = ({ recordTableId, objectNameSingular, onColumnsChange, - createRecord, }: RecordTableProps) => { const { scopeId } = useRecordTableStates(recordTableId); @@ -51,12 +48,10 @@ export const RecordTable = ({ const pendingRecordId = useRecoilValue(pendingRecordIdState); - const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem( - { objectNameSingular }, - ); - - const objectLabel = foundObjectMetadataItem?.labelSingular; - const isRemote = foundObjectMetadataItem?.isRemote ?? false; + const recordTableIsEmpty = + !isRecordTableInitialLoading && + tableRowIds.length === 0 && + isNull(pendingRecordId); if (!isNonEmptyString(objectNameSingular)) { return <>; @@ -73,18 +68,11 @@ export const RecordTable = ({ viewBarId={viewBarId} > - {!isRecordTableInitialLoading && - tableRowIds.length === 0 && - isNull(pendingRecordId) ? ( - + {recordTableIsEmpty ? ( + ) : ( - + )} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmptyState.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmptyState.tsx deleted file mode 100644 index 18aef6779..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmptyState.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import { IconPlus, IconSettings } from 'twenty-ui'; - -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { Button } from '@/ui/input/button/components/Button'; -import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder'; -import { - AnimatedPlaceholderEmptyContainer, - AnimatedPlaceholderEmptySubTitle, - AnimatedPlaceholderEmptyTextContainer, - AnimatedPlaceholderEmptyTitle, -} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; - -type RecordTableEmptyStateProps = { - objectNameSingular: string; - objectLabel: string; - createRecord: () => void; - isRemote: boolean; -}; - -export const RecordTableEmptyState = ({ - objectNameSingular, - objectLabel, - createRecord, - isRemote, -}: RecordTableEmptyStateProps) => { - const navigate = useNavigate(); - const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 }); - const noExistingRecords = totalCount === 0; - - const [title, subTitle, Icon, onClick, buttonTitle] = isRemote - ? [ - 'No Data Available for Remote Table', - 'If this is unexpected, please verify your settings.', - IconSettings, - () => navigate('/settings/integrations'), - 'Go to Settings', - ] - : [ - noExistingRecords - ? `Add your first ${objectLabel}` - : `No ${objectLabel} found`, - noExistingRecords - ? `Use our API or add your first ${objectLabel} manually` - : 'No records matching the filter criteria were found.', - IconPlus, - createRecord, - `Add a ${objectLabel}`, - ]; - - return ( - - - - {title} - - {subTitle} - - -