Date formatting per workspace member settings (#6408)
Implement date formatting per workspace member settings We'll need another round to maybe initialize all workspaces on the default settings. For now the default behavior is to take system settings if nothing is found in DB. --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -6,7 +6,7 @@ import {
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { SettingsAppearance } from '../SettingsAppearance';
|
||||
import { SettingsAppearance } from '../profile/appearance/components/SettingsAppearance';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/SettingsAppearance',
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
|
||||
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
|
||||
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { getWorkspaceDateFormatFromDateFormat } from '@/localization/utils/getWorkspaceDateFormatFromDateFormat';
|
||||
import { getWorkspaceTimeFormatFromTimeFormat } from '@/localization/utils/getWorkspaceTimeFormatFromTimeFormat';
|
||||
import {
|
||||
WorkspaceMemberDateFormatEnum,
|
||||
WorkspaceMemberTimeFormatEnum,
|
||||
} from '~/generated/graphql';
|
||||
import { DateTimeSettingsDateFormatSelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsDateFormatSelect';
|
||||
import { DateTimeSettingsTimeFormatSelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsTimeFormatSelect';
|
||||
import { DateTimeSettingsTimeZoneSelect } from '~/pages/settings/profile/appearance/components/DateTimeSettingsTimeZoneSelect';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
export const DateTimeSettings = () => {
|
||||
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
const [dateTimeFormat, setDateTimeFormat] =
|
||||
useRecoilState(dateTimeFormatState);
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
});
|
||||
|
||||
const updateWorkspaceMember = async (changedFields: any) => {
|
||||
if (!currentWorkspaceMember?.id) {
|
||||
throw new Error('User is not logged in');
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOneRecord({
|
||||
idToUpdate: currentWorkspaceMember.id,
|
||||
updateOneRecordInput: changedFields,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isDefined(currentWorkspaceMember)) return;
|
||||
|
||||
const handleSettingsChange = (
|
||||
settingName: 'timeZone' | 'dateFormat' | 'timeFormat',
|
||||
value: string,
|
||||
) => {
|
||||
const workspaceMember: any = {};
|
||||
const dateTime: any = {};
|
||||
|
||||
switch (settingName) {
|
||||
case 'timeZone': {
|
||||
workspaceMember[settingName] = value;
|
||||
dateTime[settingName] = value === 'system' ? detectTimeZone() : value;
|
||||
break;
|
||||
}
|
||||
case 'dateFormat': {
|
||||
workspaceMember[settingName] = getWorkspaceDateFormatFromDateFormat(
|
||||
value as DateFormat,
|
||||
);
|
||||
dateTime[settingName] =
|
||||
(value as DateFormat) === DateFormat.SYSTEM
|
||||
? detectDateFormat()
|
||||
: (value as DateFormat);
|
||||
break;
|
||||
}
|
||||
case 'timeFormat': {
|
||||
workspaceMember[settingName] = getWorkspaceTimeFormatFromTimeFormat(
|
||||
value as TimeFormat,
|
||||
);
|
||||
dateTime[settingName] =
|
||||
(value as TimeFormat) === TimeFormat.SYSTEM
|
||||
? detectTimeFormat()
|
||||
: (value as TimeFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEmptyObject(dateTime)) {
|
||||
setDateTimeFormat({
|
||||
...dateTimeFormat,
|
||||
...dateTime,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEmptyObject(workspaceMember)) {
|
||||
setCurrentWorkspaceMember({
|
||||
...currentWorkspaceMember,
|
||||
...workspaceMember,
|
||||
});
|
||||
updateWorkspaceMember(workspaceMember);
|
||||
}
|
||||
};
|
||||
|
||||
const timeZone =
|
||||
currentWorkspaceMember.timeZone === 'system'
|
||||
? 'system'
|
||||
: dateTimeFormat.timeZone;
|
||||
|
||||
const dateFormat =
|
||||
currentWorkspaceMember.dateFormat === WorkspaceMemberDateFormatEnum.System
|
||||
? DateFormat.SYSTEM
|
||||
: dateTimeFormat.dateFormat;
|
||||
|
||||
const timeFormat =
|
||||
currentWorkspaceMember.timeFormat === WorkspaceMemberTimeFormatEnum.System
|
||||
? TimeFormat.SYSTEM
|
||||
: dateTimeFormat.timeFormat;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<DateTimeSettingsTimeZoneSelect
|
||||
value={timeZone}
|
||||
onChange={(value) => handleSettingsChange('timeZone', value)}
|
||||
/>
|
||||
<DateTimeSettingsDateFormatSelect
|
||||
value={dateFormat}
|
||||
onChange={(value) => handleSettingsChange('dateFormat', value)}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<DateTimeSettingsTimeFormatSelect
|
||||
value={timeFormat}
|
||||
onChange={(value) => handleSettingsChange('timeFormat', value)}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
type DateTimeSettingsDateFormatSelectProps = {
|
||||
value: DateFormat;
|
||||
onChange: (nextValue: DateFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsDateFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: DateTimeSettingsDateFormatSelectProps) => {
|
||||
const setTimeZone = timeZone === 'system' ? detectTimeZone() : timeZone;
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-date-format"
|
||||
dropdownWidth={218}
|
||||
label="Date format"
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: `System settings`,
|
||||
value: DateFormat.SYSTEM,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
setTimeZone,
|
||||
DateFormat.MONTH_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.MONTH_FIRST,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
setTimeZone,
|
||||
DateFormat.DAY_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.DAY_FIRST,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
setTimeZone,
|
||||
DateFormat.YEAR_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.YEAR_FIRST,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
type DateTimeSettingsTimeFormatSelectProps = {
|
||||
value: TimeFormat;
|
||||
onChange: (nextValue: TimeFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsTimeFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: DateTimeSettingsTimeFormatSelectProps) => {
|
||||
const setTimeZone = timeZone === 'system' ? detectTimeZone() : timeZone;
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-time-format"
|
||||
dropdownWidth={218}
|
||||
label="Time format"
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: 'System settings',
|
||||
value: TimeFormat.SYSTEM,
|
||||
},
|
||||
{
|
||||
label: `24h (${formatInTimeZone(
|
||||
Date.now(),
|
||||
setTimeZone,
|
||||
TimeFormat.HOUR_24,
|
||||
)})`,
|
||||
value: TimeFormat.HOUR_24,
|
||||
},
|
||||
{
|
||||
label: `12h (${formatInTimeZone(
|
||||
Date.now(),
|
||||
setTimeZone,
|
||||
TimeFormat.HOUR_12,
|
||||
)})`,
|
||||
value: TimeFormat.HOUR_12,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableTimeZoneOption';
|
||||
import { AVAILABLE_TIMEZONE_OPTIONS } from '@/settings/accounts/constants/AvailableTimezoneOptions';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
type DateTimeSettingsTimeZoneSelectProps = {
|
||||
value?: string;
|
||||
onChange: (nextValue: string) => void;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsTimeZoneSelect = ({
|
||||
value = detectTimeZone(),
|
||||
onChange,
|
||||
}: DateTimeSettingsTimeZoneSelectProps) => {
|
||||
return (
|
||||
<Select
|
||||
dropdownId="settings-accounts-calendar-time-zone"
|
||||
dropdownWidth={416}
|
||||
label="Time zone"
|
||||
fullWidth
|
||||
value={
|
||||
value === 'system'
|
||||
? 'System settings'
|
||||
: findAvailableTimeZoneOption(value)?.value
|
||||
}
|
||||
options={[
|
||||
{ label: 'System settings', value: 'system' },
|
||||
...AVAILABLE_TIMEZONE_OPTIONS,
|
||||
]}
|
||||
onChange={onChange}
|
||||
withSearchInput
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -6,6 +6,7 @@ import { ColorSchemePicker } from '@/ui/input/color-scheme/components/ColorSchem
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
|
||||
import { DateTimeSettings } from '~/pages/settings/profile/appearance/components/DateTimeSettings';
|
||||
|
||||
const StyledH1Title = styled(H1Title)`
|
||||
margin-bottom: 0;
|
||||
@ -22,6 +23,13 @@ export const SettingsAppearance = () => {
|
||||
<H2Title title="Theme" />
|
||||
<ColorSchemePicker value={colorScheme} onChange={setColorScheme} />
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Date and time"
|
||||
description="Configure how dates are displayed across the app"
|
||||
/>
|
||||
<DateTimeSettings />
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
Reference in New Issue
Block a user