From 2ef837680e2b3f3260685b962c9c5d7273d0f8bc Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:13:55 +0200 Subject: [PATCH] fix(i18n): honor general_time_format across all time displays Route 10 surfaces that hardcoded 12-hour date-fns patterns ('h:mm a', 'PPp', 'p', toLocaleTimeString) through useLocalizedDate's formatDateTime/formatTime so they respect general_date_format + general_time_format: backup/restore, archives, photo viewer, feedback, event details, gallery timeline, public quote page, CMS save indicator. Also pin a lang hint on the business-hours native time inputs (Chrome/Edge render 24h). Drops now-unused date-fns imports. --- frontend/src/components/admin/AdminPhotoViewer.tsx | 7 ++++--- .../src/components/admin/FeedbackModerationPanel.tsx | 10 ++-------- frontend/src/components/admin/RestoreWizard.jsx | 7 ++++--- .../gallery/layouts/TimelineGalleryLayout.tsx | 4 +++- frontend/src/pages/admin/ArchivesPage.tsx | 4 +++- frontend/src/pages/admin/BackupManagement.tsx | 5 +++-- frontend/src/pages/admin/CMSPage.tsx | 4 ++-- frontend/src/pages/admin/EventDetailsPage.tsx | 4 ++-- frontend/src/pages/admin/EventFeedbackPage.tsx | 6 ++++-- frontend/src/pages/public/QuoteResponsePage.tsx | 4 ++-- 10 files changed, 29 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/admin/AdminPhotoViewer.tsx b/frontend/src/components/admin/AdminPhotoViewer.tsx index 8acbffe1..00cbfc2a 100644 --- a/frontend/src/components/admin/AdminPhotoViewer.tsx +++ b/frontend/src/components/admin/AdminPhotoViewer.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { X, ChevronLeft, ChevronRight, Download, Trash2, Tag, Calendar, HardDrive, Eye, MousePointer, MessageSquare, Star, Heart, CheckCircle, XCircle, AlertCircle } from 'lucide-react'; -import { format } from 'date-fns'; import { toast } from 'react-toastify'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; @@ -10,6 +9,7 @@ import { feedbackService, type PhotoFeedback, type FeedbackSummary } from '../.. import { Button } from '../common'; import { AdminAuthenticatedImage } from './AdminAuthenticatedImage'; import { AdminAuthenticatedVideo } from './AdminAuthenticatedVideo'; +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; type AdminFeedbackResponse = { feedback: PhotoFeedback[]; @@ -38,6 +38,7 @@ export const AdminPhotoViewer: React.FC = ({ const [showCategoryMenu, setShowCategoryMenu] = useState(false); const [expandedComments, setExpandedComments] = useState(false); const queryClient = useQueryClient(); + const { formatDateTime: fmtDateTime } = useLocalizedDate(); const currentPhoto = photos[currentIndex]; const isVideo = currentPhoto @@ -312,7 +313,7 @@ export const AdminPhotoViewer: React.FC = ({ Uploaded

- {format(new Date(currentPhoto.uploaded_at), 'MMM d, yyyy h:mm a')} + {fmtDateTime(currentPhoto.uploaded_at)}

@@ -408,7 +409,7 @@ export const AdminPhotoViewer: React.FC = ({ {comment.guest_name || 'Anonymous'}

- {format(new Date(comment.created_at), 'MMM d, yyyy h:mm a')} + {fmtDateTime(comment.created_at)}

diff --git a/frontend/src/components/admin/FeedbackModerationPanel.tsx b/frontend/src/components/admin/FeedbackModerationPanel.tsx index 23b9e80e..75592e70 100644 --- a/frontend/src/components/admin/FeedbackModerationPanel.tsx +++ b/frontend/src/components/admin/FeedbackModerationPanel.tsx @@ -8,7 +8,6 @@ import { CheckCircle, User } from 'lucide-react'; -import { parseISO } from 'date-fns'; import { toast } from 'react-toastify'; import { Card, Loading, Button } from '../common'; @@ -30,7 +29,7 @@ export const FeedbackModerationPanel: React.FC = ( maxItems = 5 }) => { const { t } = useTranslation(); - const { format } = useLocalizedDate(); + const { formatDateTime } = useLocalizedDate(); const queryClient = useQueryClient(); const [showAll, setShowAll] = useState(false); @@ -116,12 +115,7 @@ export const FeedbackModerationPanel: React.FC = ( - {format( - typeof item.created_at === 'string' - ? parseISO(item.created_at) - : new Date(item.created_at), - 'MMM d, h:mm a' - )} + {formatDateTime(item.created_at)}

{item.comment_text || item.comment}

diff --git a/frontend/src/components/admin/RestoreWizard.jsx b/frontend/src/components/admin/RestoreWizard.jsx index 31d152ab..74f6b047 100644 --- a/frontend/src/components/admin/RestoreWizard.jsx +++ b/frontend/src/components/admin/RestoreWizard.jsx @@ -24,14 +24,15 @@ import { AlertCircle, ShieldCheck } from 'lucide-react'; -import { format } from 'date-fns'; import { toast } from 'react-toastify'; import { useQuery, useMutation } from '@tanstack/react-query'; import { Button, Card, Input, Loading } from '../common'; import { api } from '../../config/api'; +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; export const RestoreWizard = ({ onVerifyIntegrity } = {}) => { const { t } = useTranslation(); + const { format: fmtDate, formatTime: fmtTime, formatDateTime: fmtDateTime } = useLocalizedDate(); const [currentStep, setCurrentStep] = useState(0); const steps = [ @@ -331,7 +332,7 @@ export const RestoreWizard = ({ onVerifyIntegrity } = {}) => {

- {format(new Date(backup.created_at), 'PPP')} {t('backup.restore.backup.at')} {format(new Date(backup.created_at), 'p')} + {fmtDate(backup.created_at)} {t('backup.restore.backup.at')} {fmtTime(backup.created_at)}

{t('backup.dashboard.backupType', { type: backup.backup_type })} • {formatBytes(backup.total_size || 0)} @@ -586,7 +587,7 @@ export const RestoreWizard = ({ onVerifyIntegrity } = {}) => {

{t('backup.restore.confirmation.summary.backupDate')}:
- {format(new Date(restoreData.selectedBackup.created_at), 'PPp')} + {fmtDateTime(restoreData.selectedBackup.created_at)}
diff --git a/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx b/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx index ebaa361a..20cebebc 100644 --- a/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx @@ -8,6 +8,7 @@ import type { Photo } from '../../../types'; import { FeedbackIdentityModal } from '../../gallery/FeedbackIdentityModal'; import { feedbackService } from '../../../services/feedback.service'; import { useGuestIdentityOptional } from '../../../contexts/GuestIdentityContext'; +import { useLocalizedDate } from '../../../hooks/useLocalizedDate'; export const TimelineGalleryLayout: React.FC = ({ photos, @@ -23,6 +24,7 @@ export const TimelineGalleryLayout: React.FC = ({ feedbackOptions }) => { const { theme } = useTheme(); + const { formatTime: fmtTime } = useLocalizedDate(); const [likedIds, setLikedIds] = useState>(new Set()); // Seed from server is_liked on first non-empty payload (#590 follow-up). // Mount-only so refetches don't clobber in-session optimistic toggles. @@ -124,7 +126,7 @@ export const TimelineGalleryLayout: React.FC = ({ {/* Time label */}
- {format(parseISO(photo.uploaded_at), 'h:mm a')} + {fmtTime(photo.uploaded_at)}
diff --git a/frontend/src/pages/admin/ArchivesPage.tsx b/frontend/src/pages/admin/ArchivesPage.tsx index 98ec599c..9ff55597 100644 --- a/frontend/src/pages/admin/ArchivesPage.tsx +++ b/frontend/src/pages/admin/ArchivesPage.tsx @@ -19,10 +19,12 @@ import { Button, Input, Card, Loading } from '../../components/common'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { archiveService } from '../../services/archive.service'; import { useTranslation } from 'react-i18next'; +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; // import { useNavigate } from 'react-router-dom'; export const ArchivesPage: React.FC = () => { const { t } = useTranslation(); + const { formatTime: fmtTime } = useLocalizedDate(); const [searchTerm, setSearchTerm] = useState(''); const [filterType, setFilterType] = useState('all'); const [sortBy, setSortBy] = useState<'date' | 'size' | 'name'>('date'); @@ -286,7 +288,7 @@ export const ArchivesPage: React.FC = () => {

{formatDate(archive.archivedAt, 'MMM d, yyyy') || t('archives.processing')}

- {formatDate(archive.archivedAt, 'h:mm a')} + {archive.archivedAt ? fmtTime(archive.archivedAt) : ''}

diff --git a/frontend/src/pages/admin/BackupManagement.tsx b/frontend/src/pages/admin/BackupManagement.tsx index 43e89774..1e6532e8 100644 --- a/frontend/src/pages/admin/BackupManagement.tsx +++ b/frontend/src/pages/admin/BackupManagement.tsx @@ -15,10 +15,10 @@ import { } from 'lucide-react'; import { toast } from 'react-toastify'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { format } from 'date-fns'; import { useTranslation } from 'react-i18next'; import { Button, Card, Loading } from '../../components/common'; +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; import { BackupDashboard } from '../../components/admin/BackupDashboard'; import { BackupConfiguration } from '../../components/admin/BackupConfiguration'; import { BackupHistory } from '../../components/admin/BackupHistory'; @@ -33,6 +33,7 @@ export const BackupManagement: React.FC = () => { const [activeTab, setActiveTab] = useState('dashboard'); const queryClient = useQueryClient(); const { t } = useTranslation(); + const { formatDateTime: fmtDateTime } = useLocalizedDate(); const tabs = [ { id: 'dashboard' as const, label: t('backup.tabs.dashboard'), icon: HardDrive }, @@ -122,7 +123,7 @@ export const BackupManagement: React.FC = () => { <> - {t('backup.status.lastBackup')}: {format(new Date(backupStatus.lastBackup.created_at), 'PPp')} + {t('backup.status.lastBackup')}: {fmtDateTime(backupStatus.lastBackup.created_at)} ) : ( diff --git a/frontend/src/pages/admin/CMSPage.tsx b/frontend/src/pages/admin/CMSPage.tsx index 28359e31..b51116eb 100644 --- a/frontend/src/pages/admin/CMSPage.tsx +++ b/frontend/src/pages/admin/CMSPage.tsx @@ -16,7 +16,7 @@ import { useLocalizedDate } from '../../hooks/useLocalizedDate'; export const CMSPage: React.FC = () => { const { t } = useTranslation(); - const { formatDateTime: fmtDateTime } = useLocalizedDate(); + const { formatDateTime: fmtDateTime, formatTime: fmtTime } = useLocalizedDate(); const queryClient = useQueryClient(); const [selectedPage, setSelectedPage] = useState('impressum'); const [editingLang, setEditingLang] = useState<'en' | 'de'>('en'); @@ -592,7 +592,7 @@ export const CMSPage: React.FC = () => { {!hasUnsavedChanges && lastSaved && (
- Saved {new Date(lastSaved).toLocaleTimeString()} + Saved {fmtTime(new Date(lastSaved))}
)}
diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index 7a412233..483327a6 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -244,7 +244,7 @@ export const EventDetailsPage: React.FC = () => { const navigate = useNavigate(); const queryClient = useQueryClient(); const { t } = useTranslation(); - const { format } = useLocalizedDate(); + const { format, formatDateTime: fmtDateTime } = useLocalizedDate(); const { flags } = useFeatureFlags(); // Validate ID parameter @@ -2330,7 +2330,7 @@ export const EventDetailsPage: React.FC = () => {

{t('events.archivedOn')}

- {event.archived_at && format(safeParseDate(event.archived_at)!, 'PPp')} + {event.archived_at && fmtDateTime(safeParseDate(event.archived_at)!)}

diff --git a/frontend/src/pages/admin/EventFeedbackPage.tsx b/frontend/src/pages/admin/EventFeedbackPage.tsx index 7d8ef126..147d6806 100644 --- a/frontend/src/pages/admin/EventFeedbackPage.tsx +++ b/frontend/src/pages/admin/EventFeedbackPage.tsx @@ -25,13 +25,15 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { eventsService } from '../../services/events.service'; import { feedbackService } from '../../services/feedback.service'; import type { PhotoFeedback, FeedbackAnalytics, FeedbackResponse } from '../../services/feedback.service'; +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; export const EventFeedbackPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { t } = useTranslation(); - + const { formatDateTime: fmtDateTime } = useLocalizedDate(); + const [activeTab, setActiveTab] = useState<'settings' | 'feedback' | 'analytics' | 'moderation'>('settings'); const [feedbackFilter, setFeedbackFilter] = useState({ type: '', @@ -297,7 +299,7 @@ export const EventFeedbackPage: React.FC = () => { const d = typeof item.created_at === 'string' ? parseISO(item.created_at) : new Date(item.created_at); - return isNaN(d.getTime()) ? t('common.unknownDate', 'Unknown date') : format(d, 'PPpp'); + return isNaN(d.getTime()) ? t('common.unknownDate', 'Unknown date') : fmtDateTime(d); })()}

diff --git a/frontend/src/pages/public/QuoteResponsePage.tsx b/frontend/src/pages/public/QuoteResponsePage.tsx index 4d651e18..d270d578 100644 --- a/frontend/src/pages/public/QuoteResponsePage.tsx +++ b/frontend/src/pages/public/QuoteResponsePage.tsx @@ -31,7 +31,7 @@ import { formatShortDate } from '../../utils/dateShort'; export const QuoteResponsePage: React.FC = () => { const { t, i18n } = useTranslation(); - const { formatDateTime: fmtDateTime } = useLocalizedDate(); + const { formatDateTime: fmtDateTime, formatTime: fmtTime } = useLocalizedDate(); const { token } = useParams<{ token: string }>(); const [searchParams, setSearchParams] = useSearchParams(); const [error, setError] = useState(null); @@ -309,7 +309,7 @@ export const QuoteResponsePage: React.FC = () => { ? Math.max(0, Math.ceil((lockAt.getTime() - Date.now()) / 60000)) : 0; return { - at: lockAt ? lockAt.toLocaleTimeString() : '', + at: lockAt ? fmtTime(lockAt) : '', minutes, }; })())