Support custom object renaming (#7504)
This PR was created by [GitStart](https://gitstart.com/) to address the requirements from this ticket: [TWNTY-5491](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-5491). This ticket was imported from: [TWNTY-5491](https://github.com/twentyhq/twenty/issues/5491) --- ### Description **How To Test:**\ 1. Reset db using `npx nx database:reset twenty-server` on this PR 1. Run both backend and frontend 2. Navigate to `settings/data-model/objects/ `page 3. Select a `Custom `object from the list or create a new `Custom `object 4. Navigate to custom object details page and click on edit button 5. Finally edit the object details. **Issues and bugs** The Typecheck is failing but we could not see this error locally There is a bug after updating the label of a custom object. View title is not updated till refreshing the page. We could not find a consistent way to update this, should we reload the page after editing an object? ### Demo <https://www.loom.com/share/64ecb57efad7498d99085cb11480b5dd?sid=28d0868c-e54f-454d-8432-3f789be9e2b7> ### Refs #5491 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu> Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
committed by
GitHub
parent
c6ef14acc4
commit
414f2ac498
@ -1,5 +1,6 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
|
||||
import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem';
|
||||
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
||||
@ -20,13 +21,16 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import pick from 'lodash.pick';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { Button, H2Title, IconArchive } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
|
||||
const objectEditFormSchema = z
|
||||
.object({})
|
||||
@ -45,6 +49,9 @@ export const SettingsObjectEdit = () => {
|
||||
const { findActiveObjectMetadataItemBySlug } =
|
||||
useFilteredObjectMetadataItems();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
const { lastVisitedObjectMetadataItemId } =
|
||||
useLastVisitedObjectMetadataItem();
|
||||
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
|
||||
|
||||
const activeObjectMetadataItem =
|
||||
findActiveObjectMetadataItemBySlug(objectSlug);
|
||||
@ -56,6 +63,10 @@ export const SettingsObjectEdit = () => {
|
||||
resolver: zodResolver(objectEditFormSchema),
|
||||
});
|
||||
|
||||
const setNavigationMemorizedUrl = useSetRecoilState(
|
||||
navigationMemorizedUrlState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObjectMetadataItem) navigate(AppPath.NotFound);
|
||||
}, [activeObjectMetadataItem, navigate]);
|
||||
@ -65,25 +76,72 @@ export const SettingsObjectEdit = () => {
|
||||
const { isDirty, isValid, isSubmitting } = formConfig.formState;
|
||||
const canSave = isDirty && isValid && !isSubmitting;
|
||||
|
||||
const handleSave = async (
|
||||
const getUpdatePayload = (
|
||||
formValues: SettingsDataModelObjectEditFormValues,
|
||||
) => {
|
||||
let values = formValues;
|
||||
if (
|
||||
formValues.shouldSyncLabelAndName ??
|
||||
activeObjectMetadataItem.shouldSyncLabelAndName
|
||||
) {
|
||||
values = {
|
||||
...values,
|
||||
...(values.labelSingular
|
||||
? {
|
||||
nameSingular: computeMetadataNameFromLabelOrThrow(
|
||||
formValues.labelSingular,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...(values.labelPlural
|
||||
? {
|
||||
namePlural: computeMetadataNameFromLabelOrThrow(
|
||||
formValues.labelPlural,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
const dirtyFieldKeys = Object.keys(
|
||||
formConfig.formState.dirtyFields,
|
||||
) as (keyof SettingsDataModelObjectEditFormValues)[];
|
||||
|
||||
return settingsUpdateObjectInputSchema.parse(
|
||||
pick(values, [
|
||||
...dirtyFieldKeys,
|
||||
...(values.namePlural ? ['namePlural'] : []),
|
||||
...(values.nameSingular ? ['nameSingular'] : []),
|
||||
]),
|
||||
);
|
||||
};
|
||||
|
||||
const handleSave = async (
|
||||
formValues: SettingsDataModelObjectEditFormValues,
|
||||
) => {
|
||||
try {
|
||||
const updatePayload = getUpdatePayload(formValues);
|
||||
await updateOneObjectMetadataItem({
|
||||
idToUpdate: activeObjectMetadataItem.id,
|
||||
updatePayload: settingsUpdateObjectInputSchema.parse(
|
||||
pick(formValues, dirtyFieldKeys),
|
||||
),
|
||||
updatePayload,
|
||||
});
|
||||
|
||||
const objectNamePluralForRedirection =
|
||||
updatePayload.namePlural ?? activeObjectMetadataItem.namePlural;
|
||||
|
||||
if (lastVisitedObjectMetadataItemId === activeObjectMetadataItem.id) {
|
||||
const lastVisitedView = getLastVisitedViewIdFromObjectMetadataItemId(
|
||||
activeObjectMetadataItem.id,
|
||||
);
|
||||
setNavigationMemorizedUrl(
|
||||
`/objects/${objectNamePluralForRedirection}?view=${lastVisitedView}`,
|
||||
);
|
||||
}
|
||||
|
||||
navigate(
|
||||
`${settingsObjectsPagePath}/${getObjectSlug({
|
||||
...formValues,
|
||||
namePlural: formValues.labelPlural,
|
||||
...updatePayload,
|
||||
namePlural: objectNamePluralForRedirection,
|
||||
})}`,
|
||||
);
|
||||
} catch (error) {
|
||||
@ -142,7 +200,7 @@ export const SettingsObjectEdit = () => {
|
||||
/>
|
||||
<SettingsDataModelObjectAboutForm
|
||||
disabled={!activeObjectMetadataItem.isCustom}
|
||||
disableNameEdit
|
||||
disableNameEdit={!activeObjectMetadataItem.isCustom}
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
@ -2,5 +2,8 @@ import { METADATA_NAME_VALID_PATTERN } from '~/pages/settings/data-model/constan
|
||||
import { transliterateAndFormatOrThrow } from '~/pages/settings/data-model/utils/transliterate-and-format.utils';
|
||||
|
||||
export const computeMetadataNameFromLabelOrThrow = (label: string): string => {
|
||||
if (label === '') {
|
||||
return '';
|
||||
}
|
||||
return transliterateAndFormatOrThrow(label, METADATA_NAME_VALID_PATTERN);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user