mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-25 09:13:22 +03:00
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 <bordeau.lucas@gmail.com>
This commit is contained in:
parent
df8bb84b35
commit
cfc00c7924
@ -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(
|
||||
() => {
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
@ -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={
|
||||
<DropdownMenu width="160px">
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={isCustomField ? 'Edit' : 'View'}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Activate"
|
||||
LeftIcon={IconArchiveOff}
|
||||
|
@ -269,6 +269,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
<SettingsObjectFieldInactiveActionDropdown
|
||||
isCustomField={fieldMetadataItem.isCustom === true}
|
||||
scopeKey={fieldMetadataItem.id}
|
||||
onEdit={() => navigate(linkToNavigate)}
|
||||
onActivate={() => activateMetadataField(fieldMetadataItem)}
|
||||
onDelete={() => deleteMetadataField(fieldMetadataItem)}
|
||||
/>
|
||||
|
@ -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 (
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
@ -174,7 +185,7 @@ export const SettingsObjectFieldEdit = () => {
|
||||
<FormProvider {...formConfig}>
|
||||
<SubMenuTopBarContainer
|
||||
Icon={IconHierarchy2}
|
||||
title={activeMetadataField?.label}
|
||||
title={fieldMetadataItem?.label}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
@ -185,11 +196,11 @@ 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"
|
||||
/>
|
||||
<SettingsDataModelFieldIconLabelForm
|
||||
disabled={!activeMetadataField.isCustom}
|
||||
fieldMetadataItem={activeMetadataField}
|
||||
disabled={!fieldMetadataItem.isCustom}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
maxLength={FIELD_NAME_MAXIMUM_LENGTH}
|
||||
/>
|
||||
</Section>
|
||||
@ -219,8 +230,8 @@ export const SettingsObjectFieldEdit = () => {
|
||||
<H2Title title="Values" description="The values of this field" />
|
||||
<SettingsDataModelFieldSettingsFormCard
|
||||
disableCurrencyForm
|
||||
fieldMetadataItem={activeMetadataField}
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
@ -229,8 +240,8 @@ export const SettingsObjectFieldEdit = () => {
|
||||
description="The description of this field"
|
||||
/>
|
||||
<SettingsDataModelFieldDescriptionForm
|
||||
disabled={!activeMetadataField.isCustom}
|
||||
fieldMetadataItem={activeMetadataField}
|
||||
disabled={!fieldMetadataItem.isCustom}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
/>
|
||||
</Section>
|
||||
{!isLabelIdentifier && (
|
||||
@ -240,11 +251,17 @@ export const SettingsObjectFieldEdit = () => {
|
||||
description="Deactivate this field"
|
||||
/>
|
||||
<Button
|
||||
Icon={IconArchive}
|
||||
Icon={
|
||||
fieldMetadataItem.isActive ? IconArchive : IconArchiveOff
|
||||
}
|
||||
variant="secondary"
|
||||
title="Deactivate"
|
||||
title={fieldMetadataItem.isActive ? 'Deactivate' : 'Activate'}
|
||||
size="small"
|
||||
onClick={handleDeactivate}
|
||||
onClick={
|
||||
fieldMetadataItem.isActive
|
||||
? handleDeactivate
|
||||
: handleActivate
|
||||
}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
|
Loading…
Reference in New Issue
Block a user