From cfc00c7924b0789804bc8c43fb7ac31a84383b03 Mon Sep 17 00:00:00 2001 From: HKS07 <77383317+HKS07@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:31:21 +0530 Subject: [PATCH] Twnty-#6797 view/edit inactive feature (#6953) This PR resolves the issue raised under #6797. Earlier no view/edit option were present for inActive objects in data-model inside settings. To resolve that following changes were done: 1. `SettingsObjectFieldItemTableRow` was not passing the onEdit with url to `SettingsObjectFieldActiveActionDropdown` to redirect on clicking it. So passed onEdit there. 2. `SettingsObjectFieldActiveActionDropdown` was not implementing the onEdit functionality, so implemented that by creating `handleEdit()` function. 3. `SettingsObjectFieldEdit` was assuming only `activeObjectMetadata `will be coming to render on page. So, when inactive object was accessed the path not found error message was thrown. Thus did changes to manage both active and inactive objects by generalizing the `activeObjectMetadata ` -> `objectMetadata` `activeMetadataField `-> `metadataField`. 4. `findObjectMetadataItemBySlug `function was written inside `useFilteredObjectMetadataItems` for fetching active/inactive object. 5. Updated `SettingsObjectFieldEdit` button to show and change the active to inactive state and vice versa. 6. Test was written for `findObjectMetadataItemBySlug`. --------- Co-authored-by: Lucas Bordeau --- .../useFilteredObjectMetadataItems.test.tsx | 20 +++++ .../hooks/useFilteredObjectMetadataItems.ts | 6 ++ ...tingsObjectFieldDisabledActionDropdown.tsx | 20 ++++- .../SettingsObjectFieldItemTableRow.tsx | 1 + .../data-model/SettingsObjectFieldEdit.tsx | 89 +++++++++++-------- 5 files changed, 99 insertions(+), 37 deletions(-) diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx index c42b94fbbb..9bb8f01696 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx @@ -57,6 +57,26 @@ describe('useFilteredObjectMetadataItems', () => { }); }); + it('should findObjectMetadataItemBySlug', async () => { + const { result } = renderHook( + () => { + const setMetadataItems = useSetRecoilState(objectMetadataItemsState); + setMetadataItems(mockObjectMetadataItems); + + return useFilteredObjectMetadataItems(); + }, + { + wrapper: Wrapper, + }, + ); + + act(() => { + const res = result.current.findObjectMetadataItemBySlug('people'); + expect(res).toBeDefined(); + expect(res?.namePlural).toBe('people'); + }); + }); + it('should findObjectMetadataItemById', async () => { const { result } = renderHook( () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts index 2cf99ae123..61922bf55e 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts @@ -27,6 +27,11 @@ export const useFilteredObjectMetadataItems = () => { ({ isActive, isSystem }) => !isActive && !isSystem, ); + const findObjectMetadataItemBySlug = (slug: string) => + objectMetadataItems.find( + (objectMetadataItem) => getObjectSlug(objectMetadataItem) === slug, + ); + const findActiveObjectMetadataItemBySlug = (slug: string) => activeObjectMetadataItems.find( (activeObjectMetadataItem) => @@ -50,6 +55,7 @@ export const useFilteredObjectMetadataItems = () => { findObjectMetadataItemByNamePlural, inactiveObjectMetadataItems, objectMetadataItems, + findObjectMetadataItemBySlug, alphaSortedActiveObjectMetadataItems, }; }; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown.tsx index 6879667dc8..a6b68c6b5a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown.tsx @@ -1,4 +1,10 @@ -import { IconArchiveOff, IconDotsVertical, IconTrash } from 'twenty-ui'; +import { + IconArchiveOff, + IconDotsVertical, + IconEye, + IconPencil, + IconTrash, +} from 'twenty-ui'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -12,6 +18,7 @@ type SettingsObjectFieldInactiveActionDropdownProps = { isCustomField?: boolean; fieldType?: FieldMetadataType; onActivate: () => void; + onEdit: () => void; onDelete: () => void; scopeKey: string; }; @@ -20,6 +27,7 @@ export const SettingsObjectFieldInactiveActionDropdown = ({ onActivate, scopeKey, onDelete, + onEdit, isCustomField, }: SettingsObjectFieldInactiveActionDropdownProps) => { const dropdownId = `${scopeKey}-settings-field-disabled-action-dropdown`; @@ -36,6 +44,11 @@ export const SettingsObjectFieldInactiveActionDropdown = ({ closeDropdown(); }; + const handleEdit = () => { + onEdit(); + closeDropdown(); + }; + const isDeletable = isCustomField; return ( @@ -47,6 +60,11 @@ export const SettingsObjectFieldInactiveActionDropdown = ({ dropdownComponents={ + navigate(linkToNavigate)} onActivate={() => activateMetadataField(fieldMetadataItem)} onDelete={() => deleteMetadataField(fieldMetadataItem)} /> diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 353beeee86..5e4769913b 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -5,7 +5,12 @@ import pick from 'lodash.pick'; import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router-dom'; -import { H2Title, IconArchive, IconHierarchy2 } from 'twenty-ui'; +import { + H2Title, + IconArchive, + IconArchiveOff, + IconHierarchy2, +} from 'twenty-ui'; import { z } from 'zod'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; @@ -56,16 +61,15 @@ export const SettingsObjectFieldEdit = () => { const { enqueueSnackBar } = useSnackBar(); const { objectSlug = '', fieldSlug = '' } = useParams(); - const { findActiveObjectMetadataItemBySlug } = - useFilteredObjectMetadataItems(); + const { findObjectMetadataItemBySlug } = useFilteredObjectMetadataItems(); - const activeObjectMetadataItem = - findActiveObjectMetadataItemBySlug(objectSlug); + const objectMetadataItem = findObjectMetadataItemBySlug(objectSlug); - const { deactivateMetadataField } = useFieldMetadataItem(); - const activeMetadataField = activeObjectMetadataItem?.fields.find( - (metadataField) => - metadataField.isActive && getFieldSlug(metadataField) === fieldSlug, + const { deactivateMetadataField, activateMetadataField } = + useFieldMetadataItem(); + + const fieldMetadataItem = objectMetadataItem?.fields.find( + (fieldMetadataItem) => getFieldSlug(fieldMetadataItem) === fieldSlug, ); const getRelationMetadata = useGetRelationMetadata(); @@ -74,11 +78,11 @@ export const SettingsObjectFieldEdit = () => { const apolloClient = useApolloClient(); const { findManyRecordsQuery } = useFindManyRecordsQuery({ - objectNameSingular: activeObjectMetadataItem?.nameSingular || '', + objectNameSingular: objectMetadataItem?.nameSingular || '', }); const refetchRecords = async () => { - if (!activeObjectMetadataItem) return; + if (!objectMetadataItem) return; await apolloClient.query({ query: findManyRecordsQuery, fetchPolicy: 'network-only', @@ -89,27 +93,29 @@ export const SettingsObjectFieldEdit = () => { mode: 'onTouched', resolver: zodResolver(settingsFieldFormSchema()), values: { - icon: activeMetadataField?.icon ?? 'Icon123', - type: activeMetadataField?.type as SettingsSupportedFieldType, - label: activeMetadataField?.label ?? '', - description: activeMetadataField?.description, + icon: fieldMetadataItem?.icon ?? 'Icon', + type: fieldMetadataItem?.type as SettingsSupportedFieldType, + label: fieldMetadataItem?.label ?? '', + description: fieldMetadataItem?.description, }, }); useEffect(() => { - if (!activeObjectMetadataItem || !activeMetadataField) { + if (!objectMetadataItem || !fieldMetadataItem) { navigate(AppPath.NotFound); } - }, [activeMetadataField, activeObjectMetadataItem, navigate]); + }, [fieldMetadataItem, objectMetadataItem, navigate]); const { isDirty, isValid, isSubmitting } = formConfig.formState; const canSave = isDirty && isValid && !isSubmitting; - if (!activeObjectMetadataItem || !activeMetadataField) return null; + if (!isDefined(objectMetadataItem) || !isDefined(fieldMetadataItem)) { + return null; + } const isLabelIdentifier = isLabelIdentifierField({ - fieldMetadataItem: activeMetadataField, - objectMetadataItem: activeObjectMetadataItem, + fieldMetadataItem: fieldMetadataItem, + objectMetadataItem: objectMetadataItem, }); const handleSave = async ( @@ -125,7 +131,7 @@ export const SettingsObjectFieldEdit = () => { ) { const { relationFieldMetadataItem } = getRelationMetadata({ - fieldMetadataItem: activeMetadataField, + fieldMetadataItem: fieldMetadataItem, }) ?? {}; if (isDefined(relationFieldMetadataItem)) { @@ -145,7 +151,7 @@ export const SettingsObjectFieldEdit = () => { ); await updateOneFieldMetadataItem({ - fieldMetadataIdToUpdate: activeMetadataField.id, + fieldMetadataIdToUpdate: fieldMetadataItem.id, updatePayload: formattedInput, }); } @@ -161,12 +167,17 @@ export const SettingsObjectFieldEdit = () => { }; const handleDeactivate = async () => { - await deactivateMetadataField(activeMetadataField); + await deactivateMetadataField(fieldMetadataItem); + navigate(`/settings/objects/${objectSlug}`); + }; + + const handleActivate = async () => { + await activateMetadataField(fieldMetadataItem); navigate(`/settings/objects/${objectSlug}`); }; const shouldDisplaySaveAndCancel = - canPersistFieldMetadataItemUpdate(activeMetadataField); + canPersistFieldMetadataItemUpdate(fieldMetadataItem); return ( @@ -174,7 +185,7 @@ export const SettingsObjectFieldEdit = () => { { href: '/settings/objects', }, { - children: activeObjectMetadataItem.labelPlural, + children: objectMetadataItem.labelPlural, href: `/settings/objects/${objectSlug}`, }, { - children: activeMetadataField.label, + children: fieldMetadataItem.label, }, ]} actionButton={ @@ -210,8 +221,8 @@ export const SettingsObjectFieldEdit = () => { description="The name and icon of this field" /> @@ -219,8 +230,8 @@ export const SettingsObjectFieldEdit = () => {
@@ -229,8 +240,8 @@ export const SettingsObjectFieldEdit = () => { description="The description of this field" />
{!isLabelIdentifier && ( @@ -240,11 +251,17 @@ export const SettingsObjectFieldEdit = () => { description="Deactivate this field" />