mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-17 11:25:48 +02:00
Fix time format setting not applied to UI date/time displays (#75)
- Add time format utilities to date.ts (applyTimeFormat, formatDateTime,
formatTimeOnly, TimeFormat type)
- PrintersPage: Pass timeFormat to formatETA for printer card ETA display
- ArchivesPage: Add timeFormat prop to ArchiveCard for archive timestamps
- AMSHistoryModal: Apply timeFormat to chart labels, axis ticks, and tooltip
- ProjectDetailPage: Use formatDateTime for timeline dates
- QueuePage: Pass timeFormat to formatRelativeTime for scheduled prints
- SystemInfoPage: Use formatDateTime for boot time display
- NotificationLogViewer: Apply timeFormat to notification timestamps
- Bump service worker cache version to v17
Closes #75
This commit is contained in:
@@ -46,6 +46,12 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- Updates Projects page and Project Detail page instantly
|
||||
|
||||
### Fixed
|
||||
- **Time format setting not applied** - Fixed 24-hour time format not being respected across the UI:
|
||||
- ETA display on printer cards now uses configured time format
|
||||
- Archive cards and timelapse file lists respect the setting
|
||||
- AMS history charts use the configured format
|
||||
- Project timeline, queue page, notification logs, and system info all updated
|
||||
- Settings > General > Time Format now works consistently everywhere
|
||||
- **QR code endpoint** - Fixed 500 error on archive QR code generation:
|
||||
- Added `qrcode[pil]` to requirements.txt
|
||||
- Improved error handling for missing dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Bambuddy Service Worker
|
||||
const CACHE_NAME = 'bambuddy-v15';
|
||||
const STATIC_CACHE = 'bambuddy-static-v15';
|
||||
const CACHE_NAME = 'bambuddy-v17';
|
||||
const STATIC_CACHE = 'bambuddy-static-v17';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ReferenceLine,
|
||||
} from 'recharts';
|
||||
import { api, type AMSHistoryResponse } from '../api/client';
|
||||
import { parseUTCDate } from '../utils/date';
|
||||
import { parseUTCDate, applyTimeFormat, type TimeFormat } from '../utils/date';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
|
||||
@@ -58,6 +58,13 @@ export function AMSHistoryModal({
|
||||
const [mode, setMode] = useState<'humidity' | 'temperature'>(initialMode);
|
||||
const isDark = themeMode === 'dark';
|
||||
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ['settings'],
|
||||
queryFn: api.getSettings,
|
||||
});
|
||||
|
||||
const timeFormat: TimeFormat = settings?.time_format || 'system';
|
||||
|
||||
// Close on Escape key
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
@@ -82,15 +89,16 @@ export function AMSHistoryModal({
|
||||
// Format data for chart
|
||||
const chartData = data?.data.map(point => {
|
||||
const date = parseUTCDate(point.recorded_at) || new Date();
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
...(hours > 24 ? { day: 'numeric', month: 'short' } : {}),
|
||||
};
|
||||
return {
|
||||
time: date.getTime(),
|
||||
humidity: point.humidity,
|
||||
temperature: point.temperature,
|
||||
timeLabel: date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
...(hours > 24 ? { day: 'numeric', month: 'short' } : {}),
|
||||
}),
|
||||
timeLabel: date.toLocaleTimeString([], applyTimeFormat(timeOptions, timeFormat)),
|
||||
};
|
||||
}) || [];
|
||||
|
||||
@@ -311,7 +319,7 @@ export function AMSHistoryModal({
|
||||
if (hours > 24) {
|
||||
return date.toLocaleDateString([], { day: 'numeric', month: 'short' });
|
||||
}
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
return date.toLocaleTimeString([], applyTimeFormat({ hour: '2-digit', minute: '2-digit' }, timeFormat));
|
||||
}}
|
||||
stroke={isDark ? '#9ca3af' : '#6b7280'}
|
||||
tick={{ fontSize: 12 }}
|
||||
@@ -329,7 +337,13 @@ export function AMSHistoryModal({
|
||||
borderRadius: '8px',
|
||||
color: isDark ? '#fff' : '#000',
|
||||
}}
|
||||
labelFormatter={(ts) => new Date(ts).toLocaleString()}
|
||||
labelFormatter={(ts) => new Date(ts).toLocaleString(undefined, applyTimeFormat({
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}, timeFormat))}
|
||||
formatter={(value: number) => [
|
||||
mode === 'humidity' ? `${value}%` : `${value}°C`,
|
||||
mode === 'humidity' ? 'Humidity' : 'Temperature'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { History, CheckCircle, XCircle, Loader2, Trash2, RefreshCw, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { api } from '../api/client';
|
||||
import { parseUTCDate, formatDate as formatDateUtil } from '../utils/date';
|
||||
import { parseUTCDate, formatTimeOnly, formatDateTime, type TimeFormat } from '../utils/date';
|
||||
import type { NotificationLogEntry } from '../api/client';
|
||||
import { Button } from './Button';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
@@ -44,6 +44,13 @@ export function NotificationLogViewer({ onClose }: NotificationLogViewerProps) {
|
||||
const [expandedId, setExpandedId] = useState<number | null>(null);
|
||||
const [showFailedOnly, setShowFailedOnly] = useState(false);
|
||||
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ['settings'],
|
||||
queryFn: api.getSettings,
|
||||
});
|
||||
|
||||
const timeFormat: TimeFormat = settings?.time_format || 'system';
|
||||
|
||||
const { data: logs, isLoading, refetch, isRefetching } = useQuery({
|
||||
queryKey: ['notification-logs', days, showFailedOnly],
|
||||
queryFn: () => api.getNotificationLogs({
|
||||
@@ -80,7 +87,7 @@ export function NotificationLogViewer({ onClose }: NotificationLogViewerProps) {
|
||||
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
|
||||
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
|
||||
|
||||
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
return date.toLocaleDateString() + ' ' + formatTimeOnly(date, timeFormat);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -191,6 +198,7 @@ export function NotificationLogViewer({ onClose }: NotificationLogViewerProps) {
|
||||
isExpanded={expandedId === log.id}
|
||||
onToggle={() => setExpandedId(expandedId === log.id ? null : log.id)}
|
||||
formatDate={formatDate}
|
||||
formatFullDate={(dateStr) => formatDateTime(dateStr, timeFormat)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -213,11 +221,13 @@ function LogEntry({
|
||||
isExpanded,
|
||||
onToggle,
|
||||
formatDate,
|
||||
formatFullDate,
|
||||
}: {
|
||||
log: NotificationLogEntry;
|
||||
isExpanded: boolean;
|
||||
onToggle: () => void;
|
||||
formatDate: (date: string) => string;
|
||||
formatFullDate: (date: string) => string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@@ -280,7 +290,7 @@ function LogEntry({
|
||||
)}
|
||||
<div className="flex gap-4 text-xs text-bambu-gray pt-1">
|
||||
<span>Provider: {log.provider_type}</span>
|
||||
<span>Time: {formatDateUtil(log.created_at)}</span>
|
||||
<span>Time: {formatFullDate(log.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { api } from '../api/client';
|
||||
import { openInSlicer } from '../utils/slicer';
|
||||
import { formatDate, formatDateOnly, parseUTCDate } from '../utils/date';
|
||||
import { formatDateTime, formatDateOnly, parseUTCDate, type TimeFormat } from '../utils/date';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import type { Archive, ProjectListItem } from '../api/client';
|
||||
import { Card, CardContent } from '../components/Card';
|
||||
@@ -89,6 +89,7 @@ function ArchiveCard({
|
||||
selectionMode,
|
||||
projects,
|
||||
isHighlighted,
|
||||
timeFormat = 'system',
|
||||
}: {
|
||||
archive: Archive;
|
||||
printerName: string;
|
||||
@@ -97,6 +98,7 @@ function ArchiveCard({
|
||||
selectionMode: boolean;
|
||||
projects: ProjectListItem[] | undefined;
|
||||
isHighlighted?: boolean;
|
||||
timeFormat?: TimeFormat;
|
||||
}) {
|
||||
// Debug: log when card is highlighted
|
||||
if (isHighlighted) {
|
||||
@@ -669,7 +671,7 @@ function ArchiveCard({
|
||||
|
||||
{/* Date & Size */}
|
||||
<div className="flex items-center justify-between text-xs text-bambu-gray border-t border-bambu-dark-tertiary pt-3">
|
||||
<span>{formatDate(archive.created_at)}</span>
|
||||
<span>{formatDateTime(archive.created_at, timeFormat)}</span>
|
||||
<span>{formatFileSize(archive.file_size)}</span>
|
||||
</div>
|
||||
|
||||
@@ -890,7 +892,7 @@ function ArchiveCard({
|
||||
<p className="text-white font-medium truncate">{file.name}</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{formatFileSize(file.size)}
|
||||
{file.mtime && ` • ${formatDate(file.mtime)}`}
|
||||
{file.mtime && ` • ${formatDateTime(file.mtime, timeFormat)}`}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
@@ -1721,6 +1723,13 @@ export function ArchivesPage() {
|
||||
queryFn: () => api.getProjects(),
|
||||
});
|
||||
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ['settings'],
|
||||
queryFn: api.getSettings,
|
||||
});
|
||||
|
||||
const timeFormat: TimeFormat = settings?.time_format || 'system';
|
||||
|
||||
const bulkDeleteMutation = useMutation({
|
||||
mutationFn: async (ids: number[]) => {
|
||||
await Promise.all(ids.map((id) => api.deleteArchive(id)));
|
||||
@@ -2449,6 +2458,7 @@ export function ArchivesPage() {
|
||||
selectionMode={selectionMode}
|
||||
projects={projects}
|
||||
isHighlighted={archive.id === highlightedArchiveId}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -644,7 +644,7 @@ function formatTime(seconds: number): string {
|
||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
}
|
||||
|
||||
function formatETA(remainingMinutes: number): string {
|
||||
function formatETA(remainingMinutes: number, timeFormat: 'system' | '12h' | '24h' = 'system'): string {
|
||||
const now = new Date();
|
||||
const eta = new Date(now.getTime() + remainingMinutes * 60 * 1000);
|
||||
const today = new Date();
|
||||
@@ -652,7 +652,16 @@ function formatETA(remainingMinutes: number): string {
|
||||
const etaDay = new Date(eta);
|
||||
etaDay.setHours(0, 0, 0, 0);
|
||||
|
||||
const timeStr = eta.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
// Build time format options based on setting
|
||||
const timeOptions: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' };
|
||||
if (timeFormat === '12h') {
|
||||
timeOptions.hour12 = true;
|
||||
} else if (timeFormat === '24h') {
|
||||
timeOptions.hour12 = false;
|
||||
}
|
||||
// 'system' leaves hour12 undefined, letting the browser decide
|
||||
|
||||
const timeStr = eta.toLocaleTimeString([], timeOptions);
|
||||
|
||||
// Check if it's tomorrow or later
|
||||
const dayDiff = Math.floor((etaDay.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
||||
@@ -865,6 +874,7 @@ function PrinterCard({
|
||||
amsThresholds,
|
||||
spoolmanEnabled = false,
|
||||
hasUnlinkedSpools = false,
|
||||
timeFormat = 'system',
|
||||
}: {
|
||||
printer: Printer;
|
||||
hideIfDisconnected?: boolean;
|
||||
@@ -879,6 +889,7 @@ function PrinterCard({
|
||||
};
|
||||
spoolmanEnabled?: boolean;
|
||||
hasUnlinkedSpools?: boolean;
|
||||
timeFormat?: 'system' | '12h' | '24h';
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
@@ -1617,7 +1628,7 @@ function PrinterCard({
|
||||
{formatTime(status.remaining_time * 60)}
|
||||
</span>
|
||||
<span className="text-bambu-green font-medium" title="Estimated completion time">
|
||||
ETA {formatETA(status.remaining_time)}
|
||||
ETA {formatETA(status.remaining_time, timeFormat)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
@@ -3986,6 +3997,7 @@ export function PrintersPage() {
|
||||
} : undefined}
|
||||
spoolmanEnabled={spoolmanEnabled}
|
||||
hasUnlinkedSpools={hasUnlinkedSpools}
|
||||
timeFormat={settings?.time_format || 'system'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -4011,6 +4023,7 @@ export function PrintersPage() {
|
||||
tempGood: Number(settings.ams_temp_good) || 28,
|
||||
tempFair: Number(settings.ams_temp_fair) || 35,
|
||||
} : undefined}
|
||||
timeFormat={settings?.time_format || 'system'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
ShoppingCart,
|
||||
} from 'lucide-react';
|
||||
import { api } from '../api/client';
|
||||
import { parseUTCDate, formatDateOnly, formatDate as formatDateUtil } from '../utils/date';
|
||||
import { parseUTCDate, formatDateOnly, formatDateTime, type TimeFormat } from '../utils/date';
|
||||
import type { Archive, ProjectUpdate, BOMItem, BOMItemCreate } from '../api/client';
|
||||
import { Card, CardContent } from '../components/Card';
|
||||
import { Button } from '../components/Button';
|
||||
@@ -235,6 +235,7 @@ export function ProjectDetailPage() {
|
||||
});
|
||||
|
||||
const currency = settings?.currency || '$';
|
||||
const timeFormat: TimeFormat = settings?.time_format || 'system';
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: ProjectUpdate) => api.updateProject(projectId, data),
|
||||
@@ -403,7 +404,7 @@ export function ProjectDetailPage() {
|
||||
});
|
||||
|
||||
const formatTimelineDate = (timestamp: string) => {
|
||||
return formatDateUtil(timestamp, {
|
||||
return formatDateTime(timestamp, timeFormat, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
Hand,
|
||||
} from 'lucide-react';
|
||||
import { api } from '../api/client';
|
||||
import { parseUTCDate } from '../utils/date';
|
||||
import { parseUTCDate, formatDateTime, type TimeFormat } from '../utils/date';
|
||||
import type { PrintQueueItem } from '../api/client';
|
||||
import { Card, CardContent } from '../components/Card';
|
||||
import { Button } from '../components/Button';
|
||||
@@ -60,7 +60,7 @@ function formatDuration(seconds: number | null | undefined): string {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
function formatRelativeTime(dateString: string | null): string {
|
||||
function formatRelativeTime(dateString: string | null, timeFormat: TimeFormat = 'system'): string {
|
||||
if (!dateString) return 'ASAP';
|
||||
const date = parseUTCDate(dateString);
|
||||
if (!date) return 'ASAP';
|
||||
@@ -72,7 +72,7 @@ function formatRelativeTime(dateString: string | null): string {
|
||||
if (diff < 60000) return 'In less than a minute';
|
||||
if (diff < 3600000) return `In ${Math.round(diff / 60000)} min`;
|
||||
if (diff < 86400000) return `In ${Math.round(diff / 3600000)} hours`;
|
||||
return date.toLocaleString();
|
||||
return formatDateTime(dateString, timeFormat);
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: PrintQueueItem['status'] }) {
|
||||
@@ -105,6 +105,7 @@ function SortableQueueItem({
|
||||
onStop,
|
||||
onRequeue,
|
||||
onStart,
|
||||
timeFormat = 'system',
|
||||
}: {
|
||||
item: PrintQueueItem;
|
||||
position?: number;
|
||||
@@ -114,6 +115,7 @@ function SortableQueueItem({
|
||||
onStop: () => void;
|
||||
onRequeue: () => void;
|
||||
onStart: () => void;
|
||||
timeFormat?: TimeFormat;
|
||||
}) {
|
||||
const {
|
||||
attributes,
|
||||
@@ -206,7 +208,7 @@ function SortableQueueItem({
|
||||
{isPending && !item.manual_start && (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Clock className="w-3.5 h-3.5" />
|
||||
{formatRelativeTime(item.scheduled_time)}
|
||||
{formatRelativeTime(item.scheduled_time, timeFormat)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -377,6 +379,13 @@ export function QueuePage() {
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||
);
|
||||
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ['settings'],
|
||||
queryFn: api.getSettings,
|
||||
});
|
||||
|
||||
const timeFormat: TimeFormat = settings?.time_format || 'system';
|
||||
|
||||
const { data: queue, isLoading } = useQuery({
|
||||
queryKey: ['queue', filterPrinter, filterStatus],
|
||||
queryFn: () => api.getQueue(filterPrinter || undefined, filterStatus || undefined),
|
||||
@@ -659,6 +668,7 @@ export function QueuePage() {
|
||||
onStop={() => setConfirmAction({ type: 'stop', item })}
|
||||
onRequeue={() => {}}
|
||||
onStart={() => {}}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -722,6 +732,7 @@ export function QueuePage() {
|
||||
onStop={() => {}}
|
||||
onRequeue={() => {}}
|
||||
onStart={() => startMutation.mutate(item.id)}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -774,6 +785,7 @@ export function QueuePage() {
|
||||
onStop={() => {}}
|
||||
onRequeue={() => setRequeueItem(item)}
|
||||
onStart={() => {}}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { api, supportApi } from '../api/client';
|
||||
import { Card } from '../components/Card';
|
||||
import { formatDateTime, type TimeFormat } from '../utils/date';
|
||||
|
||||
function StatCard({
|
||||
icon: Icon,
|
||||
@@ -102,6 +103,13 @@ export function SystemInfoPage() {
|
||||
refetchInterval: 10 * 1000,
|
||||
});
|
||||
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ['settings'],
|
||||
queryFn: api.getSettings,
|
||||
});
|
||||
|
||||
const timeFormat: TimeFormat = settings?.time_format || 'system';
|
||||
|
||||
const handleToggleDebugLogging = async () => {
|
||||
setDebugToggling(true);
|
||||
try {
|
||||
@@ -525,7 +533,7 @@ export function SystemInfoPage() {
|
||||
<StatCard
|
||||
icon={Clock}
|
||||
label={t('system.bootTime', 'Boot Time')}
|
||||
value={new Date(systemInfo.system.boot_time).toLocaleString()}
|
||||
value={formatDateTime(systemInfo.system.boot_time, timeFormat)}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -6,6 +6,25 @@
|
||||
* displayed in the user's local timezone.
|
||||
*/
|
||||
|
||||
export type TimeFormat = 'system' | '12h' | '24h';
|
||||
|
||||
/**
|
||||
* Apply time format setting to Intl.DateTimeFormatOptions.
|
||||
* This modifies the options object in place and returns it.
|
||||
*/
|
||||
export function applyTimeFormat(
|
||||
options: Intl.DateTimeFormatOptions,
|
||||
timeFormat: TimeFormat = 'system'
|
||||
): Intl.DateTimeFormatOptions {
|
||||
if (timeFormat === '12h') {
|
||||
options.hour12 = true;
|
||||
} else if (timeFormat === '24h') {
|
||||
options.hour12 = false;
|
||||
}
|
||||
// 'system' leaves hour12 undefined, letting the browser decide
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date string from the backend as UTC.
|
||||
* Handles ISO 8601 strings with or without timezone indicators.
|
||||
@@ -72,3 +91,53 @@ export function formatDateOnly(
|
||||
|
||||
return date.toLocaleDateString(undefined, options ?? defaultOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a UTC date string to a localized date/time string with time format support.
|
||||
*
|
||||
* @param dateStr - Date string from backend
|
||||
* @param timeFormat - Time format setting ('system', '12h', '24h')
|
||||
* @param options - Intl.DateTimeFormat options (defaults to showing date and time)
|
||||
* @returns Formatted date string in user's locale and timezone
|
||||
*/
|
||||
export function formatDateTime(
|
||||
dateStr: string | null | undefined,
|
||||
timeFormat: TimeFormat = 'system',
|
||||
options?: Intl.DateTimeFormatOptions
|
||||
): string {
|
||||
const date = parseUTCDate(dateStr);
|
||||
if (!date) return '';
|
||||
|
||||
const defaultOptions: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
};
|
||||
|
||||
const finalOptions = applyTimeFormat(options ?? defaultOptions, timeFormat);
|
||||
return date.toLocaleString(undefined, finalOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a Date object to a localized time string with time format support.
|
||||
*
|
||||
* @param date - Date object
|
||||
* @param timeFormat - Time format setting ('system', '12h', '24h')
|
||||
* @param options - Additional Intl.DateTimeFormat options
|
||||
* @returns Formatted time string
|
||||
*/
|
||||
export function formatTimeOnly(
|
||||
date: Date,
|
||||
timeFormat: TimeFormat = 'system',
|
||||
options?: Intl.DateTimeFormatOptions
|
||||
): string {
|
||||
const defaultOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
};
|
||||
|
||||
const finalOptions = applyTimeFormat({ ...defaultOptions, ...options }, timeFormat);
|
||||
return date.toLocaleTimeString([], finalOptions);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-CMSE_lmd.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-B03CK04P.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-48h_gFgJ.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
// Bambuddy Service Worker
|
||||
const CACHE_NAME = 'bambuddy-v15';
|
||||
const STATIC_CACHE = 'bambuddy-static-v15';
|
||||
const CACHE_NAME = 'bambuddy-v16';
|
||||
const STATIC_CACHE = 'bambuddy-static-v16';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
|
||||
Reference in New Issue
Block a user