Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,182 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import {
|
||||
SettingsObjectFieldItemTableRow,
|
||||
StyledObjectFieldTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconMinus, IconPlus, IconSettings } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
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 { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
|
||||
const StyledSection = styled(Section)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledAddCustomFieldButton = styled(Button)`
|
||||
align-self: flex-end;
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const SettingsObjectNewFieldStep1 = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { objectSlug = '' } = useParams();
|
||||
const { findActiveObjectMetadataItemBySlug } =
|
||||
useObjectMetadataItemForSettings();
|
||||
|
||||
const activeObjectMetadataItem =
|
||||
findActiveObjectMetadataItemBySlug(objectSlug);
|
||||
|
||||
const { activateMetadataField, disableMetadataField } =
|
||||
useFieldMetadataItem();
|
||||
const [metadataFields, setMetadataFields] = useState(
|
||||
activeObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
const activeMetadataFields = metadataFields.filter((field) => field.isActive);
|
||||
const disabledMetadataFields = metadataFields.filter(
|
||||
(field) => !field.isActive,
|
||||
);
|
||||
|
||||
const canSave = metadataFields.some(
|
||||
(field, index) =>
|
||||
field.isActive !== activeObjectMetadataItem?.fields[index].isActive,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObjectMetadataItem) {
|
||||
navigate(AppPath.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!metadataFields.length)
|
||||
setMetadataFields(activeObjectMetadataItem.fields);
|
||||
}, [activeObjectMetadataItem, metadataFields.length, navigate]);
|
||||
|
||||
if (!activeObjectMetadataItem) return null;
|
||||
|
||||
const handleToggleField = (fieldMetadataId: string) =>
|
||||
setMetadataFields((previousFields) =>
|
||||
previousFields.map((field) =>
|
||||
field.id === fieldMetadataId
|
||||
? { ...field, isActive: !field.isActive }
|
||||
: field,
|
||||
),
|
||||
);
|
||||
|
||||
const handleSave = async () => {
|
||||
await Promise.all(
|
||||
metadataFields.map((metadataField, index) => {
|
||||
if (
|
||||
metadataField.isActive ===
|
||||
activeObjectMetadataItem.fields[index].isActive
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return metadataField.isActive
|
||||
? activateMetadataField(metadataField)
|
||||
: disableMetadataField(metadataField);
|
||||
}),
|
||||
);
|
||||
|
||||
navigate(`/settings/objects/${objectSlug}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<SettingsHeaderContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{ children: 'New Field' },
|
||||
]}
|
||||
/>
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
onCancel={() => navigate(`/settings/objects/${objectSlug}`)}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</SettingsHeaderContainer>
|
||||
<StyledSection>
|
||||
<H2Title
|
||||
title="Check disabled fields"
|
||||
description="Before creating a custom field, check if it already exists in the disabled section."
|
||||
/>
|
||||
<Table>
|
||||
<StyledObjectFieldTableRow>
|
||||
<TableHeader>Name</TableHeader>
|
||||
<TableHeader>Field type</TableHeader>
|
||||
<TableHeader>Data type</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectFieldTableRow>
|
||||
{!!activeMetadataFields.length && (
|
||||
<TableSection isInitiallyExpanded={false} title="Active">
|
||||
{activeMetadataFields.map((field) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={field.id}
|
||||
fieldMetadataItem={field}
|
||||
ActionIcon={
|
||||
<LightIconButton
|
||||
Icon={IconMinus}
|
||||
accent="tertiary"
|
||||
onClick={() => handleToggleField(field.id)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</TableSection>
|
||||
)}
|
||||
{!!disabledMetadataFields.length && (
|
||||
<TableSection title="Disabled">
|
||||
{disabledMetadataFields.map((field) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={field.name}
|
||||
fieldMetadataItem={field}
|
||||
ActionIcon={
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
accent="tertiary"
|
||||
onClick={() => handleToggleField(field.id)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</TableSection>
|
||||
)}
|
||||
</Table>
|
||||
<StyledAddCustomFieldButton
|
||||
Icon={IconPlus}
|
||||
title="Add Custom Field"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
navigate(`/settings/objects/${objectSlug}/new-field/step-2`)
|
||||
}
|
||||
/>
|
||||
</StyledSection>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,304 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useCreateOneRelationMetadataItem } from '@/object-metadata/hooks/useCreateOneRelationMetadataItem';
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsObjectFieldFormSection } from '@/settings/data-model/components/SettingsObjectFieldFormSection';
|
||||
import { SettingsObjectFieldTypeSelectSection } from '@/settings/data-model/components/SettingsObjectFieldTypeSelectSection';
|
||||
import { useFieldMetadataForm } from '@/settings/data-model/hooks/useFieldMetadataForm';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconSettings } from '@/ui/display/icon';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { View } from '@/views/types/View';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const SettingsObjectNewFieldStep2 = () => {
|
||||
const navigate = useNavigate();
|
||||
const { objectSlug = '' } = useParams();
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const {
|
||||
findActiveObjectMetadataItemBySlug,
|
||||
findObjectMetadataItemById,
|
||||
findObjectMetadataItemByNamePlural,
|
||||
} = useObjectMetadataItemForSettings();
|
||||
|
||||
const activeObjectMetadataItem =
|
||||
findActiveObjectMetadataItemBySlug(objectSlug);
|
||||
const { createMetadataField } = useFieldMetadataItem();
|
||||
|
||||
const isRelationFieldTypeEnabled = useIsFeatureEnabled(
|
||||
'IS_RELATION_FIELD_TYPE_ENABLED',
|
||||
);
|
||||
|
||||
const {
|
||||
formValues,
|
||||
handleFormChange,
|
||||
initForm,
|
||||
isValid: canSave,
|
||||
validatedFormValues,
|
||||
} = useFieldMetadataForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObjectMetadataItem) {
|
||||
navigate(AppPath.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
initForm({
|
||||
relation: {
|
||||
field: { icon: activeObjectMetadataItem.icon },
|
||||
objectMetadataId:
|
||||
findObjectMetadataItemByNamePlural('people')?.id || '',
|
||||
},
|
||||
});
|
||||
}, [
|
||||
activeObjectMetadataItem,
|
||||
findObjectMetadataItemByNamePlural,
|
||||
initForm,
|
||||
|
||||
navigate,
|
||||
]);
|
||||
|
||||
const [objectViews, setObjectViews] = useState<View[]>([]);
|
||||
const [relationObjectViews, setRelationObjectViews] = useState<View[]>([]);
|
||||
|
||||
const { modifyRecordFromCache: modifyViewFromCache } = useObjectMetadataItem({
|
||||
objectNameSingular: 'view',
|
||||
});
|
||||
|
||||
useFindManyRecords({
|
||||
objectNameSingular: 'view',
|
||||
filter: {
|
||||
type: { eq: ViewType.Table },
|
||||
objectMetadataId: { eq: activeObjectMetadataItem?.id },
|
||||
},
|
||||
onCompleted: async (data: PaginatedRecordTypeResults<View>) => {
|
||||
const views = data.edges;
|
||||
|
||||
if (!views) return;
|
||||
|
||||
setObjectViews(data.edges.map(({ node }) => node));
|
||||
},
|
||||
});
|
||||
|
||||
useFindManyRecords({
|
||||
objectNameSingular: 'view',
|
||||
skip: !formValues.relation?.objectMetadataId,
|
||||
filter: {
|
||||
type: { eq: ViewType.Table },
|
||||
objectMetadataId: { eq: formValues.relation?.objectMetadataId },
|
||||
},
|
||||
onCompleted: async (data: PaginatedRecordTypeResults<View>) => {
|
||||
const views = data.edges;
|
||||
|
||||
if (!views) return;
|
||||
|
||||
setRelationObjectViews(data.edges.map(({ node }) => node));
|
||||
},
|
||||
});
|
||||
|
||||
const { createOneRelationMetadataItem: createOneRelationMetadata } =
|
||||
useCreateOneRelationMetadataItem();
|
||||
|
||||
if (!activeObjectMetadataItem) return null;
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!validatedFormValues) return;
|
||||
|
||||
try {
|
||||
if (validatedFormValues.type === FieldMetadataType.Relation) {
|
||||
const createdRelation = await createOneRelationMetadata({
|
||||
relationType: validatedFormValues.relation.type,
|
||||
field: {
|
||||
description: validatedFormValues.description,
|
||||
icon: validatedFormValues.icon,
|
||||
label: validatedFormValues.label,
|
||||
},
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
connect: {
|
||||
field: {
|
||||
icon: validatedFormValues.relation.field.icon,
|
||||
label: validatedFormValues.relation.field.label,
|
||||
},
|
||||
objectMetadataId: validatedFormValues.relation.objectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
const relationObjectMetadataItem = findObjectMetadataItemById(
|
||||
validatedFormValues.relation.objectMetadataId,
|
||||
);
|
||||
|
||||
objectViews.forEach(async (view) => {
|
||||
const viewFieldToCreate = {
|
||||
viewId: view.id,
|
||||
fieldMetadataId:
|
||||
validatedFormValues.relation.type === 'MANY_TO_ONE'
|
||||
? createdRelation.data?.createOneRelation.toFieldMetadataId
|
||||
: createdRelation.data?.createOneRelation.fromFieldMetadataId,
|
||||
position: activeObjectMetadataItem.fields.length,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
};
|
||||
|
||||
modifyViewFromCache(view.id, {
|
||||
// Todo fix typing
|
||||
viewFields: (viewFields: any) => {
|
||||
return {
|
||||
edges: viewFields.edges.concat({ node: viewFieldToCreate }),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
relationObjectViews.forEach(async (view) => {
|
||||
const viewFieldToCreate = {
|
||||
viewId: view.id,
|
||||
fieldMetadataId:
|
||||
validatedFormValues.relation.type === 'MANY_TO_ONE'
|
||||
? createdRelation.data?.createOneRelation.fromFieldMetadataId
|
||||
: createdRelation.data?.createOneRelation.toFieldMetadataId,
|
||||
position: relationObjectMetadataItem?.fields.length,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
};
|
||||
modifyViewFromCache(view.id, {
|
||||
// Todo fix typing
|
||||
viewFields: (viewFields: any) => {
|
||||
return {
|
||||
edges: viewFields.edges.concat({ node: viewFieldToCreate }),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const createdMetadataField = await createMetadataField({
|
||||
description: validatedFormValues.description,
|
||||
icon: validatedFormValues.icon,
|
||||
label: validatedFormValues.label ?? '',
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
type: validatedFormValues.type,
|
||||
options:
|
||||
validatedFormValues.type === FieldMetadataType.Select
|
||||
? validatedFormValues.select
|
||||
: undefined,
|
||||
});
|
||||
|
||||
objectViews.forEach(async (view) => {
|
||||
const viewFieldToCreate = {
|
||||
viewId: view.id,
|
||||
fieldMetadataId: createdMetadataField.data?.createOneField.id,
|
||||
position: activeObjectMetadataItem.fields.length,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
};
|
||||
|
||||
modifyViewFromCache(view.id, {
|
||||
// Todo fix typing
|
||||
viewFields: (viewFields: any) => {
|
||||
return {
|
||||
edges: viewFields.edges.concat({ node: viewFieldToCreate }),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
navigate(`/settings/objects/${objectSlug}`);
|
||||
} catch (error) {
|
||||
enqueueSnackBar((error as Error).message, {
|
||||
variant: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const excludedFieldTypes = [
|
||||
FieldMetadataType.Currency,
|
||||
FieldMetadataType.Email,
|
||||
FieldMetadataType.FullName,
|
||||
FieldMetadataType.Link,
|
||||
FieldMetadataType.MultiSelect,
|
||||
FieldMetadataType.Numeric,
|
||||
FieldMetadataType.Phone,
|
||||
FieldMetadataType.Probability,
|
||||
FieldMetadataType.Rating,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.Uuid,
|
||||
];
|
||||
|
||||
if (!isRelationFieldTypeEnabled) {
|
||||
excludedFieldTypes.push(FieldMetadataType.Relation);
|
||||
}
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<SettingsHeaderContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{
|
||||
children: activeObjectMetadataItem.labelPlural,
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{ children: 'New Field' },
|
||||
]}
|
||||
/>
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
onCancel={() => navigate(`/settings/objects/${objectSlug}`)}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</SettingsHeaderContainer>
|
||||
<SettingsObjectFieldFormSection
|
||||
iconKey={formValues.icon}
|
||||
name={formValues.label}
|
||||
description={formValues.description}
|
||||
onChange={handleFormChange}
|
||||
/>
|
||||
<SettingsObjectFieldTypeSelectSection
|
||||
excludedFieldTypes={excludedFieldTypes}
|
||||
fieldMetadata={{
|
||||
icon: formValues.icon,
|
||||
label: formValues.label || 'Employees',
|
||||
}}
|
||||
objectMetadataId={activeObjectMetadataItem.id}
|
||||
onChange={handleFormChange}
|
||||
values={{
|
||||
type: formValues.type,
|
||||
relation: formValues.relation,
|
||||
select: formValues.select,
|
||||
}}
|
||||
/>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user