feat: Add timezone architecture for consistent date/time handling
- Create dateUtils.ts with helpers for UTC conversion and timezone display - Add TimezoneSerializerMixin to include business_timezone in API responses - Update GeneralSettings timezone dropdown with IANA identifiers - Apply timezone mixin to Event, TimeBlock, and field mobile serializers - Document timezone architecture in CLAUDE.md All times stored in UTC, converted for display based on business timezone. If business_timezone is null, uses user's local timezone. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from '../api/client';
|
||||
import { X, Calendar, Clock, RotateCw, Zap } from 'lucide-react';
|
||||
import { formatLocalDate } from '../utils/dateUtils';
|
||||
|
||||
interface ScheduledTask {
|
||||
id: string;
|
||||
@@ -79,7 +80,7 @@ const EditTaskModal: React.FC<EditTaskModalProps> = ({ task, isOpen, onClose, on
|
||||
setScheduleMode('onetime');
|
||||
if (task.run_at) {
|
||||
const date = new Date(task.run_at);
|
||||
setRunAtDate(date.toISOString().split('T')[0]);
|
||||
setRunAtDate(formatLocalDate(date));
|
||||
setRunAtTime(date.toTimeString().slice(0, 5));
|
||||
}
|
||||
} else if (task.schedule_type === 'INTERVAL') {
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
Resource,
|
||||
TimeBlockListItem,
|
||||
} from '../../types';
|
||||
import { formatLocalDate } from '../../utils/dateUtils';
|
||||
|
||||
// Preset block types
|
||||
const PRESETS = [
|
||||
@@ -418,8 +419,8 @@ const TimeBlockCreatorModal: React.FC<TimeBlockCreatorModalProps> = ({
|
||||
if (recurrenceType === 'NONE') {
|
||||
if (selectedDates.length > 0) {
|
||||
const sorted = [...selectedDates].sort((a, b) => a.getTime() - b.getTime());
|
||||
data.start_date = sorted[0].toISOString().split('T')[0];
|
||||
data.end_date = sorted[sorted.length - 1].toISOString().split('T')[0];
|
||||
data.start_date = formatLocalDate(sorted[0]);
|
||||
data.end_date = formatLocalDate(sorted[sorted.length - 1]);
|
||||
}
|
||||
} else if (recurrenceType === 'WEEKLY') {
|
||||
data.recurrence_pattern = { days_of_week: daysOfWeek };
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { ChevronLeft, ChevronRight, CalendarDays, X } from 'lucide-react';
|
||||
import { BlockedDate, TimeBlockListItem } from '../../types';
|
||||
import { useBlockedDates, useTimeBlock } from '../../hooks/useTimeBlocks';
|
||||
import { formatLocalDate } from '../../utils/dateUtils';
|
||||
|
||||
interface YearlyBlockCalendarProps {
|
||||
resourceId?: string;
|
||||
@@ -134,7 +135,7 @@ const YearlyBlockCalendar: React.FC<YearlyBlockCalendarProps> = ({
|
||||
return <div key={`empty-${i}`} className="aspect-square" />;
|
||||
}
|
||||
|
||||
const dateKey = day.toISOString().split('T')[0];
|
||||
const dateKey = formatLocalDate(day);
|
||||
const blocks = blockedDateMap.get(dateKey) || [];
|
||||
const hasBlocks = blocks.length > 0;
|
||||
const isToday = new Date().toDateString() === day.toDateString();
|
||||
|
||||
Reference in New Issue
Block a user