@ -10,15 +10,16 @@ import { Field } from '~/generated/graphql';
|
|||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
import { SettingsObjectFieldPreviewValueEffect } from '../components/SettingsObjectFieldPreviewValueEffect';
|
import { SettingsObjectFieldPreviewValueEffect } from '../components/SettingsObjectFieldPreviewValueEffect';
|
||||||
import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes';
|
|
||||||
import { useFieldPreview } from '../hooks/useFieldPreview';
|
import { useFieldPreview } from '../hooks/useFieldPreview';
|
||||||
import { useRelationFieldPreview } from '../hooks/useRelationFieldPreview';
|
|
||||||
|
import { SettingsObjectFieldSelectFormValues } from './SettingsObjectFieldSelectForm';
|
||||||
|
|
||||||
export type SettingsObjectFieldPreviewProps = {
|
export type SettingsObjectFieldPreviewProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
fieldMetadata: Pick<Field, 'icon' | 'label' | 'type'> & { id?: string };
|
fieldMetadata: Pick<Field, 'icon' | 'label' | 'type'> & { id?: string };
|
||||||
objectMetadataId: string;
|
objectMetadataId: string;
|
||||||
relationObjectMetadataId?: string;
|
relationObjectMetadataId?: string;
|
||||||
|
selectOptions?: SettingsObjectFieldSelectFormValues;
|
||||||
shrink?: boolean;
|
shrink?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
fieldMetadata,
|
fieldMetadata,
|
||||||
objectMetadataId,
|
objectMetadataId,
|
||||||
relationObjectMetadataId,
|
relationObjectMetadataId,
|
||||||
|
selectOptions,
|
||||||
shrink,
|
shrink,
|
||||||
}: SettingsObjectFieldPreviewProps) => {
|
}: SettingsObjectFieldPreviewProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -81,27 +83,17 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
entityId,
|
entityId,
|
||||||
FieldIcon,
|
FieldIcon,
|
||||||
fieldName,
|
fieldName,
|
||||||
hasValue,
|
|
||||||
ObjectIcon,
|
ObjectIcon,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
relationObjectMetadataItem,
|
||||||
value,
|
value,
|
||||||
} = useFieldPreview({
|
} = useFieldPreview({
|
||||||
fieldMetadata,
|
fieldMetadata,
|
||||||
objectMetadataId,
|
objectMetadataId,
|
||||||
|
relationObjectMetadataId,
|
||||||
|
selectOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { defaultValue: relationDefaultValue, relationObjectMetadataItem } =
|
|
||||||
useRelationFieldPreview({
|
|
||||||
relationObjectMetadataId,
|
|
||||||
skipDefaultValue:
|
|
||||||
fieldMetadata.type !== FieldMetadataType.Relation || hasValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
const defaultValue =
|
|
||||||
fieldMetadata.type === FieldMetadataType.Relation
|
|
||||||
? relationDefaultValue
|
|
||||||
: settingsFieldMetadataTypes[fieldMetadata.type].defaultValue;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer className={className}>
|
<StyledContainer className={className}>
|
||||||
<StyledObjectSummary>
|
<StyledObjectSummary>
|
||||||
@ -123,7 +115,7 @@ export const SettingsObjectFieldPreview = ({
|
|||||||
<SettingsObjectFieldPreviewValueEffect
|
<SettingsObjectFieldPreviewValueEffect
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
fieldName={fieldName}
|
fieldName={fieldName}
|
||||||
value={value ?? defaultValue}
|
value={value}
|
||||||
/>
|
/>
|
||||||
<StyledFieldPreview shrink={shrink}>
|
<StyledFieldPreview shrink={shrink}>
|
||||||
<StyledFieldLabel>
|
<StyledFieldLabel>
|
||||||
|
|||||||
@ -0,0 +1,58 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
|
export type SettingsObjectFieldSelectFormValues = {
|
||||||
|
color: ThemeColor;
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
type SettingsObjectFieldSelectFormProps = {
|
||||||
|
onChange: (values: SettingsObjectFieldSelectFormValues) => void;
|
||||||
|
values?: SettingsObjectFieldSelectFormValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledLabel = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
display: block;
|
||||||
|
font-size: ${({ theme }) => theme.font.size.xs};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(3)};
|
||||||
|
text-transform: uppercase;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledInputsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledOptionInput = styled(TextInput)`
|
||||||
|
& input {
|
||||||
|
height: ${({ theme }) => theme.spacing(2)};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsObjectFieldSelectForm = ({
|
||||||
|
onChange,
|
||||||
|
values = [],
|
||||||
|
}: SettingsObjectFieldSelectFormProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledLabel>Options</StyledLabel>
|
||||||
|
<StyledInputsContainer>
|
||||||
|
{values.map((value, index) => (
|
||||||
|
<StyledOptionInput
|
||||||
|
value={value.text}
|
||||||
|
onChange={(text) => {
|
||||||
|
const nextValues = [...values];
|
||||||
|
nextValues.splice(index, 1, { ...values[index], text });
|
||||||
|
onChange(nextValues);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledInputsContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -16,11 +16,16 @@ import {
|
|||||||
SettingsObjectFieldRelationForm,
|
SettingsObjectFieldRelationForm,
|
||||||
SettingsObjectFieldRelationFormValues,
|
SettingsObjectFieldRelationFormValues,
|
||||||
} from './SettingsObjectFieldRelationForm';
|
} from './SettingsObjectFieldRelationForm';
|
||||||
|
import {
|
||||||
|
SettingsObjectFieldSelectForm,
|
||||||
|
SettingsObjectFieldSelectFormValues,
|
||||||
|
} from './SettingsObjectFieldSelectForm';
|
||||||
import { SettingsObjectFieldTypeCard } from './SettingsObjectFieldTypeCard';
|
import { SettingsObjectFieldTypeCard } from './SettingsObjectFieldTypeCard';
|
||||||
|
|
||||||
export type SettingsObjectFieldTypeSelectSectionFormValues = Partial<{
|
export type SettingsObjectFieldTypeSelectSectionFormValues = Partial<{
|
||||||
type: FieldMetadataType;
|
type: FieldMetadataType;
|
||||||
relation: SettingsObjectFieldRelationFormValues;
|
relation: SettingsObjectFieldRelationFormValues;
|
||||||
|
select: SettingsObjectFieldSelectFormValues;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type SettingsObjectFieldTypeSelectSectionProps = {
|
type SettingsObjectFieldTypeSelectSectionProps = {
|
||||||
@ -54,6 +59,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
|||||||
values,
|
values,
|
||||||
}: SettingsObjectFieldTypeSelectSectionProps) => {
|
}: SettingsObjectFieldTypeSelectSectionProps) => {
|
||||||
const relationFormConfig = values?.relation;
|
const relationFormConfig = values?.relation;
|
||||||
|
const selectFormConfig = values?.select;
|
||||||
|
|
||||||
const fieldTypeOptions = Object.entries(settingsFieldMetadataTypes)
|
const fieldTypeOptions = Object.entries(settingsFieldMetadataTypes)
|
||||||
.filter(([key]) => !excludedFieldTypes?.includes(key as FieldMetadataType))
|
.filter(([key]) => !excludedFieldTypes?.includes(key as FieldMetadataType))
|
||||||
@ -80,6 +86,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
|||||||
FieldMetadataType.Boolean,
|
FieldMetadataType.Boolean,
|
||||||
FieldMetadataType.Currency,
|
FieldMetadataType.Currency,
|
||||||
FieldMetadataType.DateTime,
|
FieldMetadataType.DateTime,
|
||||||
|
FieldMetadataType.Enum,
|
||||||
FieldMetadataType.Link,
|
FieldMetadataType.Link,
|
||||||
FieldMetadataType.Number,
|
FieldMetadataType.Number,
|
||||||
FieldMetadataType.Relation,
|
FieldMetadataType.Relation,
|
||||||
@ -98,6 +105,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
|||||||
relationObjectMetadataId={
|
relationObjectMetadataId={
|
||||||
relationFormConfig?.objectMetadataId
|
relationFormConfig?.objectMetadataId
|
||||||
}
|
}
|
||||||
|
selectOptions={selectFormConfig}
|
||||||
/>
|
/>
|
||||||
{values.type === FieldMetadataType.Relation &&
|
{values.type === FieldMetadataType.Relation &&
|
||||||
!!relationFormConfig?.type &&
|
!!relationFormConfig?.type &&
|
||||||
@ -127,7 +135,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
form={
|
form={
|
||||||
values.type === FieldMetadataType.Relation && (
|
values.type === FieldMetadataType.Relation ? (
|
||||||
<SettingsObjectFieldRelationForm
|
<SettingsObjectFieldRelationForm
|
||||||
disableFieldEdition={
|
disableFieldEdition={
|
||||||
relationFieldMetadata && !relationFieldMetadata.isCustom
|
relationFieldMetadata && !relationFieldMetadata.isCustom
|
||||||
@ -140,7 +148,12 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
) : values.type === FieldMetadataType.Enum ? (
|
||||||
|
<SettingsObjectFieldSelectForm
|
||||||
|
values={selectFormConfig}
|
||||||
|
onChange={(nextValues) => onChange({ select: nextValues })}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -61,7 +61,6 @@ export const settingsFieldMetadataTypes: Record<
|
|||||||
[FieldMetadataType.Enum]: {
|
[FieldMetadataType.Enum]: {
|
||||||
label: 'Select',
|
label: 'Select',
|
||||||
Icon: IconTag,
|
Icon: IconTag,
|
||||||
defaultValue: { color: 'green', text: 'Option 1' },
|
|
||||||
},
|
},
|
||||||
[FieldMetadataType.Currency]: {
|
[FieldMetadataType.Currency]: {
|
||||||
label: 'Currency',
|
label: 'Currency',
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useState } from 'react';
|
|||||||
import { DeepPartial } from 'react-hook-form';
|
import { DeepPartial } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
import {
|
import {
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
RelationMetadataType,
|
RelationMetadataType,
|
||||||
@ -16,6 +17,7 @@ type FormValues = {
|
|||||||
label: string;
|
label: string;
|
||||||
type: FieldMetadataType;
|
type: FieldMetadataType;
|
||||||
relation: SettingsObjectFieldTypeSelectSectionFormValues['relation'];
|
relation: SettingsObjectFieldTypeSelectSectionFormValues['relation'];
|
||||||
|
select: SettingsObjectFieldTypeSelectSectionFormValues['select'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: FormValues = {
|
const defaultValues: FormValues = {
|
||||||
@ -25,6 +27,7 @@ const defaultValues: FormValues = {
|
|||||||
relation: {
|
relation: {
|
||||||
type: RelationMetadataType.OneToMany,
|
type: RelationMetadataType.OneToMany,
|
||||||
},
|
},
|
||||||
|
select: [{ color: 'green', text: 'Option 1' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldSchema = z.object({
|
const fieldSchema = z.object({
|
||||||
@ -48,7 +51,27 @@ const relationSchema = fieldSchema.merge(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { Relation: _, ...otherFieldTypes } = FieldMetadataType;
|
const selectSchema = fieldSchema.merge(
|
||||||
|
z.object({
|
||||||
|
type: z.literal(FieldMetadataType.Enum),
|
||||||
|
select: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
color: z.enum(
|
||||||
|
Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]],
|
||||||
|
),
|
||||||
|
text: z.string().min(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.nonempty(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
Enum: _Enum,
|
||||||
|
Relation: _Relation,
|
||||||
|
...otherFieldTypes
|
||||||
|
} = FieldMetadataType;
|
||||||
|
|
||||||
const otherFieldTypesSchema = fieldSchema.merge(
|
const otherFieldTypesSchema = fieldSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
@ -63,9 +86,13 @@ const otherFieldTypesSchema = fieldSchema.merge(
|
|||||||
|
|
||||||
const schema = z.discriminatedUnion('type', [
|
const schema = z.discriminatedUnion('type', [
|
||||||
relationSchema,
|
relationSchema,
|
||||||
|
selectSchema,
|
||||||
otherFieldTypesSchema,
|
otherFieldTypesSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
type PartialFormValues = Partial<FormValues> &
|
||||||
|
DeepPartial<Pick<FormValues, 'relation'>>;
|
||||||
|
|
||||||
export const useFieldMetadataForm = () => {
|
export const useFieldMetadataForm = () => {
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const [initialFormValues, setInitialFormValues] =
|
const [initialFormValues, setInitialFormValues] =
|
||||||
@ -73,14 +100,15 @@ export const useFieldMetadataForm = () => {
|
|||||||
const [formValues, setFormValues] = useState<FormValues>(defaultValues);
|
const [formValues, setFormValues] = useState<FormValues>(defaultValues);
|
||||||
const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false);
|
const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false);
|
||||||
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
|
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
|
||||||
|
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
|
||||||
const [validationResult, setValidationResult] = useState(
|
const [validationResult, setValidationResult] = useState(
|
||||||
schema.safeParse(formValues),
|
schema.safeParse(formValues),
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergePartialValues = (
|
const mergePartialValues = (
|
||||||
previousValues: FormValues,
|
previousValues: FormValues,
|
||||||
nextValues: DeepPartial<FormValues>,
|
nextValues: PartialFormValues,
|
||||||
) => ({
|
): FormValues => ({
|
||||||
...previousValues,
|
...previousValues,
|
||||||
...nextValues,
|
...nextValues,
|
||||||
relation: {
|
relation: {
|
||||||
@ -93,7 +121,7 @@ export const useFieldMetadataForm = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const initForm = (lazyInitialFormValues: DeepPartial<FormValues>) => {
|
const initForm = (lazyInitialFormValues: PartialFormValues) => {
|
||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
|
|
||||||
const mergedFormValues = mergePartialValues(
|
const mergedFormValues = mergePartialValues(
|
||||||
@ -107,16 +135,22 @@ export const useFieldMetadataForm = () => {
|
|||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormChange = (values: DeepPartial<FormValues>) => {
|
const handleFormChange = (values: PartialFormValues) => {
|
||||||
const nextFormValues = mergePartialValues(formValues, values);
|
const nextFormValues = mergePartialValues(formValues, values);
|
||||||
|
|
||||||
setFormValues(nextFormValues);
|
setFormValues(nextFormValues);
|
||||||
setValidationResult(schema.safeParse(nextFormValues));
|
setValidationResult(schema.safeParse(nextFormValues));
|
||||||
|
|
||||||
const { relation: initialRelationFormValues, ...initialFieldFormValues } =
|
const {
|
||||||
initialFormValues;
|
relation: initialRelationFormValues,
|
||||||
const { relation: nextRelationFormValues, ...nextFieldFormValues } =
|
select: initialSelectFormValues,
|
||||||
nextFormValues;
|
...initialFieldFormValues
|
||||||
|
} = initialFormValues;
|
||||||
|
const {
|
||||||
|
relation: nextRelationFormValues,
|
||||||
|
select: nextSelectFormValues,
|
||||||
|
...nextFieldFormValues
|
||||||
|
} = nextFormValues;
|
||||||
|
|
||||||
setHasFieldFormChanged(
|
setHasFieldFormChanged(
|
||||||
!isDeeplyEqual(initialFieldFormValues, nextFieldFormValues),
|
!isDeeplyEqual(initialFieldFormValues, nextFieldFormValues),
|
||||||
@ -125,13 +159,18 @@ export const useFieldMetadataForm = () => {
|
|||||||
nextFieldFormValues.type === FieldMetadataType.Relation &&
|
nextFieldFormValues.type === FieldMetadataType.Relation &&
|
||||||
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
|
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
|
||||||
);
|
);
|
||||||
|
setHasSelectFormChanged(
|
||||||
|
nextFieldFormValues.type === FieldMetadataType.Enum &&
|
||||||
|
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formValues,
|
formValues,
|
||||||
handleFormChange,
|
handleFormChange,
|
||||||
hasFieldFormChanged,
|
hasFieldFormChanged,
|
||||||
hasFormChanged: hasFieldFormChanged || hasRelationFormChanged,
|
hasFormChanged:
|
||||||
|
hasFieldFormChanged || hasRelationFormChanged || hasSelectFormChanged,
|
||||||
hasRelationFormChanged,
|
hasRelationFormChanged,
|
||||||
initForm,
|
initForm,
|
||||||
isInitialized,
|
isInitialized,
|
||||||
|
|||||||
@ -1,43 +1,73 @@
|
|||||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
|
||||||
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
|
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
|
||||||
import { Field } from '~/generated-metadata/graphql';
|
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { assertNotNull } from '~/utils/assert';
|
|
||||||
|
import { SettingsObjectFieldSelectFormValues } from '../components/SettingsObjectFieldSelectForm';
|
||||||
|
import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes';
|
||||||
|
|
||||||
|
import { useFieldPreviewValue } from './useFieldPreviewValue';
|
||||||
|
import { useRelationFieldPreviewValue } from './useRelationFieldPreviewValue';
|
||||||
|
|
||||||
export const useFieldPreview = ({
|
export const useFieldPreview = ({
|
||||||
fieldMetadata,
|
fieldMetadata,
|
||||||
objectMetadataId,
|
objectMetadataId,
|
||||||
|
relationObjectMetadataId,
|
||||||
|
selectOptions,
|
||||||
}: {
|
}: {
|
||||||
fieldMetadata: Partial<Pick<Field, 'icon' | 'id' | 'type'>>;
|
fieldMetadata: Pick<Field, 'icon' | 'label' | 'type'> & { id?: string };
|
||||||
objectMetadataId: string;
|
objectMetadataId: string;
|
||||||
|
relationObjectMetadataId?: string;
|
||||||
|
selectOptions?: SettingsObjectFieldSelectFormValues;
|
||||||
}) => {
|
}) => {
|
||||||
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
|
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
|
||||||
const objectMetadataItem = findObjectMetadataItemById(objectMetadataId);
|
const objectMetadataItem = findObjectMetadataItemById(objectMetadataId);
|
||||||
|
|
||||||
const { objects } = useFindManyObjectRecords({
|
|
||||||
objectNamePlural: objectMetadataItem?.namePlural,
|
|
||||||
skip: !objectMetadataItem || !fieldMetadata.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { Icon: ObjectIcon } = useLazyLoadIcon(objectMetadataItem?.icon ?? '');
|
const { Icon: ObjectIcon } = useLazyLoadIcon(objectMetadataItem?.icon ?? '');
|
||||||
const { Icon: FieldIcon } = useLazyLoadIcon(fieldMetadata.icon ?? '');
|
const { Icon: FieldIcon } = useLazyLoadIcon(fieldMetadata.icon ?? '');
|
||||||
|
|
||||||
const [firstRecord] = objects;
|
|
||||||
const fieldName = fieldMetadata.id
|
const fieldName = fieldMetadata.id
|
||||||
? objectMetadataItem?.fields.find(({ id }) => id === fieldMetadata.id)?.name
|
? objectMetadataItem?.fields.find(({ id }) => id === fieldMetadata.id)?.name
|
||||||
: undefined;
|
: undefined;
|
||||||
const value =
|
|
||||||
fieldMetadata.type !== 'RELATION' && fieldName
|
const { value: firstRecordFieldValue } = useFieldPreviewValue({
|
||||||
? firstRecord?.[fieldName]
|
fieldName: fieldName || '',
|
||||||
: undefined;
|
objectNamePlural: objectMetadataItem?.namePlural || '',
|
||||||
|
skip:
|
||||||
|
!fieldName ||
|
||||||
|
!objectMetadataItem ||
|
||||||
|
fieldMetadata.type === FieldMetadataType.Relation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { relationObjectMetadataItem, value: relationValue } =
|
||||||
|
useRelationFieldPreviewValue({
|
||||||
|
relationObjectMetadataId,
|
||||||
|
skip: fieldMetadata.type !== FieldMetadataType.Relation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultValue =
|
||||||
|
fieldMetadata.type === FieldMetadataType.Enum
|
||||||
|
? selectOptions?.[0]
|
||||||
|
: settingsFieldMetadataTypes[fieldMetadata.type].defaultValue;
|
||||||
|
|
||||||
|
const isValidSelectValue =
|
||||||
|
fieldMetadata.type === FieldMetadataType.Enum &&
|
||||||
|
!!firstRecordFieldValue &&
|
||||||
|
selectOptions?.some(
|
||||||
|
(selectOption) => selectOption.text === firstRecordFieldValue,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entityId: firstRecord?.id || `${objectMetadataId}-no-records`,
|
entityId: `${objectMetadataId}-field-form`,
|
||||||
FieldIcon,
|
FieldIcon,
|
||||||
fieldName: fieldName || `${fieldMetadata.type}-new-field`,
|
fieldName: fieldName || `${fieldMetadata.type}-new-field`,
|
||||||
hasValue: assertNotNull(value),
|
|
||||||
ObjectIcon,
|
ObjectIcon,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
value,
|
relationObjectMetadataItem,
|
||||||
|
value:
|
||||||
|
(fieldMetadata.type === FieldMetadataType.Relation
|
||||||
|
? relationValue
|
||||||
|
: fieldMetadata.type !== FieldMetadataType.Enum || isValidSelectValue
|
||||||
|
? firstRecordFieldValue
|
||||||
|
: undefined) || defaultValue,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||||
|
import { assertNotNull } from '~/utils/assert';
|
||||||
|
|
||||||
|
export const useFieldPreviewValue = ({
|
||||||
|
fieldName,
|
||||||
|
objectNamePlural,
|
||||||
|
skip,
|
||||||
|
}: {
|
||||||
|
fieldName: string;
|
||||||
|
objectNamePlural: string;
|
||||||
|
skip?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { objects } = useFindManyObjectRecords({
|
||||||
|
objectNamePlural,
|
||||||
|
skip,
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstRecordWithValue = objects.find(
|
||||||
|
(record) => assertNotNull(record[fieldName]) && record[fieldName] !== '',
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: firstRecordWithValue?.[fieldName],
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
|
||||||
|
|
||||||
export const useRelationFieldPreview = ({
|
export const useRelationFieldPreviewValue = ({
|
||||||
relationObjectMetadataId,
|
relationObjectMetadataId,
|
||||||
skipDefaultValue,
|
skip,
|
||||||
}: {
|
}: {
|
||||||
relationObjectMetadataId?: string;
|
relationObjectMetadataId?: string;
|
||||||
skipDefaultValue: boolean;
|
skip?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
|
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
|
||||||
|
|
||||||
@ -17,18 +16,16 @@ export const useRelationFieldPreview = ({
|
|||||||
|
|
||||||
const { objects: relationObjects } = useFindManyObjectRecords({
|
const { objects: relationObjects } = useFindManyObjectRecords({
|
||||||
objectNamePlural: relationObjectMetadataItem?.namePlural,
|
objectNamePlural: relationObjectMetadataItem?.namePlural,
|
||||||
skip: skipDefaultValue || !relationObjectMetadataItem,
|
skip: skip || !relationObjectMetadataItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockValueName = capitalize(
|
const label = relationObjectMetadataItem?.labelSingular ?? '';
|
||||||
relationObjectMetadataItem?.nameSingular ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
relationObjectMetadataItem,
|
relationObjectMetadataItem,
|
||||||
defaultValue: relationObjects?.[0] ?? {
|
value: relationObjects?.[0] ?? {
|
||||||
company: { name: mockValueName }, // Temporary mock for opportunities, this needs to be replaced once labelIdentifiers are implemented
|
company: { name: label }, // Temporary mock for opportunities, this needs to be replaced once labelIdentifiers are implemented
|
||||||
name: mockValueName,
|
name: label,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -1,28 +1,29 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { ChipFieldDisplay } from '@/ui/object/field/meta-types/display/components/ChipFieldDisplay';
|
|
||||||
import { FullNameFieldDisplay } from '@/ui/object/field/meta-types/display/components/FullNameFieldDisplay';
|
|
||||||
import { LinkFieldDisplay } from '@/ui/object/field/meta-types/display/components/LinkFieldDisplay';
|
|
||||||
import { RelationFieldDisplay } from '@/ui/object/field/meta-types/display/components/RelationFieldDisplay';
|
|
||||||
import { UuidFieldDisplay } from '@/ui/object/field/meta-types/display/components/UuidFieldDisplay';
|
|
||||||
import { isFieldFullName } from '@/ui/object/field/types/guards/isFieldFullName';
|
|
||||||
import { isFieldLink } from '@/ui/object/field/types/guards/isFieldLink';
|
|
||||||
import { isFieldUuid } from '@/ui/object/field/types/guards/isFieldUuid';
|
|
||||||
|
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
|
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||||
import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay';
|
import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay';
|
||||||
import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay';
|
import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay';
|
||||||
import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay';
|
import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay';
|
||||||
|
import { FullNameFieldDisplay } from '../meta-types/display/components/FullNameFieldDisplay';
|
||||||
|
import { LinkFieldDisplay } from '../meta-types/display/components/LinkFieldDisplay';
|
||||||
import { NumberFieldDisplay } from '../meta-types/display/components/NumberFieldDisplay';
|
import { NumberFieldDisplay } from '../meta-types/display/components/NumberFieldDisplay';
|
||||||
import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDisplay';
|
import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDisplay';
|
||||||
|
import { RelationFieldDisplay } from '../meta-types/display/components/RelationFieldDisplay';
|
||||||
|
import { SelectFieldDisplay } from '../meta-types/display/components/SelectFieldDisplay';
|
||||||
import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay';
|
import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay';
|
||||||
|
import { UuidFieldDisplay } from '../meta-types/display/components/UuidFieldDisplay';
|
||||||
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
|
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
|
||||||
import { isFieldDateTime } from '../types/guards/isFieldDateTime';
|
import { isFieldDateTime } from '../types/guards/isFieldDateTime';
|
||||||
import { isFieldEmail } from '../types/guards/isFieldEmail';
|
import { isFieldEmail } from '../types/guards/isFieldEmail';
|
||||||
|
import { isFieldFullName } from '../types/guards/isFieldFullName';
|
||||||
|
import { isFieldLink } from '../types/guards/isFieldLink';
|
||||||
import { isFieldNumber } from '../types/guards/isFieldNumber';
|
import { isFieldNumber } from '../types/guards/isFieldNumber';
|
||||||
import { isFieldPhone } from '../types/guards/isFieldPhone';
|
import { isFieldPhone } from '../types/guards/isFieldPhone';
|
||||||
import { isFieldRelation } from '../types/guards/isFieldRelation';
|
import { isFieldRelation } from '../types/guards/isFieldRelation';
|
||||||
|
import { isFieldSelect } from '../types/guards/isFieldSelect';
|
||||||
import { isFieldText } from '../types/guards/isFieldText';
|
import { isFieldText } from '../types/guards/isFieldText';
|
||||||
|
import { isFieldUuid } from '../types/guards/isFieldUuid';
|
||||||
|
|
||||||
export const FieldDisplay = () => {
|
export const FieldDisplay = () => {
|
||||||
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
|
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
|
||||||
@ -55,6 +56,8 @@ export const FieldDisplay = () => {
|
|||||||
<FullNameFieldDisplay />
|
<FullNameFieldDisplay />
|
||||||
) : isFieldPhone(fieldDefinition) ? (
|
) : isFieldPhone(fieldDefinition) ? (
|
||||||
<PhoneFieldDisplay />
|
<PhoneFieldDisplay />
|
||||||
|
) : isFieldSelect(fieldDefinition) ? (
|
||||||
|
<SelectFieldDisplay />
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||||
|
|
||||||
|
import { useSelectField } from '../../hooks/useSelectField';
|
||||||
|
|
||||||
|
export const SelectFieldDisplay = () => {
|
||||||
|
const { fieldValue } = useSelectField();
|
||||||
|
|
||||||
|
return <Tag color={fieldValue.color} text={fieldValue.text} />;
|
||||||
|
};
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
|
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
|
||||||
|
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
|
||||||
|
import { FieldSelectValue } from '../../types/FieldMetadata';
|
||||||
|
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||||
|
import { isFieldSelect } from '../../types/guards/isFieldSelect';
|
||||||
|
import { isFieldSelectValue } from '../../types/guards/isFieldSelectValue';
|
||||||
|
|
||||||
|
export const useSelectField = () => {
|
||||||
|
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||||
|
|
||||||
|
assertFieldMetadata('ENUM', isFieldSelect, fieldDefinition);
|
||||||
|
|
||||||
|
const { fieldName } = fieldDefinition.metadata;
|
||||||
|
|
||||||
|
const [fieldValue, setFieldValue] = useRecoilState<FieldSelectValue>(
|
||||||
|
entityFieldsFamilySelector({
|
||||||
|
entityId: entityId,
|
||||||
|
fieldName: fieldName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const fieldSelectValue = isFieldSelectValue(fieldValue)
|
||||||
|
? fieldValue
|
||||||
|
: { color: 'green' as ThemeColor, text: '' };
|
||||||
|
|
||||||
|
const fieldInitialValue = useFieldInitialValue();
|
||||||
|
|
||||||
|
const initialValue = {
|
||||||
|
color: 'green' as ThemeColor,
|
||||||
|
text: fieldInitialValue?.isEmpty
|
||||||
|
? ''
|
||||||
|
: fieldInitialValue?.value ?? fieldSelectValue?.text ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fieldDefinition,
|
||||||
|
fieldValue: fieldSelectValue,
|
||||||
|
initialValue,
|
||||||
|
setFieldValue,
|
||||||
|
hotkeyScope,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
export type FieldUuidMetadata = {
|
export type FieldUuidMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
@ -80,19 +81,24 @@ export type FieldRelationMetadata = {
|
|||||||
relationObjectMetadataNamePlural: string;
|
relationObjectMetadataNamePlural: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FieldSelectMetadata = {
|
||||||
|
fieldName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type FieldMetadata =
|
export type FieldMetadata =
|
||||||
| FieldBooleanMetadata
|
| FieldBooleanMetadata
|
||||||
| FieldNumberMetadata
|
|
||||||
| FieldDateTimeMetadata
|
|
||||||
| FieldTextMetadata
|
|
||||||
| FieldUuidMetadata
|
|
||||||
| FieldCurrencyMetadata
|
| FieldCurrencyMetadata
|
||||||
| FieldLinkMetadata
|
| FieldDateTimeMetadata
|
||||||
| FieldPhoneMetadata
|
|
||||||
| FieldEmailMetadata
|
| FieldEmailMetadata
|
||||||
|
| FieldFullNameMetadata
|
||||||
|
| FieldLinkMetadata
|
||||||
|
| FieldNumberMetadata
|
||||||
|
| FieldPhoneMetadata
|
||||||
| FieldProbabilityMetadata
|
| FieldProbabilityMetadata
|
||||||
| FieldRelationMetadata
|
| FieldRelationMetadata
|
||||||
| FieldFullNameMetadata;
|
| FieldSelectMetadata
|
||||||
|
| FieldTextMetadata
|
||||||
|
| FieldUuidMetadata;
|
||||||
|
|
||||||
export type FieldTextValue = string;
|
export type FieldTextValue = string;
|
||||||
export type FieldUUidValue = string;
|
export type FieldUUidValue = string;
|
||||||
@ -109,5 +115,6 @@ export type FieldCurrencyValue = {
|
|||||||
};
|
};
|
||||||
export type FieldFullNameValue = { firstName: string; lastName: string };
|
export type FieldFullNameValue = { firstName: string; lastName: string };
|
||||||
export type FieldProbabilityValue = number;
|
export type FieldProbabilityValue = number;
|
||||||
|
export type FieldSelectValue = { color: ThemeColor; text: string };
|
||||||
|
|
||||||
export type FieldRelationValue = EntityForSelect | null;
|
export type FieldRelationValue = EntityForSelect | null;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
FieldPhoneMetadata,
|
FieldPhoneMetadata,
|
||||||
FieldProbabilityMetadata,
|
FieldProbabilityMetadata,
|
||||||
FieldRelationMetadata,
|
FieldRelationMetadata,
|
||||||
|
FieldSelectMetadata,
|
||||||
FieldTextMetadata,
|
FieldTextMetadata,
|
||||||
FieldUuidMetadata,
|
FieldUuidMetadata,
|
||||||
} from '../FieldMetadata';
|
} from '../FieldMetadata';
|
||||||
@ -28,6 +29,8 @@ type AssertFieldMetadataFunction = <
|
|||||||
? FieldDateTimeMetadata
|
? FieldDateTimeMetadata
|
||||||
: E extends 'EMAIL'
|
: E extends 'EMAIL'
|
||||||
? FieldEmailMetadata
|
? FieldEmailMetadata
|
||||||
|
: E extends 'ENUM'
|
||||||
|
? FieldSelectMetadata
|
||||||
: E extends 'LINK'
|
: E extends 'LINK'
|
||||||
? FieldLinkMetadata
|
? FieldLinkMetadata
|
||||||
: E extends 'NUMBER'
|
: E extends 'NUMBER'
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
|
|
||||||
|
|
||||||
const enumColors = Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]];
|
|
||||||
const enumValueSchema = z.object({
|
|
||||||
color: z.enum(enumColors),
|
|
||||||
text: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const isFieldEnumValue = (
|
|
||||||
fieldValue: unknown,
|
|
||||||
): fieldValue is z.infer<typeof enumValueSchema> =>
|
|
||||||
enumValueSchema.safeParse(fieldValue).success;
|
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { FieldDefinition } from '../FieldDefinition';
|
||||||
|
import { FieldMetadata, FieldSelectMetadata } from '../FieldMetadata';
|
||||||
|
|
||||||
|
export const isFieldSelect = (
|
||||||
|
field: FieldDefinition<FieldMetadata>,
|
||||||
|
): field is FieldDefinition<FieldSelectMetadata> => field.type === 'ENUM';
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
|
const selectColors = Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]];
|
||||||
|
const selectValueSchema = z.object({
|
||||||
|
color: z.enum(selectColors),
|
||||||
|
text: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isFieldSelectValue = (
|
||||||
|
fieldValue: unknown,
|
||||||
|
): fieldValue is z.infer<typeof selectValueSchema> =>
|
||||||
|
selectValueSchema.safeParse(fieldValue).success;
|
||||||
@ -172,6 +172,7 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
values={{
|
values={{
|
||||||
type: formValues.type,
|
type: formValues.type,
|
||||||
relation: formValues.relation,
|
relation: formValues.relation,
|
||||||
|
select: formValues.select,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Section>
|
<Section>
|
||||||
|
|||||||
@ -223,6 +223,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
values={{
|
values={{
|
||||||
type: formValues.type,
|
type: formValues.type,
|
||||||
relation: formValues.relation,
|
relation: formValues.relation,
|
||||||
|
select: formValues.select,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user