Implements #5398. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const StyledFormCardTitle = styled.h3`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
margin: 0;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
@ -9,9 +9,9 @@ import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
|
||||
export const settingsDataModelFieldIconLabelFormSchema = (
|
||||
existingLabels?: string[],
|
||||
existingOtherLabels: string[] = [],
|
||||
) => {
|
||||
return fieldMetadataItemSchema(existingLabels || []).pick({
|
||||
return fieldMetadataItemSchema(existingOtherLabels).pick({
|
||||
icon: true,
|
||||
label: true,
|
||||
});
|
||||
|
||||
@ -9,6 +9,8 @@ import { settingsDataModelFieldBooleanFormSchema } from '@/settings/data-model/f
|
||||
import { SettingsDataModelFieldBooleanSettingsFormCard } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard';
|
||||
import { settingsDataModelFieldCurrencyFormSchema } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencyForm';
|
||||
import { SettingsDataModelFieldCurrencySettingsFormCard } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencySettingsFormCard';
|
||||
import { settingsDataModelFieldDateFormSchema } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm';
|
||||
import { SettingsDataModelFieldDateSettingsFormCard } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard';
|
||||
import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm';
|
||||
import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard';
|
||||
import {
|
||||
@ -30,6 +32,14 @@ const currencyFieldFormSchema = z
|
||||
.object({ type: z.literal(FieldMetadataType.Currency) })
|
||||
.merge(settingsDataModelFieldCurrencyFormSchema);
|
||||
|
||||
const dateFieldFormSchema = z
|
||||
.object({ type: z.literal(FieldMetadataType.Date) })
|
||||
.merge(settingsDataModelFieldDateFormSchema);
|
||||
|
||||
const dateTimeFieldFormSchema = z
|
||||
.object({ type: z.literal(FieldMetadataType.DateTime) })
|
||||
.merge(settingsDataModelFieldDateFormSchema);
|
||||
|
||||
const relationFieldFormSchema = z
|
||||
.object({ type: z.literal(FieldMetadataType.Relation) })
|
||||
.merge(settingsDataModelFieldRelationFormSchema);
|
||||
@ -51,6 +61,8 @@ const otherFieldsFormSchema = z.object({
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.MultiSelect,
|
||||
FieldMetadataType.Date,
|
||||
FieldMetadataType.DateTime,
|
||||
]),
|
||||
) as [FieldMetadataType, ...FieldMetadataType[]],
|
||||
),
|
||||
@ -61,6 +73,8 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion(
|
||||
[
|
||||
booleanFieldFormSchema,
|
||||
currencyFieldFormSchema,
|
||||
dateFieldFormSchema,
|
||||
dateTimeFieldFormSchema,
|
||||
relationFieldFormSchema,
|
||||
selectFieldFormSchema,
|
||||
multiSelectFieldFormSchema,
|
||||
@ -69,7 +83,7 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion(
|
||||
);
|
||||
|
||||
type SettingsDataModelFieldSettingsFormCardProps = {
|
||||
disableCurrencyForm?: boolean;
|
||||
isCreatingField?: boolean;
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> &
|
||||
Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>;
|
||||
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
|
||||
@ -102,7 +116,7 @@ const previewableTypes = [
|
||||
];
|
||||
|
||||
export const SettingsDataModelFieldSettingsFormCard = ({
|
||||
disableCurrencyForm,
|
||||
isCreatingField,
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
}: SettingsDataModelFieldSettingsFormCardProps) => {
|
||||
@ -120,7 +134,20 @@ export const SettingsDataModelFieldSettingsFormCard = ({
|
||||
if (fieldMetadataItem.type === FieldMetadataType.Currency) {
|
||||
return (
|
||||
<SettingsDataModelFieldCurrencySettingsFormCard
|
||||
disabled={disableCurrencyForm}
|
||||
disabled={!isCreatingField}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.type === FieldMetadataType.Date ||
|
||||
fieldMetadataItem.type === FieldMetadataType.DateTime
|
||||
) {
|
||||
return (
|
||||
<SettingsDataModelFieldDateSettingsFormCard
|
||||
disabled={!isCreatingField}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
import { Toggle } from '@/ui/input/components/Toggle';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
AppTooltip,
|
||||
IconComponent,
|
||||
IconInfoCircle,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div<{ disabled?: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ disabled, theme }) =>
|
||||
disabled ? theme.font.color.tertiary : theme.font.color.primary};
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
justify-content: space-between;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledGroup = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
interface SettingsDataModelFieldToggleProps {
|
||||
disabled?: boolean;
|
||||
Icon?: IconComponent;
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
value?: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const SettingsDataModelFieldToggle = ({
|
||||
disabled,
|
||||
Icon,
|
||||
label,
|
||||
tooltip,
|
||||
value,
|
||||
onChange,
|
||||
}: SettingsDataModelFieldToggleProps) => {
|
||||
const theme = useTheme();
|
||||
const infoCircleElementId = `info-circle-id-${Math.random().toString(36).slice(2)}`;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledGroup>
|
||||
{Icon && (
|
||||
<Icon color={theme.font.color.tertiary} size={theme.icon.size.md} />
|
||||
)}
|
||||
{label}
|
||||
</StyledGroup>
|
||||
<StyledGroup>
|
||||
{tooltip && (
|
||||
<IconInfoCircle
|
||||
id={infoCircleElementId}
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
)}
|
||||
{tooltip &&
|
||||
createPortal(
|
||||
<AppTooltip
|
||||
anchorSelect={`#${infoCircleElementId}`}
|
||||
content={tooltip}
|
||||
offset={5}
|
||||
noArrow
|
||||
place="bottom"
|
||||
positionStrategy="absolute"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>,
|
||||
document.body,
|
||||
)}
|
||||
<Toggle
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</StyledGroup>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle';
|
||||
import { SettingsDataModelFieldToggle } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle';
|
||||
import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
import { IconClockShare } from 'twenty-ui';
|
||||
|
||||
export const settingsDataModelFieldDateFormSchema = z.object({
|
||||
settings: z
|
||||
.object({
|
||||
displayAsRelativeDate: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type SettingsDataModelFieldDateFormValues = z.infer<
|
||||
typeof settingsDataModelFieldDateFormSchema
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldDateFormProps = {
|
||||
disabled?: boolean;
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'settings'>;
|
||||
};
|
||||
|
||||
export const SettingsDataModelFieldDateForm = ({
|
||||
disabled,
|
||||
fieldMetadataItem,
|
||||
}: SettingsDataModelFieldDateFormProps) => {
|
||||
const { control } = useFormContext<SettingsDataModelFieldDateFormValues>();
|
||||
|
||||
const { initialDisplayAsRelativeDateValue } =
|
||||
useDateSettingsFormInitialValues({
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
return (
|
||||
<CardContent>
|
||||
<Controller
|
||||
name="settings.displayAsRelativeDate"
|
||||
control={control}
|
||||
defaultValue={initialDisplayAsRelativeDateValue}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<>
|
||||
<StyledFormCardTitle>Options</StyledFormCardTitle>
|
||||
<SettingsDataModelFieldToggle
|
||||
label="Display as relative date"
|
||||
Icon={IconClockShare}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
tooltip={
|
||||
'Show dates in a human-friendly format. Example: "13 mins ago" instead of "Jul 30, 2024 7:11pm"'
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,66 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
|
||||
import {
|
||||
SettingsDataModelFieldDateForm,
|
||||
SettingsDataModelFieldDateFormValues,
|
||||
} from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm';
|
||||
import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues';
|
||||
import {
|
||||
SettingsDataModelFieldPreviewCard,
|
||||
SettingsDataModelFieldPreviewCardProps,
|
||||
} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard';
|
||||
|
||||
type SettingsDataModelFieldDateSettingsFormCardProps = {
|
||||
disabled?: boolean;
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'icon' | 'label' | 'type' | 'settings'
|
||||
>;
|
||||
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
|
||||
|
||||
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
|
||||
display: grid;
|
||||
flex: 1 1 100%;
|
||||
`;
|
||||
|
||||
export const SettingsDataModelFieldDateSettingsFormCard = ({
|
||||
disabled,
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
}: SettingsDataModelFieldDateSettingsFormCardProps) => {
|
||||
const { initialDisplayAsRelativeDateValue } =
|
||||
useDateSettingsFormInitialValues({
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
const { watch: watchFormValue } =
|
||||
useFormContext<SettingsDataModelFieldDateFormValues>();
|
||||
|
||||
return (
|
||||
<SettingsDataModelPreviewFormCard
|
||||
preview={
|
||||
<StyledFieldPreviewCard
|
||||
fieldMetadataItem={{
|
||||
...fieldMetadataItem,
|
||||
settings: {
|
||||
displayAsRelativeDate: watchFormValue(
|
||||
'settings.displayAsRelativeDate',
|
||||
initialDisplayAsRelativeDateValue,
|
||||
),
|
||||
},
|
||||
}}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
}
|
||||
form={
|
||||
<SettingsDataModelFieldDateForm
|
||||
disabled={disabled}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { SettingsDataModelFieldDateFormValues } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm';
|
||||
|
||||
export const useDateSettingsFormInitialValues = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'settings'>;
|
||||
}) => {
|
||||
const initialDisplayAsRelativeDateValue =
|
||||
fieldMetadataItem?.settings?.displayAsRelativeDate;
|
||||
|
||||
const { resetField } = useFormContext<SettingsDataModelFieldDateFormValues>();
|
||||
|
||||
const resetDefaultValueField = () =>
|
||||
resetField('settings.displayAsRelativeDate', {
|
||||
defaultValue: initialDisplayAsRelativeDateValue,
|
||||
});
|
||||
|
||||
return {
|
||||
initialDisplayAsRelativeDateValue,
|
||||
resetDefaultValueField,
|
||||
};
|
||||
};
|
||||
@ -5,10 +5,10 @@ import { settingsDataModelFieldIconLabelFormSchema } from '@/settings/data-model
|
||||
import { settingsDataModelFieldSettingsFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||
import { settingsDataModelFieldTypeFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect';
|
||||
|
||||
export const settingsFieldFormSchema = (existingLabels?: string[]) => {
|
||||
export const settingsFieldFormSchema = (existingOtherLabels?: string[]) => {
|
||||
return z
|
||||
.object({})
|
||||
.merge(settingsDataModelFieldIconLabelFormSchema(existingLabels))
|
||||
.merge(settingsDataModelFieldIconLabelFormSchema(existingOtherLabels))
|
||||
.merge(settingsDataModelFieldDescriptionFormSchema())
|
||||
.merge(settingsDataModelFieldTypeFormSchema)
|
||||
.and(settingsDataModelFieldSettingsFormSchema);
|
||||
|
||||
@ -18,7 +18,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
export type SettingsDataModelFieldPreviewProps = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'icon' | 'label' | 'type' | 'defaultValue' | 'options'
|
||||
'icon' | 'label' | 'type' | 'defaultValue' | 'options' | 'settings'
|
||||
> & {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@ -132,6 +132,7 @@ export const SettingsDataModelFieldPreview = ({
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem?.nameSingular,
|
||||
options: fieldMetadataItem.options ?? [],
|
||||
settings: fieldMetadataItem.settings,
|
||||
},
|
||||
defaultValue: fieldMetadataItem.defaultValue,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user