Centralize timezone display in the date helper

Add date.getTimezoneAbbr() and route every "GMT-x" abbreviation through it,
removing the three inline dayjs().format('z') copies (schedule-summary,
TriggerNodeConfig, PickTimePopover) and the duplicate userTimezone helper.
Drop the pointless computed wrapper for a value that never changes, and rename
snap5 to snapToFiveMinutes.
This commit is contained in:
Paulo Castellano 2026-06-13 16:46:34 -03:00
parent ff8fd9de40
commit 90e9e4bee6
4 changed files with 19 additions and 16 deletions

View file

@ -5,8 +5,6 @@ import {
generateScheduleCron,
humanSchedule as scheduleSummary,
normalizeScheduleData,
timezoneAbbr as getTimezoneAbbr,
userTimezone as getUserTimezone,
} from '@/components/automations/schedule-summary';
import InputError from '@/components/InputError.vue';
import { Input } from '@/components/ui/input';
@ -18,6 +16,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import date from '@/date';
import type { ScheduleData } from '@/types/automation/schedule-data';
import { ScheduleField } from '@/types/automation/schedule-field';
import { TriggerType, type TriggerTypeValue } from '@/types/automation/trigger-type';
@ -30,9 +29,9 @@ const emit = defineEmits<{ update: [Record<string, unknown>] }>();
const pad2 = (n: number) => String(n).padStart(2, '0');
const clamp = (n: number, min: number, max: number) => Math.min(Math.max(n, min), max);
const snap5 = (n: number) => (Math.round(n / 5) * 5) % 60;
const snapToFiveMinutes = (n: number) => (Math.round(n / 5) * 5) % 60;
const timezoneAbbr = computed(() => getTimezoneAbbr());
const timezoneAbbr = date.getTimezoneAbbr();
// Single source of truth for default + inferred field values, shared with the
// Trigger card via `triggerSummary`. Anything beyond `normalizeScheduleData`'s
@ -41,8 +40,8 @@ const local = ref<ScheduleData & { trigger_type: TriggerTypeValue; cron: string;
...normalizeScheduleData(props.data as ScheduleData),
trigger_type: (props.data.trigger_type as TriggerTypeValue) ?? TriggerType.Schedule,
cron: (props.data.cron as string) ?? '0 9 * * *',
schedule_minute: snap5(Number(props.data.schedule_minute ?? normalizeScheduleData(props.data as ScheduleData).schedule_minute) || 0),
schedule_timezone: (props.data.schedule_timezone as string) ?? getUserTimezone(),
schedule_minute: snapToFiveMinutes(Number(props.data.schedule_minute ?? normalizeScheduleData(props.data as ScheduleData).schedule_minute) || 0),
schedule_timezone: (props.data.schedule_timezone as string) ?? date.getUserTimezone(),
});
const num = (key: keyof typeof local.value, fallback: number, min: number, max: number) =>
@ -97,14 +96,14 @@ const scheduleSummaryLine = computed(() => {
|| local.value.schedule_field === ScheduleField.Months;
return pinsClockTime
? `${humanSchedule.value} (${timezoneAbbr.value})`
? `${humanSchedule.value} (${timezoneAbbr})`
: humanSchedule.value;
});
watch(generatedCron, (cron) => {
if (local.value.trigger_type === TriggerType.Schedule) {
local.value.cron = cron;
local.value.schedule_timezone = getUserTimezone();
local.value.schedule_timezone = date.getUserTimezone();
}
}, { immediate: true });

View file

@ -1,6 +1,5 @@
import { trans, transChoice } from 'laravel-vue-i18n';
import dayjs from '@/dayjs';
import type { ScheduleData } from '@/types/automation/schedule-data';
import { ScheduleField } from '@/types/automation/schedule-field';
import { TriggerType } from '@/types/automation/trigger-type';
@ -155,7 +154,3 @@ export const triggerSummary = (data: ScheduleData): string => {
}
return '';
};
export const userTimezone = (): string => Intl.DateTimeFormat().resolvedOptions().timeZone;
export const timezoneAbbr = (): string => dayjs().format('z');

View file

@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import date from '@/date';
import dayjs from '@/dayjs';
const props = defineProps<{
@ -14,7 +15,7 @@ const props = defineProps<{
showRemove?: boolean;
}>();
const timezoneAbbr = computed(() => dayjs().format('z'));
const timezoneAbbr = date.getTimezoneAbbr();
const emit = defineEmits<{
'update:modelValue': [value: string];
@ -27,8 +28,8 @@ const open = ref(false);
const parseInput = (value: string) => {
if (!value) return undefined;
try {
const date = dayjs(value);
if (date.isValid()) return parseDate(date.format('YYYY-MM-DD'));
const parsed = dayjs(value);
if (parsed.isValid()) return parseDate(parsed.format('YYYY-MM-DD'));
} catch {
return undefined;
}

View file

@ -155,6 +155,14 @@ export default {
*/
getUserTimezone,
/**
* Abreviação do timezone do usuário para exibição (ex.: "GMT-3")
* @returns Abreviação do timezone
*/
getTimezoneAbbr(): string {
return dayjs().format('z');
},
/**
* Formata uma data para o formato YYYY-MM-DD (usado em DatePicker)
* Evita problemas de timezone ao não criar objeto Date