From 0fa2a4524a3152695245a97a66efa3205bfd8a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Mon, 17 Nov 2025 19:10:19 +0100 Subject: [PATCH] feat: Add filter button to objects table and enable system objects access (#15856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR replaces the Active/Inactive accordion sections with a modern filter dropdown button and enables full access to system objects in advanced mode. ## Changes ### UI Improvements - ✅ Replaced accordion sections with a filter button dropdown (matching the design pattern from the Group filter) - ✅ Added 'Deactivated' toggle filter (hidden by default, uses IconArchive) - ✅ Added 'System objects' toggle filter (only visible in advanced mode, uses IconSettings) - ✅ Fixed search input width to properly fill available space - ✅ Proper button sizing and alignment ### System Objects Support - ✅ Made system objects visible when 'System objects' filter is toggled on - ✅ System objects are now fully clickable and accessible - ✅ Updated object detail page to support system objects - ✅ Updated field creation/edit pages to support system objects - ✅ System objects can now have custom fields added ### Architecture - ✅ Implemented scalable filter architecture using a single filtered list - ✅ Easy to add more filters in the future (e.g., show remote objects) - ✅ All filters work independently and can be combined ## Testing - [x] Tested deactivated objects toggle - [x] Tested system objects toggle (only shows in advanced mode) - [x] Tested clicking on system objects - [x] Tested adding custom fields to system objects - [x] No linter errors ## Screenshots See attached screenshots in the conversation for the new filter UI. --- > [!NOTE] > Adds a filter dropdown to the Settings Objects table (incl. deactivated/system toggles), enables system objects across settings/field flows, seeds core views (workspace members, messages, threads, calendar events), and adds actions for Workspace Members. > > - **Settings UI** > - **Objects Table**: Replaces Active/Inactive sections with a single filterable list and dropdown (`Deactivated`, `System objects` in advanced mode); updates props to `objectMetadataItems` and removes accordion sections. > - **Search/UX**: Search input fills available space; inactive rows show activation/delete menu; active rows remain navigable. > - **Pages Updated**: `SettingsObjects`, `SettingsApplicationDetailContentTab` switch to new table API; object detail and new-field flows use `findObjectMetadataItemByNamePlural` (works with system objects). > - **Action Menu** > - **Workspace Members**: Adds `WORKSPACE_MEMBERS_ACTIONS_CONFIG` with "Manage members in settings" action; wired into `getActionConfig` for `WorkspaceMember`. > - **Server (Core Views Seed)** > - Adds default views: `workspaceMembersAllView`, `messagesAllView`, `messageThreadsAllView`, `calendarEventsAllView`; included in `prefillCoreViews`. > - Marks workflow entities as system (`WorkflowRun`, `WorkflowVersion`). > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6a2856df85104665bd986cc63f2d54f19bf668d0. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../WorkspaceMembersActionsConfig.tsx | 124 ++++++++++ .../actions/utils/getActionConfig.ts | 4 + .../NavigationDrawerOpenedSection.tsx | 5 +- .../SettingsApplicationDetailContentTab.tsx | 3 +- .../data-model/SettingsObjectDetailPage.tsx | 8 +- .../data-model/SettingsObjectTable.tsx | 221 ++++++++++-------- .../settings/data-model/SettingsObjects.tsx | 10 +- .../SettingsObjectNewFieldConfigure.tsx | 4 +- .../SettingsObjectNewFieldSelect.tsx | 4 +- .../prefill-core-views.ts | 8 + .../views/calendar-events-all.view.ts | 122 ++++++++++ .../views/message-threads-all.view.ts | 55 +++++ .../views/messages-all.view.ts | 104 +++++++++ .../views/workspace-members-all.view.ts | 132 +++++++++++ .../workflow-run.workspace-entity.ts | 3 +- .../workflow-version.workspace-entity.ts | 1 + 16 files changed, 690 insertions(+), 118 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkspaceMembersActionsConfig.tsx create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/calendar-events-all.view.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/message-threads-all.view.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/messages-all.view.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workspace-members-all.view.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkspaceMembersActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkspaceMembersActionsConfig.tsx new file mode 100644 index 0000000000..b6476ec3c6 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkspaceMembersActionsConfig.tsx @@ -0,0 +1,124 @@ +import { ActionLink } from '@/action-menu/actions/components/ActionLink'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys'; +import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { inheritActionsFromDefaultConfig } from '@/action-menu/actions/record-actions/utils/inheritActionsFromDefaultConfig'; +import { ActionScope } from '@/action-menu/actions/types/ActionScope'; +import { ActionType } from '@/action-menu/actions/types/ActionType'; +import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; +import { msg } from '@lingui/core/macro'; +import { AppPath, SettingsPath } from 'twenty-shared/types'; +import { IconSettings } from 'twenty-ui/display'; + +export const WORKSPACE_MEMBERS_ACTIONS_CONFIG = inheritActionsFromDefaultConfig( + { + config: { + [NoSelectionRecordActionKeys.GO_TO_SETTINGS]: { + type: ActionType.Navigation, + scope: ActionScope.Global, + key: NoSelectionRecordActionKeys.GO_TO_SETTINGS, + label: msg`Manage members in settings`, + shortLabel: msg`Manage in settings`, + position: 14, + Icon: IconSettings, + isPinned: true, + availableOn: [ + ActionViewType.INDEX_PAGE_NO_SELECTION, + ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, + ActionViewType.INDEX_PAGE_BULK_SELECTION, + ActionViewType.SHOW_PAGE, + ], + shouldBeRegistered: () => true, + component: ( + + ), + hotKeys: ['G', 'S'], + }, + }, + actionKeys: [ + SingleRecordActionKeys.ADD_TO_FAVORITES, + SingleRecordActionKeys.REMOVE_FROM_FAVORITES, + SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD, + SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD, + SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX, + SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW, + MultipleRecordsActionKeys.EXPORT, + NoSelectionRecordActionKeys.EXPORT_VIEW, + NoSelectionRecordActionKeys.SEE_DELETED_RECORDS, + NoSelectionRecordActionKeys.HIDE_DELETED_RECORDS, + NoSelectionRecordActionKeys.GO_TO_WORKFLOWS, + NoSelectionRecordActionKeys.GO_TO_PEOPLE, + NoSelectionRecordActionKeys.GO_TO_COMPANIES, + NoSelectionRecordActionKeys.GO_TO_OPPORTUNITIES, + NoSelectionRecordActionKeys.GO_TO_DASHBOARDS, + NoSelectionRecordActionKeys.GO_TO_TASKS, + NoSelectionRecordActionKeys.GO_TO_NOTES, + ], + propertiesToOverwrite: { + [SingleRecordActionKeys.ADD_TO_FAVORITES]: { + isPinned: false, + position: 0, + }, + [SingleRecordActionKeys.REMOVE_FROM_FAVORITES]: { + isPinned: false, + position: 1, + }, + [SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX]: { + position: 2, + label: msg`Export member`, + }, + [SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: { + position: 2, + label: msg`Export member`, + }, + [MultipleRecordsActionKeys.EXPORT]: { + position: 3, + label: msg`Export members`, + }, + [NoSelectionRecordActionKeys.EXPORT_VIEW]: { + position: 4, + label: msg`Export view`, + }, + [NoSelectionRecordActionKeys.SEE_DELETED_RECORDS]: { + position: 5, + label: msg`See deleted members`, + }, + [NoSelectionRecordActionKeys.HIDE_DELETED_RECORDS]: { + position: 6, + label: msg`Hide deleted members`, + }, + [SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD]: { + position: 7, + }, + [SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD]: { + position: 8, + }, + [NoSelectionRecordActionKeys.GO_TO_WORKFLOWS]: { + position: 9, + }, + [NoSelectionRecordActionKeys.GO_TO_PEOPLE]: { + position: 10, + }, + [NoSelectionRecordActionKeys.GO_TO_COMPANIES]: { + position: 11, + }, + [NoSelectionRecordActionKeys.GO_TO_OPPORTUNITIES]: { + position: 12, + }, + [NoSelectionRecordActionKeys.GO_TO_DASHBOARDS]: { + position: 13, + }, + [NoSelectionRecordActionKeys.GO_TO_TASKS]: { + position: 15, + }, + [NoSelectionRecordActionKeys.GO_TO_NOTES]: { + position: 16, + }, + }, + }, +); diff --git a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts index 31ea3ebe50..c5bed2d8e2 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts @@ -4,6 +4,7 @@ import { RECORD_PAGE_LAYOUT_ACTIONS_CONFIG } from '@/action-menu/actions/record- import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig'; import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig'; import { WORKFLOW_VERSIONS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig'; +import { WORKSPACE_MEMBERS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkspaceMembersActionsConfig'; import { type ActionConfig } from '@/action-menu/actions/types/ActionConfig'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -33,6 +34,9 @@ export const getActionConfig = ({ case CoreObjectNameSingular.WorkflowRun: { return WORKFLOW_RUNS_ACTIONS_CONFIG; } + case CoreObjectNameSingular.WorkspaceMember: { + return WORKSPACE_MEMBERS_ACTIONS_CONFIG; + } case CoreObjectNameSingular.Company: { if (isRecordPageLayoutEnabled) { return RECORD_PAGE_LAYOUT_ACTIONS_CONFIG; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx index 5538313763..aa94f97fcc 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx @@ -10,10 +10,9 @@ import { useLingui } from '@lingui/react/macro'; export const NavigationDrawerOpenedSection = () => { const { t } = useLingui(); - const { activeNonSystemObjectMetadataItems } = - useFilteredObjectMetadataItems(); + const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); const filteredActiveNonSystemObjectMetadataItems = - activeNonSystemObjectMetadataItems.filter((item) => !item.isRemote); + activeObjectMetadataItems.filter((item) => !item.isRemote); const loading = useIsPrefetchLoading(); diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx index 0b581cd5d0..cfdb3d8d37 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx @@ -66,8 +66,7 @@ export const SettingsApplicationDetailContentTab = ({ description={t`Objects created by application`} /> diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx index 820a8bb31e..bd686c4bc0 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx @@ -14,6 +14,7 @@ import styled from '@emotion/styled'; import { useRecoilState, useRecoilValue } from 'recoil'; import { AppPath, SettingsPath } from 'twenty-shared/types'; +import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly'; import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -34,7 +35,6 @@ import { FeatureFlagKey } from '~/generated/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { SETTINGS_OBJECT_DETAIL_TABS } from '~/pages/settings/data-model/constants/SettingsObjectDetailTabs'; import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState'; -import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly'; const StyledContentContainer = styled.div` flex: 1; @@ -57,15 +57,15 @@ export const SettingsObjectDetailPage = () => { const { t } = useLingui(); const { objectNamePlural = '' } = useParams(); - const { findActiveObjectMetadataItemByNamePlural } = + const { findObjectMetadataItemByNamePlural } = useFilteredObjectMetadataItems(); const [updatedObjectNamePlural, setUpdatedObjectNamePlural] = useRecoilState( updatedObjectNamePluralState, ); const objectMetadataItem = - findActiveObjectMetadataItemByNamePlural(objectNamePlural) ?? - findActiveObjectMetadataItemByNamePlural(updatedObjectNamePlural); + findObjectMetadataItemByNamePlural(objectNamePlural) ?? + findObjectMetadataItemByNamePlural(updatedObjectNamePlural); const readonly = isObjectMetadataSettingsReadOnly({ objectMetadataItem }); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx index d8fc991b6f..5b103097ef 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx @@ -9,19 +9,30 @@ import { import { SettingsObjectInactiveMenuDropDown } from '@/settings/data-model/objects/components/SettingsObjectInactiveMenuDropDown'; import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; import { Table } from '@/ui/layout/table/components/Table'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; -import { TableSection } from '@/ui/layout/table/components/TableSection'; import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { isNonEmptyArray } from '@sniptt/guards'; import { useMemo, useState } from 'react'; +import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; -import { IconChevronRight, IconSearch } from 'twenty-ui/display'; +import { + IconArchive, + IconChevronRight, + IconFilter, + IconSearch, + IconSettings, +} from 'twenty-ui/display'; +import { Button } from 'twenty-ui/input'; +import { MenuItemToggle } from 'twenty-ui/navigation'; import { GET_SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata'; import type { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem'; import { normalizeSearchText } from '~/utils/normalizeSearchText'; @@ -30,25 +41,34 @@ const StyledIconChevronRight = styled(IconChevronRight)` color: ${({ theme }) => theme.font.color.tertiary}; `; -const StyledSearchInput = styled(SettingsTextInput)` +const StyledSearchAndFilterContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + align-items: center; padding-bottom: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledSearchInput = styled(SettingsTextInput)` + flex: 1; width: 100%; `; export const SettingsObjectTable = ({ - activeObjects, - inactiveObjects, + objectMetadataItems, withSearchBar = true, }: { - activeObjects: ObjectMetadataItem[]; - inactiveObjects: ObjectMetadataItem[]; + objectMetadataItems: ObjectMetadataItem[]; withSearchBar?: boolean; }) => { const { t } = useLingui(); const theme = useTheme(); + const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); + const [searchTerm, setSearchTerm] = useState(''); + const [showDeactivated, setShowDeactivated] = useState(false); + const [showSystemObjects, setShowSystemObjects] = useState(false); const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem(); @@ -56,13 +76,13 @@ export const SettingsObjectTable = ({ const { totalCountByObjectMetadataItemNamePlural } = useCombinedGetTotalCount( { - objectMetadataItems: [...activeObjects, ...inactiveObjects], + objectMetadataItems, }, ); - const activeObjectSettingsArray = useMemo( + const allObjectSettingsArray = useMemo( () => - activeObjects.map( + objectMetadataItems.map( (objectMetadataItem) => ({ objectMetadataItem, @@ -77,76 +97,91 @@ export const SettingsObjectTable = ({ ] ?? 0, }) satisfies SettingsObjectTableItem, ), - [activeObjects, totalCountByObjectMetadataItemNamePlural], + [objectMetadataItems, totalCountByObjectMetadataItemNamePlural], ); - const inactiveObjectSettingsArray = useMemo( - () => - inactiveObjects.map( - (objectMetadataItem) => - ({ - objectMetadataItem, - labelPlural: objectMetadataItem.labelPlural, - objectTypeLabel: getItemTagInfo({ - isCustom: objectMetadataItem.isCustom, - isRemote: objectMetadataItem.isRemote, - }).labelText, - fieldsCount: objectMetadataItem.fields.filter( - (field) => !field.isSystem, - ).length, - totalObjectCount: - totalCountByObjectMetadataItemNamePlural[ - objectMetadataItem.namePlural - ] ?? 0, - }) satisfies SettingsObjectTableItem, - ), - [inactiveObjects, totalCountByObjectMetadataItemNamePlural], - ); - - const sortedActiveObjectSettingsItems = useSortedArray( - activeObjectSettingsArray, + const sortedObjectSettingsItems = useSortedArray( + allObjectSettingsArray, GET_SETTINGS_OBJECT_TABLE_METADATA, ); - const sortedInactiveObjectSettingsItems = useSortedArray( - inactiveObjectSettingsArray, - GET_SETTINGS_OBJECT_TABLE_METADATA, - ); - - const filteredActiveObjectSettingsItems = useMemo( + const filteredObjectSettingsItems = useMemo( () => - sortedActiveObjectSettingsItems.filter((item) => { + sortedObjectSettingsItems.filter((item) => { const searchNormalized = normalizeSearchText(searchTerm); - return ( + const matchesSearch = normalizeSearchText(item.labelPlural).includes(searchNormalized) || - normalizeSearchText(item.objectTypeLabel).includes(searchNormalized) - ); - }), - [sortedActiveObjectSettingsItems, searchTerm], - ); + normalizeSearchText(item.objectTypeLabel).includes(searchNormalized); - const filteredInactiveObjectSettingsItems = useMemo( - () => - sortedInactiveObjectSettingsItems.filter((item) => { - const searchNormalized = normalizeSearchText(searchTerm); - return ( - normalizeSearchText(item.labelPlural).includes(searchNormalized) || - normalizeSearchText(item.objectTypeLabel).includes(searchNormalized) - ); + if (!matchesSearch) { + return false; + } + + const isActive = item.objectMetadataItem.isActive; + if (!isActive && !showDeactivated) { + return false; + } + + const isSystem = item.objectMetadataItem.isSystem; + if (isSystem && !showSystemObjects) { + return false; + } + + return true; }), - [sortedInactiveObjectSettingsItems, searchTerm], + [sortedObjectSettingsItems, searchTerm, showDeactivated, showSystemObjects], ); return ( <> {withSearchBar && ( - + + + + } + dropdownComponents={ + + + setShowDeactivated(!showDeactivated)} + toggled={showDeactivated} + text={t`Deactivated`} + toggleSize="small" + /> + {isAdvancedModeEnabled && ( + + setShowSystemObjects(!showSystemObjects) + } + toggled={showSystemObjects} + text={t`System objects`} + toggleSize="small" + /> + )} + + + } + /> + )} @@ -165,35 +200,21 @@ export const SettingsObjectTable = ({ )} - {isNonEmptyArray(sortedActiveObjectSettingsItems) && ( - - {filteredActiveObjectSettingsItems.map((objectSettingsItem) => ( - { + const isActive = objectSettingsItem.objectMetadataItem.isActive; + + return ( + - } - link={getSettingsPath(SettingsPath.ObjectDetail, { - objectNamePlural: - objectSettingsItem.objectMetadataItem.namePlural, - })} - /> - ))} - - )} - {isNonEmptyArray(sortedInactiveObjectSettingsItems) && ( - - {filteredInactiveObjectSettingsItems.map((objectSettingsItem) => ( - - } - /> - ))} - - )} + ) + } + link={ + isActive + ? getSettingsPath(SettingsPath.ObjectDetail, { + objectNamePlural: + objectSettingsItem.objectMetadataItem.namePlural, + }) + : undefined + } + /> + ); + })}
); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx index 72221fc245..85607d7728 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx @@ -15,10 +15,7 @@ import { SettingsObjectTable } from '~/pages/settings/data-model/SettingsObjectT export const SettingsObjects = () => { const { t } = useLingui(); - const { - activeNonSystemObjectMetadataItems, - inactiveNonSystemObjectMetadataItems, - } = useFilteredObjectMetadataItems(); + const { objectMetadataItems } = useFilteredObjectMetadataItems(); return ( {
- +
diff --git a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx index 0d165d45a3..581d726dbf 100644 --- a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx @@ -49,10 +49,10 @@ export const SettingsObjectNewFieldConfigure = () => { FieldMetadataType.TEXT; const { enqueueErrorSnackBar } = useSnackBar(); - const { findActiveObjectMetadataItemByNamePlural } = + const { findObjectMetadataItemByNamePlural } = useFilteredObjectMetadataItems(); const activeObjectMetadataItem = - findActiveObjectMetadataItemByNamePlural(objectNamePlural); + findObjectMetadataItemByNamePlural(objectNamePlural); const { createMetadataField } = useFieldMetadataItem(); const formConfig = useForm({ diff --git a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx index 4df58c1c13..107223d569 100644 --- a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx @@ -33,10 +33,10 @@ export type SettingsDataModelFieldTypeFormValues = z.infer< export const SettingsObjectNewFieldSelect = () => { const navigate = useNavigateApp(); const { objectNamePlural = '' } = useParams(); - const { findActiveObjectMetadataItemByNamePlural } = + const { findObjectMetadataItemByNamePlural } = useFilteredObjectMetadataItems(); const activeObjectMetadataItem = - findActiveObjectMetadataItemByNamePlural(objectNamePlural); + findObjectMetadataItemByNamePlural(objectNamePlural); const formMethods = useForm({ resolver: zodResolver(settingsDataModelFieldTypeFormSchema), defaultValues: { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts index 1b3ada68c8..0da404bb20 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views.ts @@ -16,8 +16,11 @@ import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manage import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites'; import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface'; +import { calendarEventsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/calendar-events-all.view'; import { companiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view'; import { dashboardsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view'; +import { messageThreadsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/message-threads-all.view'; +import { messagesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/messages-all.view'; import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view'; import { opportunitiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view'; import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view'; @@ -28,6 +31,7 @@ import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view'; import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view'; import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view'; +import { workspaceMembersAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workspace-members-all.view'; type PrefillCoreViewsArgs = { coreDataSource: DataSource; @@ -56,6 +60,10 @@ export const prefillCoreViews = async ({ workflowVersionsAllView(objectMetadataItems, true), workflowRunsAllView(objectMetadataItems, true), dashboardsAllView(objectMetadataItems, true), + workspaceMembersAllView(objectMetadataItems, true), + messagesAllView(objectMetadataItems, true), + messageThreadsAllView(objectMetadataItems, true), + calendarEventsAllView(objectMetadataItems, true), ]; const queryRunner = coreDataSource.createQueryRunner(); diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/calendar-events-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/calendar-events-all.view.ts new file mode 100644 index 0000000000..2ec1ee3507 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/calendar-events-all.view.ts @@ -0,0 +1,122 @@ +import { msg } from '@lingui/core/macro'; + +import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE'; +import { + BASE_OBJECT_STANDARD_FIELD_IDS, + CALENDAR_EVENT_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const calendarEventsAllView = ( + objectMetadataItems: ObjectMetadataEntity[], + useCoreNaming = false, +) => { + const calendarEventObjectMetadata = objectMetadataItems.find( + (object) => object.standardId === STANDARD_OBJECT_IDS.calendarEvent, + ); + + if (!calendarEventObjectMetadata) { + throw new Error('CalendarEvent object metadata not found'); + } + + return { + name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Calendar Events', + objectMetadataId: calendarEventObjectMetadata.id ?? '', + type: 'calendar', + key: 'INDEX', + position: 0, + icon: 'IconCalendar', + kanbanFieldMetadataId: '', + calendarFieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.startsAt, + )?.id ?? '', + calendarLayout: 'MONTH', + filters: [], + fields: [ + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.title, + )?.id ?? '', + position: 0, + isVisible: true, + size: DEFAULT_VIEW_FIELD_SIZE, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.startsAt, + )?.id ?? '', + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.endsAt, + )?.id ?? '', + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.isFullDay, + )?.id ?? '', + position: 3, + isVisible: true, + size: 100, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.location, + )?.id ?? '', + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === + CALENDAR_EVENT_STANDARD_FIELD_IDS.conferenceLink, + )?.id ?? '', + position: 5, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.isCanceled, + )?.id ?? '', + position: 6, + isVisible: true, + size: 100, + }, + { + fieldMetadataId: + calendarEventObjectMetadata.fields.find( + (field) => + field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + )?.id ?? '', + position: 7, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/message-threads-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/message-threads-all.view.ts new file mode 100644 index 0000000000..fecd0ffe3d --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/message-threads-all.view.ts @@ -0,0 +1,55 @@ +import { msg } from '@lingui/core/macro'; + +import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE'; +import { + BASE_OBJECT_STANDARD_FIELD_IDS, + MESSAGE_THREAD_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const messageThreadsAllView = ( + objectMetadataItems: ObjectMetadataEntity[], + useCoreNaming = false, +) => { + const messageThreadObjectMetadata = objectMetadataItems.find( + (object) => object.standardId === STANDARD_OBJECT_IDS.messageThread, + ); + + if (!messageThreadObjectMetadata) { + throw new Error('MessageThread object metadata not found'); + } + + return { + name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Message Threads', + objectMetadataId: messageThreadObjectMetadata.id ?? '', + type: 'table', + key: 'INDEX', + position: 0, + icon: 'IconList', + kanbanFieldMetadataId: '', + filters: [], + fields: [ + { + fieldMetadataId: + messageThreadObjectMetadata.fields.find( + (field) => + field.standardId === MESSAGE_THREAD_STANDARD_FIELD_IDS.messages, + )?.id ?? '', + position: 0, + isVisible: true, + size: DEFAULT_VIEW_FIELD_SIZE, + }, + { + fieldMetadataId: + messageThreadObjectMetadata.fields.find( + (field) => + field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + )?.id ?? '', + position: 1, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/messages-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/messages-all.view.ts new file mode 100644 index 0000000000..b06d925567 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/messages-all.view.ts @@ -0,0 +1,104 @@ +import { msg } from '@lingui/core/macro'; + +import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE'; +import { + BASE_OBJECT_STANDARD_FIELD_IDS, + MESSAGE_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const messagesAllView = ( + objectMetadataItems: ObjectMetadataEntity[], + useCoreNaming = false, +) => { + const messageObjectMetadata = objectMetadataItems.find( + (object) => object.standardId === STANDARD_OBJECT_IDS.message, + ); + + if (!messageObjectMetadata) { + throw new Error('Message object metadata not found'); + } + + return { + name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Messages', + objectMetadataId: messageObjectMetadata.id ?? '', + type: 'table', + key: 'INDEX', + position: 0, + icon: 'IconList', + kanbanFieldMetadataId: '', + filters: [], + fields: [ + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => field.standardId === MESSAGE_STANDARD_FIELD_IDS.subject, + )?.id ?? '', + position: 0, + isVisible: true, + size: DEFAULT_VIEW_FIELD_SIZE, + }, + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => + field.standardId === MESSAGE_STANDARD_FIELD_IDS.messageThread, + )?.id ?? '', + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => + field.standardId === + MESSAGE_STANDARD_FIELD_IDS.messageParticipants, + )?.id ?? '', + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => + field.standardId === MESSAGE_STANDARD_FIELD_IDS.receivedAt, + )?.id ?? '', + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => + field.standardId === MESSAGE_STANDARD_FIELD_IDS.headerMessageId, + )?.id ?? '', + position: 4, + isVisible: true, + size: 180, + }, + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => field.standardId === MESSAGE_STANDARD_FIELD_IDS.text, + )?.id ?? '', + position: 5, + isVisible: true, + size: 200, + }, + { + fieldMetadataId: + messageObjectMetadata.fields.find( + (field) => + field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + )?.id ?? '', + position: 6, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workspace-members-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workspace-members-all.view.ts new file mode 100644 index 0000000000..685127e56b --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workspace-members-all.view.ts @@ -0,0 +1,132 @@ +import { msg } from '@lingui/core/macro'; + +import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE'; +import { + BASE_OBJECT_STANDARD_FIELD_IDS, + WORKSPACE_MEMBER_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const workspaceMembersAllView = ( + objectMetadataItems: ObjectMetadataEntity[], + useCoreNaming = false, +) => { + const workspaceMemberObjectMetadata = objectMetadataItems.find( + (object) => object.standardId === STANDARD_OBJECT_IDS.workspaceMember, + ); + + if (!workspaceMemberObjectMetadata) { + throw new Error('WorkspaceMember object metadata not found'); + } + + return { + name: useCoreNaming + ? msg`All {objectLabelPlural}` + : 'All Workspace Members', + objectMetadataId: workspaceMemberObjectMetadata.id ?? '', + type: 'table', + key: 'INDEX', + position: 0, + icon: 'IconList', + kanbanFieldMetadataId: '', + filters: [], + fields: [ + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === WORKSPACE_MEMBER_STANDARD_FIELD_IDS.name, + )?.id ?? '', + position: 0, + isVisible: true, + size: DEFAULT_VIEW_FIELD_SIZE, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === + WORKSPACE_MEMBER_STANDARD_FIELD_IDS.userEmail, + )?.id ?? '', + position: 1, + isVisible: true, + size: 180, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === + WORKSPACE_MEMBER_STANDARD_FIELD_IDS.avatarUrl, + )?.id ?? '', + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === + WORKSPACE_MEMBER_STANDARD_FIELD_IDS.colorScheme, + )?.id ?? '', + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === WORKSPACE_MEMBER_STANDARD_FIELD_IDS.locale, + )?.id ?? '', + position: 4, + isVisible: true, + size: 120, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === WORKSPACE_MEMBER_STANDARD_FIELD_IDS.timeZone, + )?.id ?? '', + position: 5, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === + WORKSPACE_MEMBER_STANDARD_FIELD_IDS.dateFormat, + )?.id ?? '', + position: 6, + isVisible: true, + size: 120, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === + WORKSPACE_MEMBER_STANDARD_FIELD_IDS.timeFormat, + )?.id ?? '', + position: 7, + isVisible: true, + size: 120, + }, + { + fieldMetadataId: + workspaceMemberObjectMetadata.fields.find( + (field) => + field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + )?.id ?? '', + position: 8, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts index fb53c70ae4..92a611d36d 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts @@ -2,9 +2,9 @@ import { registerEnumType } from '@nestjs/graphql'; import { msg } from '@lingui/core/macro'; import { + ActorMetadata, FieldMetadataType, RelationOnDeleteAction, - ActorMetadata, } from 'twenty-shared/types'; import { type WorkflowRunStepInfos } from 'twenty-shared/workflow'; @@ -92,6 +92,7 @@ export const SEARCH_FIELDS_FOR_WORKFLOW_RUNS: FieldTypeAndNameMetadata[] = [ labelIdentifierStandardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.name, icon: STANDARD_OBJECT_ICONS.workflowRun, }) +@WorkspaceIsSystem() @WorkspaceIsNotAuditLogged() @WorkspaceIsObjectUIReadOnly() export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts index ccf782b3ec..ce54c4b633 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts @@ -80,6 +80,7 @@ export const SEARCH_FIELDS_FOR_WORKFLOW_VERSIONS: FieldTypeAndNameMetadata[] = [ icon: STANDARD_OBJECT_ICONS.workflowVersion, labelIdentifierStandardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.name, }) +@WorkspaceIsSystem() @WorkspaceIsObjectUIReadOnly() export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({