feat: implement time format preference across calendar and email components

This commit is contained in:
Linus Rath
2026-03-17 21:39:59 +01:00
parent 828ad4df72
commit 5c933a595f
9 changed files with 71 additions and 40 deletions
@@ -3,10 +3,11 @@
import { useState, useRef, useEffect } from "react";
import { useTranslations } from "next-intl";
import { Globe, Plus, RefreshCw, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn, formatDateTime } from "@/lib/utils";
import type { Calendar } from "@/lib/jmap/types";
import { CalendarColorPicker } from "@/components/settings/calendar-management-settings";
import { useCalendarStore } from "@/stores/calendar-store";
import { useSettingsStore } from "@/stores/settings-store";
import { toast } from "@/stores/toast-store";
import type { JMAPClient } from "@/lib/jmap/client";
@@ -33,6 +34,7 @@ export function CalendarSidebarPanel({
const icalSubscriptions = useCalendarStore((s) => s.icalSubscriptions);
const refreshICalSubscription = useCalendarStore((s) => s.refreshICalSubscription);
const removeICalSubscription = useCalendarStore((s) => s.removeICalSubscription);
const timeFormat = useSettingsStore((s) => s.timeFormat);
const [colorPickerId, setColorPickerId] = useState<string | null>(null);
const [contextMenuCalId, setContextMenuCalId] = useState<string | null>(null);
@@ -169,7 +171,7 @@ export function CalendarSidebarPanel({
</button>
{sub.lastRefreshed && (
<div className="px-3 py-1.5 text-xs text-muted-foreground border-t border-border mt-1 pt-1">
{tSub('last_refreshed', { time: new Date(sub.lastRefreshed).toLocaleString() })}
{tSub('last_refreshed', { time: formatDateTime(sub.lastRefreshed, timeFormat, { month: 'short', day: 'numeric', year: 'numeric' }) })}
</div>
)}
</div>
+4 -1
View File
@@ -7,6 +7,7 @@ import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
import { format, parseISO } from "date-fns";
import { Users } from "lucide-react";
import { getParticipantCount } from "@/lib/calendar-participants";
import { useSettingsStore } from "@/stores/settings-store";
interface EventCardProps {
event: CalendarEvent;
@@ -68,11 +69,13 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM
const [isBeingDragged, setIsBeingDragged] = useState(false);
const color = getEventColor(event, calendar);
const startDate = parseISO(event.start);
const timeFormat = useSettingsStore((state) => state.timeFormat);
const timeFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
const calendarName = calendar?.name || "";
const durationMinutes = parseDuration(event.duration);
const endTime = new Date(startDate.getTime() + durationMinutes * 60000);
const timeString = `${format(startDate, "HH:mm")} ${format(endTime, "HH:mm")}`;
const timeString = `${format(startDate, timeFmt)} ${format(endTime, timeFmt)}`;
const ariaLabel = `${event.title || t("events.no_title")}, ${timeString}${calendarName ? `, ${calendarName}` : ""}`;
const handleDragStart = useCallback((e: DragEvent) => {
+5 -2
View File
@@ -18,6 +18,7 @@ import {
getStatusCounts,
buildParticipantMap,
} from "@/lib/calendar-participants";
import { useSettingsStore } from "@/stores/settings-store";
interface EventModalProps {
event?: CalendarEvent | null;
@@ -108,6 +109,8 @@ export function EventModal({
isMobile = false,
}: EventModalProps) {
const t = useTranslations("calendar");
const timeFormat = useSettingsStore((s) => s.timeFormat);
const timeDisplayFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
const isEdit = !!event;
const [mode, setMode] = useState<"view" | "edit">(isEdit ? "view" : "edit");
@@ -452,7 +455,7 @@ export function EventModal({
<span className="font-medium">{format(startD, "EEE, MMM d, yyyy")}</span>
{!event.showWithoutTime && (
<span className="text-muted-foreground ml-2">
{format(startD, "HH:mm")} {format(endD, "HH:mm")}
{format(startD, timeDisplayFmt)} {format(endD, timeDisplayFmt)}
</span>
)}
</div>
@@ -576,7 +579,7 @@ export function EventModal({
<span className="text-muted-foreground ml-1.5">{t("events.all_day")}</span>
) : (
<div className="text-muted-foreground">
{format(startD, "HH:mm")} {format(endD, "HH:mm")}
{format(startD, timeDisplayFmt)} {format(endD, timeDisplayFmt)}
<span className="ml-1.5 text-xs">({formatDurationDisplay(durMin)})</span>
</div>
)}
+4 -1
View File
@@ -8,6 +8,7 @@ import { format, parseISO } from "date-fns";
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
import type { JMAPClient } from "@/lib/jmap/client";
import { useCalendarStore } from "@/stores/calendar-store";
import { useSettingsStore } from "@/stores/settings-store";
import { toast } from "@/stores/toast-store";
interface ICalImportModalProps {
@@ -28,6 +29,7 @@ export function ICalImportModal({ calendars, client, onClose }: ICalImportModalP
const tCommon = useTranslations("common");
const tForm = useTranslations("calendar.form");
const importEvents = useCalendarStore((s) => s.importEvents);
const timeFormat = useSettingsStore((s) => s.timeFormat);
const [step, setStep] = useState<ImportStep>("select");
const [parsedEvents, setParsedEvents] = useState<Partial<CalendarEvent>[]>([]);
@@ -194,9 +196,10 @@ export function ICalImportModal({ calendars, client, onClose }: ICalImportModalP
if (!event.start) return "";
try {
const date = parseISO(event.start);
const timeFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
return event.showWithoutTime
? format(date, "MMM d, yyyy")
: format(date, "MMM d, yyyy HH:mm");
: format(date, `MMM d, yyyy ${timeFmt}`);
} catch {
return event.start;
}