Skip to content

Commit

Permalink
Merge commit '40bea0d95e81e9fee09d6d8d1ba41e459b1d0770' of https://gi…
Browse files Browse the repository at this point in the history
  • Loading branch information
gitstart-twenty committed Mar 9, 2024
2 parents 0c21143 + 40bea0d commit 7a5f525
Show file tree
Hide file tree
Showing 37 changed files with 765 additions and 56 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"lodash.isequal": "^4.5.0",
"lodash.isobject": "^3.0.2",
"lodash.kebabcase": "^4.1.1",
"lodash.mapvalues": "^4.6.0",
"lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
Expand Down Expand Up @@ -225,6 +226,7 @@
"@types/lodash.isequal": "^4.5.7",
"@types/lodash.isobject": "^3.0.7",
"@types/lodash.kebabcase": "^4.1.7",
"@types/lodash.mapvalues": "^4.6.9",
"@types/lodash.snakecase": "^4.1.7",
"@types/lodash.upperfirst": "^4.3.7",
"@types/luxon": "^3.3.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import styled from '@emotion/styled';
import { startOfMonth } from 'date-fns';
import { format, getYear, startOfMonth } from 'date-fns';
import mapValues from 'lodash.mapvalues';

import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
import { sortCalendarEventsDesc } from '@/activities/calendar/utils/sortCalendarEvents';
import { H3Title } from '@/ui/display/typography/components/H3Title';
import { Section } from '@/ui/layout/section/components/Section';
import { mockedCalendarEvents } from '~/testing/mock-data/calendar';
import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
import { sortDesc } from '~/utils/sort';
Expand All @@ -16,6 +19,10 @@ const StyledContainer = styled.div`
width: 100%;
`;

const StyledYear = styled.span`
color: ${({ theme }) => theme.font.color.light};
`;

export const Calendar = () => {
const sortedCalendarEvents = [...mockedCalendarEvents].sort(
sortCalendarEventsDesc,
Expand All @@ -27,18 +34,32 @@ export const Calendar = () => {
const sortedMonthTimes = Object.keys(calendarEventsByMonthTime)
.map(Number)
.sort(sortDesc);
const monthTimesByYear = groupArrayItemsBy(sortedMonthTimes, getYear);
const lastMonthTimeByYear = mapValues(monthTimesByYear, (monthTimes = []) =>
Math.max(...monthTimes),
);

return (
<StyledContainer>
{sortedMonthTimes.map((monthTime) => {
const monthCalendarEvents = calendarEventsByMonthTime[monthTime];
const year = getYear(monthTime);
const isLastMonthOfYear = lastMonthTimeByYear[year] === monthTime;
const monthLabel = format(monthTime, 'MMMM');

return (
!!monthCalendarEvents?.length && (
<CalendarMonthCard
key={monthTime}
calendarEvents={monthCalendarEvents}
/>
<Section key={monthTime}>
<H3Title
title={
<>
{monthLabel}
{isLastMonthOfYear && <StyledYear> {year}</StyledYear>}
</>
}
/>
<CalendarMonthCard calendarEvents={monthCalendarEvents} />
</Section>
)
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FieldMetadataType } from '~/generated-metadata/graphql';

export const LABEL_IDENTIFIER_FIELD_METADATA_TYPES = [
FieldMetadataType.Number,
FieldMetadataType.Text,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';

export const getActiveFieldMetadataItems = (
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
) =>
objectMetadataItem.fields.filter(
(fieldMetadataItem) =>
fieldMetadataItem.isActive && !fieldMetadataItem.isSystem,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';

export const getDisabledFieldMetadataItems = (
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
) =>
objectMetadataItem.fields.filter(
(fieldMetadataItem) =>
!fieldMetadataItem.isActive && !fieldMetadataItem.isSystem,
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
import { TextInput } from '@/ui/field/input/components/TextInput';
import { CurrencyInput } from '@/ui/field/input/components/CurrencyInput';

import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { useCurrencyField } from '../../hooks/useCurrencyField';
Expand Down Expand Up @@ -79,19 +79,28 @@ export const CurrencyFieldInput = ({
});
};

const handleSelect = (newValue: string) => {
setDraftValue({
amount: draftValue?.amount ?? '',
currencyCode: newValue as CurrencyCode,
});
};

return (
<FieldInputOverlay>
<TextInput
<CurrencyInput
value={draftValue?.amount?.toString() ?? ''}
currencyCode={draftValue?.currencyCode ?? CurrencyCode.USD}
autoFocus
placeholder="Currency"
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onShiftTab={handleShiftTab}
onTab={handleTab}
hotkeyScope={hotkeyScope}
onChange={handleChange}
onSelect={handleSelect}
hotkeyScope={hotkeyScope}
/>
</FieldInputOverlay>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
Expand Down Expand Up @@ -31,7 +30,7 @@ export const computeDraftValueFromFieldValue = <FieldValue>({

return {
amount: fieldValue?.amountMicros ? fieldValue.amountMicros / 1000000 : '',
currenyCode: CurrencyCode.USD,
currencyCode: fieldValue?.currencyCode ?? '',
} as unknown as FieldInputDraftValue<FieldValue>;
}
if (isFieldRelation(fieldDefinition)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from '@emotion/styled';

const StyledTitle = 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)};
`;

export { StyledTitle as SettingsDataModelCardTitle };
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';
import { z } from 'zod';

import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { IconCircleOff } from '@/ui/display/icon';
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { Select, SelectOption } from '@/ui/input/components/Select';

export const settingsDataModelObjectIdentifiersFormSchema =
objectMetadataItemSchema.pick({
labelIdentifierFieldMetadataId: true,
imageIdentifierFieldMetadataId: true,
});

export type SettingsDataModelObjectIdentifiersFormValues = z.infer<
typeof settingsDataModelObjectIdentifiersFormSchema
>;

type SettingsDataModelObjectIdentifiersFormProps = {
objectMetadataItem: ObjectMetadataItem;
};

const StyledContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
`;

export const SettingsDataModelObjectIdentifiersForm = ({
objectMetadataItem,
}: SettingsDataModelObjectIdentifiersFormProps) => {
const { control } =
useFormContext<SettingsDataModelObjectIdentifiersFormValues>();
const { getIcon } = useIcons();

const labelIdentifierFieldOptions = useMemo(
() =>
getActiveFieldMetadataItems(objectMetadataItem)
.filter(
({ id, type }) =>
LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(type) ||
objectMetadataItem.labelIdentifierFieldMetadataId === id,
)
.map<SelectOption<string | null>>((fieldMetadataItem) => ({
Icon: getIcon(fieldMetadataItem.icon),
label: fieldMetadataItem.label,
value: fieldMetadataItem.id,
})),
[getIcon, objectMetadataItem],
);
const imageIdentifierFieldOptions: SelectOption<string | null>[] = [];

const emptyOption: SelectOption<string | null> = {
Icon: IconCircleOff,
label: 'None',
value: null,
};

return (
<StyledContainer>
{[
{
label: 'Record label',
fieldName: 'labelIdentifierFieldMetadataId' as const,
options: labelIdentifierFieldOptions,
},
{
label: 'Record image',
fieldName: 'imageIdentifierFieldMetadataId' as const,
options: imageIdentifierFieldOptions,
},
].map(({ fieldName, label, options }) => (
<Controller
key={fieldName}
name={fieldName}
control={control}
defaultValue={objectMetadataItem[fieldName]}
render={({ field: { onBlur, onChange, value } }) => {
return (
<Select
label={label}
disabled={!objectMetadataItem.isCustom || !options.length}
fullWidth
dropdownId={`${fieldName}-select`}
emptyOption={emptyOption}
options={options}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
);
}}
/>
))}
</StyledContainer>
);
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';

import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
import { SettingsDataModelCardTitle } from '@/settings/data-model/components/SettingsDataModelCardTitle';
import { SettingsDataModelFieldPreviewCard } from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard';
import {
SettingsDataModelObjectIdentifiersForm,
SettingsDataModelObjectIdentifiersFormValues,
} from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm';
import { SettingsDataModelObjectSummary } from '@/settings/data-model/objects/SettingsDataModelObjectSummary';
import { Card } from '@/ui/layout/card/components/Card';
import { CardContent } from '@/ui/layout/card/components/CardContent';
Expand All @@ -16,6 +22,10 @@ const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
width: 100%;
`;

const StyledTopCardContent = styled(CardContent)`
background-color: ${({ theme }) => theme.background.transparent.lighter};
`;

const StyledObjectSummaryCard = styled(Card)`
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.primary};
Expand All @@ -29,13 +39,27 @@ const StyledObjectSummaryCardContent = styled(CardContent)`
export const SettingsDataModelObjectSettingsFormCard = ({
objectMetadataItem,
}: SettingsDataModelObjectSettingsFormCardProps) => {
const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
const { watch: watchFormValue } =
useFormContext<SettingsDataModelObjectIdentifiersFormValues>();

const labelIdentifierFieldMetadataIdFormValue = watchFormValue(
'labelIdentifierFieldMetadataId',
);

const labelIdentifierFieldMetadataItem = useMemo(
() =>
getLabelIdentifierFieldMetadataItem({
fields: objectMetadataItem.fields,
labelIdentifierFieldMetadataId: labelIdentifierFieldMetadataIdFormValue,
}),
[labelIdentifierFieldMetadataIdFormValue, objectMetadataItem],
);

return (
<SettingsDataModelPreviewFormCard
preview={
labelIdentifierFieldMetadataItem ? (
<Card fullWidth>
<StyledTopCardContent divider>
<SettingsDataModelCardTitle>Preview</SettingsDataModelCardTitle>
{labelIdentifierFieldMetadataItem ? (
<StyledFieldPreviewCard
objectMetadataItem={objectMetadataItem}
fieldMetadataItem={labelIdentifierFieldMetadataItem}
Expand All @@ -49,8 +73,13 @@ export const SettingsDataModelObjectSettingsFormCard = ({
/>
</StyledObjectSummaryCardContent>
</StyledObjectSummaryCard>
)
}
/>
)}
</StyledTopCardContent>
<CardContent>
<SettingsDataModelObjectIdentifiersForm
objectMetadataItem={objectMetadataItem}
/>
</CardContent>
</Card>
);
};
1 change: 1 addition & 0 deletions packages/twenty-front/src/modules/ui/display/icon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export {
IconChevronsRight,
IconChevronUp,
IconCircleDot,
IconCircleOff,
IconClick,
IconCode,
IconCoins,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';

type H3TitleProps = {
title: ReactNode;
className?: string;
};

const StyledH3Title = styled.h3`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: 0;
margin-bottom: ${({ theme }) => theme.spacing(4)};
`;

export const H3Title = ({ title, className }: H3TitleProps) => {
return <StyledH3Title className={className}>{title}</StyledH3Title>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, StoryObj } from '@storybook/react';

import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';

import { H3Title } from '../H3Title';

const meta: Meta<typeof H3Title> = {
title: 'UI/Display/Typography/Title/H3Title',
component: H3Title,
decorators: [ComponentDecorator],
args: {
title: 'H3 title',
},
};

export default meta;

type Story = StoryObj<typeof H3Title>;

export const Default: Story = {
decorators: [ComponentDecorator],
};
Loading

0 comments on commit 7a5f525

Please sign in to comment.