feat: allow users to start the calendar week on Monday (#13295)
resolve twentyhq/core-team-issues#1255 - Introduced a new field isWeekStartMonday in the workspaceMember table. - Added a function updateCalendarStartDay to update the isWeekStartMonday value when the user toggles the corresponding field in the settings. The logic works for me, but I couldn’t find a way to create the column locally in the database for the workspaceMember tables. Please let me know if this approach looks good and how to properly create dynamic columns that are not directly handled via migration. https://github.com/user-attachments/assets/4eebada5-6a96-4a88-8e96-98f1501859e3 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
type DateTimeSettingsDateFormatSelectProps = {
|
||||
value: DateFormat;
|
||||
onChange: (nextValue: DateFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsDateFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: DateTimeSettingsDateFormatSelectProps) => {
|
||||
const systemTimeZone = detectTimeZone();
|
||||
|
||||
const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone;
|
||||
|
||||
const systemDateFormat = DateFormat[detectDateFormat()];
|
||||
|
||||
const systemDateFormatLabel = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
systemDateFormat,
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-date-format"
|
||||
dropdownWidth={218}
|
||||
label={t`Date format`}
|
||||
fullWidth
|
||||
dropdownWidthAuto
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: t`System settings - ${systemDateFormatLabel}`,
|
||||
value: DateFormat.SYSTEM,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
DateFormat.MONTH_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.MONTH_FIRST,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
DateFormat.DAY_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.DAY_FIRST,
|
||||
},
|
||||
{
|
||||
label: `${formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
DateFormat.YEAR_FIRST,
|
||||
)}`,
|
||||
value: DateFormat.YEAR_FIRST,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
type DateTimeSettingsTimeFormatSelectProps = {
|
||||
value: TimeFormat;
|
||||
onChange: (nextValue: TimeFormat) => void;
|
||||
timeZone: string;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsTimeFormatSelect = ({
|
||||
onChange,
|
||||
timeZone,
|
||||
value,
|
||||
}: DateTimeSettingsTimeFormatSelectProps) => {
|
||||
const { t } = useLingui();
|
||||
const systemTimeZone = detectTimeZone();
|
||||
|
||||
const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone;
|
||||
|
||||
const systemTimeFormat = TimeFormat[detectTimeFormat()];
|
||||
|
||||
const systemTimeFormatLabel = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
systemTimeFormat,
|
||||
);
|
||||
|
||||
const hour24Label = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
TimeFormat.HOUR_24,
|
||||
);
|
||||
|
||||
const hour12Label = formatInTimeZone(
|
||||
Date.now(),
|
||||
usedTimeZone,
|
||||
TimeFormat.HOUR_12,
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-time-format"
|
||||
dropdownWidth={218}
|
||||
label={t`Time format`}
|
||||
dropdownWidthAuto
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: t`System Settings - ${systemTimeFormatLabel}`,
|
||||
value: TimeFormat.SYSTEM,
|
||||
},
|
||||
{
|
||||
label: t`24h (${hour24Label})`,
|
||||
value: TimeFormat.HOUR_24,
|
||||
},
|
||||
{
|
||||
label: t`12h (${hour12Label})`,
|
||||
value: TimeFormat.HOUR_12,
|
||||
},
|
||||
]}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableTimeZoneOption';
|
||||
import { AVAILABLE_TIMEZONE_OPTIONS } from '@/settings/experience/constants/AvailableTimezoneOptions';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
type DateTimeSettingsTimeZoneSelectProps = {
|
||||
value?: string;
|
||||
onChange: (nextValue: string) => void;
|
||||
};
|
||||
|
||||
export const DateTimeSettingsTimeZoneSelect = ({
|
||||
value = detectTimeZone(),
|
||||
onChange,
|
||||
}: DateTimeSettingsTimeZoneSelectProps) => {
|
||||
const systemTimeZone = detectTimeZone();
|
||||
|
||||
const systemTimeZoneOption = findAvailableTimeZoneOption(systemTimeZone);
|
||||
|
||||
return (
|
||||
<Select
|
||||
dropdownId="datetime-settings-time-zone"
|
||||
label={t`Time zone`}
|
||||
dropdownWidthAuto
|
||||
fullWidth
|
||||
value={value}
|
||||
options={[
|
||||
{
|
||||
label: isDefined(systemTimeZoneOption)
|
||||
? t`System settings`.concat(` - ${systemTimeZoneOption.label}`)
|
||||
: t`System settings`,
|
||||
value: 'system',
|
||||
},
|
||||
...(AVAILABLE_TIMEZONE_OPTIONS as SelectOption<string>[]),
|
||||
]}
|
||||
onChange={onChange}
|
||||
withSearchInput
|
||||
/>
|
||||
);
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { getTimezoneOffset } from 'date-fns-tz';
|
||||
|
||||
import { AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL } from '@/settings/experience/constants/AvailableTimezoneOptionsByLabel';
|
||||
|
||||
export const AVAILABLE_TIMEZONE_OPTIONS = Object.values(
|
||||
AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL,
|
||||
).sort((optionA, optionB) => {
|
||||
const difference =
|
||||
getTimezoneOffset(optionA.value) - getTimezoneOffset(optionB.value);
|
||||
|
||||
return difference === 0
|
||||
? // Sort alphabetically if the time zone offsets are the same.
|
||||
optionA.label.localeCompare(optionB.label)
|
||||
: // Sort by time zone offset if different.
|
||||
difference;
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { IANA_TIME_ZONES } from '@/localization/constants/IanaTimeZones';
|
||||
import { formatTimeZoneLabel } from '@/localization/utils/formatTimeZoneLabel';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
export const AVAILABLE_TIME_ZONE_OPTIONS_BY_LABEL = IANA_TIME_ZONES.reduce<
|
||||
Record<string, SelectOption>
|
||||
>((result, ianaTimeZone) => {
|
||||
const timeZoneLabel = formatTimeZoneLabel(ianaTimeZone);
|
||||
|
||||
// Remove the '(GMT±00:00) ' prefix from the label.
|
||||
const timeZoneName = timeZoneLabel.slice(11);
|
||||
|
||||
// Skip time zones with GMT, UTC, or UCT in their name,
|
||||
// and duplicates.
|
||||
if (
|
||||
timeZoneName.includes('GMT') ||
|
||||
timeZoneName.includes('UTC') ||
|
||||
timeZoneName.includes('UCT') ||
|
||||
timeZoneLabel in result
|
||||
) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
[timeZoneLabel]: { label: timeZoneLabel, value: ianaTimeZone },
|
||||
};
|
||||
}, {});
|
||||
Reference in New Issue
Block a user