@ -1,12 +1,28 @@
|
||||
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
|
||||
import { Field } from '~/generated/graphql';
|
||||
|
||||
import { formatMetadataFieldInput } from '../utils/formatMetadataFieldInput';
|
||||
|
||||
import { useCreateOneMetadataField } from './useCreateOneMetadataField';
|
||||
import { useDeleteOneMetadataField } from './useDeleteOneMetadataField';
|
||||
import { useUpdateOneMetadataField } from './useUpdateOneMetadataField';
|
||||
|
||||
export const useFieldMetadata = () => {
|
||||
const { createOneMetadataField } = useCreateOneMetadataField();
|
||||
const { updateOneMetadataField } = useUpdateOneMetadataField();
|
||||
const { deleteOneMetadataField } = useDeleteOneMetadataField();
|
||||
|
||||
const createField = (
|
||||
input: Pick<Field, 'label' | 'icon' | 'description'> & {
|
||||
objectId: string;
|
||||
type: ObjectFieldDataType;
|
||||
},
|
||||
) =>
|
||||
createOneMetadataField({
|
||||
...formatMetadataFieldInput(input),
|
||||
objectId: input.objectId,
|
||||
});
|
||||
|
||||
const activateField = (metadataField: Field) =>
|
||||
updateOneMetadataField({
|
||||
fieldIdToUpdate: metadataField.id,
|
||||
@ -24,6 +40,7 @@ export const useFieldMetadata = () => {
|
||||
|
||||
return {
|
||||
activateField,
|
||||
createField,
|
||||
disableField,
|
||||
eraseField,
|
||||
};
|
||||
|
||||
17
front/src/modules/metadata/utils/formatMetadataFieldInput.ts
Normal file
17
front/src/modules/metadata/utils/formatMetadataFieldInput.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import toCamelCase from 'lodash.camelcase';
|
||||
import upperFirst from 'lodash.upperfirst';
|
||||
|
||||
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
|
||||
import { Field } from '~/generated-metadata/graphql';
|
||||
|
||||
export const formatMetadataFieldInput = (
|
||||
input: Pick<Field, 'label' | 'icon' | 'description'> & {
|
||||
type: ObjectFieldDataType;
|
||||
},
|
||||
) => ({
|
||||
description: input.description?.trim() ?? null,
|
||||
icon: input.icon,
|
||||
label: input.label.trim(),
|
||||
name: upperFirst(toCamelCase(input.label.trim())),
|
||||
type: input.type,
|
||||
});
|
||||
@ -0,0 +1,4 @@
|
||||
const metadataLabelValidationPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/;
|
||||
|
||||
export const validateMetadataLabel = (value: string) =>
|
||||
!!value.match(metadataLabelValidationPattern);
|
||||
@ -1,4 +0,0 @@
|
||||
const metadataObjectLabelValidationPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/;
|
||||
|
||||
export const validateMetadataObjectLabel = (value: string) =>
|
||||
!!value.match(metadataObjectLabelValidationPattern);
|
||||
@ -1,5 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { validateMetadataLabel } from '@/metadata/utils/validateMetadataLabel';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
@ -13,8 +14,8 @@ type SettingsObjectFieldFormSectionProps = {
|
||||
iconKey?: string;
|
||||
onChange?: (
|
||||
formValues: Partial<{
|
||||
iconKey: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}>,
|
||||
) => void;
|
||||
@ -42,13 +43,17 @@ export const SettingsObjectFieldFormSection = ({
|
||||
<StyledInputsContainer>
|
||||
<IconPicker
|
||||
selectedIconKey={iconKey}
|
||||
onChange={(value) => onChange?.({ iconKey: value.iconKey })}
|
||||
onChange={(value) => onChange?.({ icon: value.iconKey })}
|
||||
variant="primary"
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Employees"
|
||||
value={name}
|
||||
onChange={(value) => onChange?.({ name: value })}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange?.({ label: value });
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
@ -10,6 +10,9 @@ type SettingsObjectFieldTypeSelectSectionProps = {
|
||||
onChange: (value: ObjectFieldDataType) => void;
|
||||
};
|
||||
|
||||
// TODO: remove "relation" type for now, add it back when the backend is ready.
|
||||
const { relation: _, ...dataTypesWithoutRelation } = dataTypes;
|
||||
|
||||
export const SettingsObjectFieldTypeSelectSection = ({
|
||||
type,
|
||||
onChange,
|
||||
@ -23,10 +26,12 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
||||
dropdownScopeId="object-field-type-select"
|
||||
value={type}
|
||||
onChange={onChange}
|
||||
options={Object.entries(dataTypes).map(([key, dataType]) => ({
|
||||
value: key as ObjectFieldDataType,
|
||||
...dataType,
|
||||
}))}
|
||||
options={Object.entries(dataTypesWithoutRelation).map(
|
||||
([key, dataType]) => ({
|
||||
value: key as ObjectFieldDataType,
|
||||
...dataType,
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { validateMetadataObjectLabel } from '@/metadata/utils/validateMetadataObjectLabel';
|
||||
import { validateMetadataLabel } from '@/metadata/utils/validateMetadataLabel';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
@ -45,7 +45,7 @@ export const SettingsObjectFormSection = ({
|
||||
placeholder="Investor"
|
||||
value={singularName}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataObjectLabel(value)) {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange?.({ labelSingular: value });
|
||||
}
|
||||
}}
|
||||
@ -57,7 +57,7 @@ export const SettingsObjectFormSection = ({
|
||||
placeholder="Investors"
|
||||
value={pluralName}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataObjectLabel(value)) {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange?.({ labelPlural: value });
|
||||
}
|
||||
}}
|
||||
|
||||
@ -3,9 +3,7 @@ import {
|
||||
IconLink,
|
||||
IconNumbers,
|
||||
IconPlug,
|
||||
IconSocial,
|
||||
IconTextSize,
|
||||
IconUserCircle,
|
||||
} from '@/ui/display/icon';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
@ -17,9 +15,7 @@ export const dataTypes: Record<
|
||||
> = {
|
||||
number: { label: 'Number', Icon: IconNumbers },
|
||||
text: { label: 'Text', Icon: IconTextSize },
|
||||
link: { label: 'Link', Icon: IconLink },
|
||||
teammate: { label: 'Team member', Icon: IconUserCircle },
|
||||
url: { label: 'Link', Icon: IconLink },
|
||||
boolean: { label: 'True/False', Icon: IconCheck },
|
||||
relation: { label: 'Relation', Icon: IconPlug },
|
||||
social: { label: 'Social', Icon: IconSocial },
|
||||
};
|
||||
|
||||
@ -1,42 +1,4 @@
|
||||
import {
|
||||
IconBrandLinkedin,
|
||||
IconBrandTwitter,
|
||||
IconBuildingSkyscraper,
|
||||
IconCurrencyDollar,
|
||||
IconFreeRights,
|
||||
IconGraph,
|
||||
IconHeadphones,
|
||||
IconLink,
|
||||
IconLuggage,
|
||||
IconMouse2,
|
||||
IconPlane,
|
||||
IconTarget,
|
||||
IconUser,
|
||||
IconUserCircle,
|
||||
IconUsers,
|
||||
} from '@/ui/display/icon';
|
||||
|
||||
import { ObjectFieldItem } from '../types/ObjectFieldItem';
|
||||
|
||||
export const activeObjectItems = [
|
||||
{
|
||||
name: 'Companies',
|
||||
singularName: 'Company',
|
||||
Icon: IconBuildingSkyscraper,
|
||||
type: 'standard',
|
||||
fields: 23,
|
||||
instances: 165,
|
||||
description: 'Lorem ipsum',
|
||||
},
|
||||
{
|
||||
name: 'People',
|
||||
singularName: 'Person',
|
||||
Icon: IconUser,
|
||||
type: 'standard',
|
||||
fields: 16,
|
||||
instances: 462,
|
||||
},
|
||||
];
|
||||
import { IconMouse2 } from '@/ui/display/icon';
|
||||
|
||||
export const standardObjects = [
|
||||
{
|
||||
@ -52,92 +14,3 @@ export const standardObjects = [
|
||||
description: 'Individuals who interact with your website',
|
||||
},
|
||||
];
|
||||
|
||||
export const disabledObjectItems = [
|
||||
{
|
||||
name: 'Travels',
|
||||
Icon: IconLuggage,
|
||||
type: 'custom',
|
||||
fields: 23,
|
||||
instances: 165,
|
||||
},
|
||||
{
|
||||
name: 'Flights',
|
||||
Icon: IconPlane,
|
||||
type: 'custom',
|
||||
fields: 23,
|
||||
instances: 165,
|
||||
},
|
||||
];
|
||||
|
||||
export const activeFieldItems: ObjectFieldItem[] = [
|
||||
{
|
||||
name: 'People',
|
||||
Icon: IconUser,
|
||||
type: 'standard',
|
||||
dataType: 'relation',
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
Icon: IconLink,
|
||||
type: 'standard',
|
||||
dataType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Linkedin',
|
||||
Icon: IconBrandLinkedin,
|
||||
type: 'standard',
|
||||
dataType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Account Owner',
|
||||
Icon: IconUserCircle,
|
||||
type: 'standard',
|
||||
dataType: 'teammate',
|
||||
},
|
||||
{
|
||||
name: 'Employees',
|
||||
Icon: IconUsers,
|
||||
type: 'custom',
|
||||
dataType: 'number',
|
||||
},
|
||||
];
|
||||
|
||||
export const disabledFieldItems: ObjectFieldItem[] = [
|
||||
{
|
||||
name: 'ICP',
|
||||
Icon: IconTarget,
|
||||
type: 'standard',
|
||||
dataType: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
Icon: IconBrandTwitter,
|
||||
type: 'standard',
|
||||
dataType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Annual revenue',
|
||||
Icon: IconCurrencyDollar,
|
||||
type: 'standard',
|
||||
dataType: 'number',
|
||||
},
|
||||
{
|
||||
name: 'Is public',
|
||||
Icon: IconGraph,
|
||||
type: 'standard',
|
||||
dataType: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'Free tier?',
|
||||
Icon: IconFreeRights,
|
||||
type: 'custom',
|
||||
dataType: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'Priority support',
|
||||
Icon: IconHeadphones,
|
||||
type: 'custom',
|
||||
dataType: 'boolean',
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
export type ObjectFieldDataType =
|
||||
| 'boolean'
|
||||
| 'link'
|
||||
| 'number'
|
||||
| 'relation'
|
||||
| 'social'
|
||||
| 'teammate'
|
||||
| 'text';
|
||||
| 'text'
|
||||
| 'url';
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
import { ObjectFieldDataType } from './ObjectFieldDataType';
|
||||
|
||||
export type ObjectFieldItem = {
|
||||
name: string;
|
||||
Icon: IconComponent;
|
||||
type: 'standard' | 'custom';
|
||||
dataType: ObjectFieldDataType;
|
||||
};
|
||||
@ -5,7 +5,6 @@ export {
|
||||
IconAlertTriangle,
|
||||
IconArchive,
|
||||
IconArchiveOff,
|
||||
IconArrowBack,
|
||||
IconArrowDown,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
@ -17,7 +16,6 @@ export {
|
||||
IconBrandGithub,
|
||||
IconBrandGoogle,
|
||||
IconBrandLinkedin,
|
||||
IconBrandTwitter,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconBuildingSkyscraper,
|
||||
@ -31,11 +29,9 @@ export {
|
||||
IconChevronsRight,
|
||||
IconChevronUp,
|
||||
IconCircleDot,
|
||||
IconCirclePlus,
|
||||
IconColorSwatch,
|
||||
IconMessageCircle as IconComment,
|
||||
IconCopy,
|
||||
IconCross,
|
||||
IconCurrencyDollar,
|
||||
IconDatabase,
|
||||
IconDotsVertical,
|
||||
@ -45,15 +41,11 @@ export {
|
||||
IconFileImport,
|
||||
IconFileUpload,
|
||||
IconForbid,
|
||||
IconFreeRights,
|
||||
IconGraph,
|
||||
IconGripVertical,
|
||||
IconHeadphones,
|
||||
IconHeart,
|
||||
IconHeartOff,
|
||||
IconHelpCircle,
|
||||
IconHierarchy2,
|
||||
IconInbox,
|
||||
IconLayoutKanban,
|
||||
IconLayoutSidebarLeftCollapse,
|
||||
IconLayoutSidebarRightCollapse,
|
||||
@ -62,7 +54,6 @@ export {
|
||||
IconLinkOff,
|
||||
IconList,
|
||||
IconLogout,
|
||||
IconLuggage,
|
||||
IconMail,
|
||||
IconMap,
|
||||
IconMinus,
|
||||
@ -72,7 +63,6 @@ export {
|
||||
IconNumbers,
|
||||
IconPencil,
|
||||
IconPhone,
|
||||
IconPlane,
|
||||
IconPlug,
|
||||
IconPlus,
|
||||
IconProgressCheck,
|
||||
@ -80,7 +70,6 @@ export {
|
||||
IconRobot,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconSocial,
|
||||
IconTag,
|
||||
IconTarget,
|
||||
IconTargetArrow,
|
||||
|
||||
@ -26,10 +26,10 @@ export const SettingsNewObject = () => {
|
||||
|
||||
const [customFormValues, setCustomFormValues] = useState<{
|
||||
description?: string;
|
||||
icon?: string;
|
||||
icon: string;
|
||||
labelPlural: string;
|
||||
labelSingular: string;
|
||||
}>({ labelPlural: '', labelSingular: '' });
|
||||
}>({ icon: 'IconPigMoney', labelPlural: '', labelSingular: '' });
|
||||
|
||||
const canSave =
|
||||
selectedObjectType === 'Custom' &&
|
||||
|
||||
@ -75,7 +75,7 @@ export const SettingsObjectDetail = () => {
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Fields"
|
||||
description={`Customise the fields available in the ${activeObject?.nameSingular} views and their display order in the ${activeObject?.nameSingular} detail view and menus.`}
|
||||
description={`Customise the fields available in the ${activeObject?.labelSingular} views and their display order in the ${activeObject?.labelSingular} detail view and menus.`}
|
||||
/>
|
||||
<Table>
|
||||
<StyledObjectFieldTableRow>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useFieldMetadata } from '@/metadata/hooks/useFieldMetadata';
|
||||
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
|
||||
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 { activeObjectItems } from '@/settings/data-model/constants/mockObjects';
|
||||
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconSettings } from '@/ui/display/icon';
|
||||
@ -16,23 +17,30 @@ import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
export const SettingsObjectNewFieldStep2 = () => {
|
||||
const navigate = useNavigate();
|
||||
const { objectSlug = '' } = useParams();
|
||||
const activeObject = activeObjectItems.find(
|
||||
(activeObject) => activeObject.name.toLowerCase() === objectSlug,
|
||||
);
|
||||
const { findActiveObjectBySlug } = useObjectMetadata();
|
||||
const activeObject = findActiveObjectBySlug(objectSlug);
|
||||
const { createField } = useFieldMetadata();
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObject) navigate(AppPath.NotFound);
|
||||
}, [activeObject, navigate]);
|
||||
|
||||
const [formValues, setFormValues] = useState<
|
||||
Partial<{
|
||||
iconKey: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}> & { type: ObjectFieldDataType }
|
||||
>({ type: 'number' });
|
||||
const [formValues, setFormValues] = useState<{
|
||||
description?: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
type: ObjectFieldDataType;
|
||||
}>({ icon: 'IconUsers', label: '', type: 'number' });
|
||||
|
||||
const canSave = !!formValues.name;
|
||||
const canSave = !!formValues.label;
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!activeObject) return;
|
||||
|
||||
await createField({ ...formValues, objectId: activeObject.id });
|
||||
|
||||
navigate(`/settings/objects/${objectSlug}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
@ -42,7 +50,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{
|
||||
children: activeObject?.name ?? '',
|
||||
children: activeObject?.labelPlural ?? '',
|
||||
href: `/settings/objects/${objectSlug}`,
|
||||
},
|
||||
{ children: 'New Field' },
|
||||
@ -51,14 +59,14 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
onCancel={() => {
|
||||
navigate('/settings/objects');
|
||||
navigate(`/settings/objects/${objectSlug}`);
|
||||
}}
|
||||
onSave={() => undefined}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</SettingsHeaderContainer>
|
||||
<SettingsObjectFieldFormSection
|
||||
iconKey={formValues.iconKey}
|
||||
name={formValues.name}
|
||||
iconKey={formValues.icon}
|
||||
name={formValues.label}
|
||||
description={formValues.description}
|
||||
onChange={(values) =>
|
||||
setFormValues((previousValues) => ({
|
||||
|
||||
Reference in New Issue
Block a user