fix(backup-ui): respect general_date_format + general_time_format

The four backup admin panes (BackupHistory, BackupDashboard,
BackupCoverageCard, BackupIntegrityCard) used raw date-fns
`format()` with hard-coded tokens like 'p' (12-hour AM/PM), 'PP',
'PPP', 'PPp', and 'yyyy-MM-dd HH:mm:ss' — ignoring the admin's
configured `general_date_format` and `general_time_format`
settings.

Net effect on a 24h-configured install: backup History row showed
"11:25 PM" instead of "23:25", and the Coverage tab's "Last dump"
+ "Coverage generated" timestamps were stuck on
yyyy-MM-dd HH:mm:ss regardless of the admin's date-format choice.

All four panes now route through `useLocalizedDate()` which honors
both settings + the active i18n locale (per the existing
[[feedback_respect_general_format_settings]] pattern).

Tokens replaced:
  format(date, 'p')              → formatTime(date)
  format(date, 'PP')             → format(date)
  format(date, 'PPP')            → format(date)
  format(date, 'PPp')            → formatDateTime(date)
  format(date, 'yyyy-MM-dd HH:mm:ss') → formatDateTime(date)
  format(date, 'yyyy-MM-dd HH:mm')    → formatDateTime(date)

No backend changes — settings already shipped via /admin/settings;
this just makes the consumers actually read them.
This commit is contained in:
Luca
2026-05-31 23:33:50 +02:00
parent 83fdb47fbf
commit 09f6a1af6a
5 changed files with 57 additions and 20 deletions
@@ -14,7 +14,8 @@ import {
Loader2,
} from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
import { format } from 'date-fns';
// Locale-aware formatters per [[feedback_respect_general_format_settings]].
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
import { Card, Button } from '../common';
import {
@@ -41,6 +42,7 @@ import {
*/
export const BackupCoverageCard: React.FC = () => {
const { t } = useTranslation();
const { formatDateTime } = useLocalizedDate();
const { data, isLoading, isError, error, refetch, isFetching } = useQuery({
queryKey: ['backup-coverage'],
queryFn: () => adminService.getBackupCoverage(),
@@ -75,7 +77,7 @@ export const BackupCoverageCard: React.FC = () => {
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-4">
{t('backup.coverage.generatedAt', 'Coverage generated: {{when}}', {
when: format(new Date(data.generatedAt), 'yyyy-MM-dd HH:mm:ss'),
when: formatDateTime(new Date(data.generatedAt)),
})}
</p>
</>
@@ -164,6 +166,7 @@ const DatabaseStatusCard: React.FC<{
database: BackupCoverageReport['database'];
}> = ({ database }) => {
const { t } = useTranslation();
const { formatDateTime } = useLocalizedDate();
const isInline = database.mode === 'inline';
const tone: Tone = database.ok ? 'green' : 'red';
const dumpAge = database.lastDumpAgeMs !== null
@@ -194,7 +197,7 @@ const DatabaseStatusCard: React.FC<{
<>
<Row
label={t('backup.coverage.database.lastDump', 'Last dump')}
value={`${format(new Date(database.lastDumpAt), 'yyyy-MM-dd HH:mm')}${
value={`${formatDateTime(new Date(database.lastDumpAt))}${
dumpAge ? ` (${dumpAge})` : ''
}`}
/>
@@ -16,7 +16,10 @@ import {
AlertTriangle,
Info
} from 'lucide-react';
import { format, formatDistanceToNow } from 'date-fns';
// Per [[feedback_respect_general_format_settings]] — route every
// displayed date/time through useLocalizedDate so general_date_format
// and general_time_format settings apply uniformly.
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
import { Card, Button } from '../common';
export type HealthStatus = 'excellent' | 'good' | 'warning' | 'critical';
@@ -109,6 +112,7 @@ const healthColors: Record<HealthStatus, string> = {
export const BackupDashboard: React.FC<BackupDashboardProps> = ({ status, config, onRunBackup, isBackupRunning }) => {
const { t } = useTranslation();
const { format, formatTime, formatDateTime, formatDistanceToNow } = useLocalizedDate();
const lastBackup = status?.lastBackup; // any status
const lastSuccessfulBackup = status?.lastSuccessfulBackup; // status='completed' only
const zombieRuns = status?.zombieRuns ?? [];
@@ -285,7 +289,7 @@ export const BackupDashboard: React.FC<BackupDashboardProps> = ({ status, config
label={t('backup.dashboard.stats.totalBackups')}
value={status?.totalBackups || 0}
color="blue"
subtext={lastBackup ? `${t('backup.dashboard.stats.last')}: ${format(new Date(lastBackup.created_at), 'PP')}` : t('backup.dashboard.stats.noBackupsYet')}
subtext={lastBackup ? `${t('backup.dashboard.stats.last')}: ${format(new Date(lastBackup.created_at))}` : t('backup.dashboard.stats.noBackupsYet')}
/>
<StatCard
@@ -301,7 +305,7 @@ export const BackupDashboard: React.FC<BackupDashboardProps> = ({ status, config
label={t('backup.dashboard.stats.lastDuration')}
value={lastBackup ? `${Math.round(lastBackup.duration_seconds / 60)}m` : 'N/A'}
color="purple"
subtext={lastBackup ? format(new Date(lastBackup.created_at), 'p') : ''}
subtext={lastBackup ? formatTime(new Date(lastBackup.created_at)) : ''}
/>
<StatCard
@@ -333,7 +337,7 @@ export const BackupDashboard: React.FC<BackupDashboardProps> = ({ status, config
{t('backup.dashboard.backupType', { type: backup.backup_type })}
</p>
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{format(new Date(backup.created_at), 'PPp')}
{formatDateTime(new Date(backup.created_at))}
</p>
</div>
</div>
@@ -20,11 +20,17 @@ import {
RefreshCw,
Loader2
} from 'lucide-react';
import { format, formatDistanceToNow } from 'date-fns';
import { toast } from 'react-toastify';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Button, Card, Input, Loading } from '../common';
import { api } from '../../config/api';
// Per [[feedback_respect_general_format_settings]]: route every displayed
// date/time through useLocalizedDate so the admin's general_date_format +
// general_time_format settings apply uniformly. Previously the backup
// History pane used raw date-fns format() with hard-coded 'p' (12-hour
// AM/PM) and 'PPP' (US-locale long date), which ignored the settings
// Ralf 2026-05-31 flagged "11:25 PM" on a 24h-configured install.
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
const statusIcons = {
completed: { icon: CheckCircle, color: 'text-green-500' },
@@ -48,6 +54,12 @@ export const BackupHistory = () => {
const [filterStatus, setFilterStatus] = useState('all');
const [currentPage, setCurrentPage] = useState(1);
const queryClient = useQueryClient();
// Locale-aware formatters that respect admin's general_date_format +
// general_time_format settings. See useLocalizedDate.ts for the full
// contract; formatTime gives "HH:mm" (24h) or "h:mm a" (12h) based on
// the setting, format(date) honors general_date_format, and
// formatDistanceToNow returns "2 minutes ago" in the admin's i18n locale.
const { format, formatTime, formatDistanceToNow } = useLocalizedDate();
// Fetch backup history
const { data, isLoading, refetch } = useQuery({
@@ -90,7 +102,7 @@ export const BackupHistory = () => {
};
const handleDelete = (backup) => {
if (window.confirm(`Are you sure you want to delete this backup from ${format(new Date(backup.created_at), 'PPP')}?`)) {
if (window.confirm(`Are you sure you want to delete this backup from ${format(new Date(backup.created_at))}?`)) {
deleteMutation.mutate(backup.id);
}
};
@@ -200,10 +212,10 @@ export const BackupHistory = () => {
<td className="px-6 py-4 whitespace-nowrap">
<div>
<p className="text-sm font-medium text-neutral-900 dark:text-neutral-100">
{format(new Date(backup.created_at), 'PPP')}
{format(new Date(backup.created_at))}
</p>
<p className="text-xs text-neutral-500 dark:text-neutral-400">
{format(new Date(backup.created_at), 'p')} {formatDistanceToNow(new Date(backup.created_at), { addSuffix: true })}
{formatTime(new Date(backup.created_at))} {formatDistanceToNow(new Date(backup.created_at), { addSuffix: true })}
</p>
</div>
</td>
@@ -270,12 +282,12 @@ export const BackupHistory = () => {
</div>
<div className="flex justify-between">
<span className="text-neutral-500 dark:text-neutral-400">{t('backup.history.details.started')}:</span>
<span className="text-neutral-900 dark:text-neutral-100">{format(new Date(backup.created_at), 'p')}</span>
<span className="text-neutral-900 dark:text-neutral-100">{formatTime(new Date(backup.created_at))}</span>
</div>
{backup.completed_at && (
<div className="flex justify-between">
<span className="text-neutral-500 dark:text-neutral-400">{t('backup.history.details.completed')}:</span>
<span className="text-neutral-900 dark:text-neutral-100">{format(new Date(backup.completed_at), 'p')}</span>
<span className="text-neutral-900 dark:text-neutral-100">{formatTime(new Date(backup.completed_at))}</span>
</div>
)}
</div>
@@ -10,7 +10,8 @@ import {
Loader2,
} from 'lucide-react';
import { useMutation } from '@tanstack/react-query';
import { format } from 'date-fns';
// Locale-aware formatters per [[feedback_respect_general_format_settings]].
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
import { Card, Button } from '../common';
import { adminService, BackupIntegrityReport } from '../../services/admin.service';
@@ -33,6 +34,7 @@ import { adminService, BackupIntegrityReport } from '../../services/admin.servic
*/
export const BackupIntegrityCard: React.FC = () => {
const { t } = useTranslation();
const { formatDateTime } = useLocalizedDate();
const [report, setReport] = useState<BackupIntegrityReport | null>(null);
const [expanded, setExpanded] = useState<'missing' | 'hashMismatches' | null>(null);
@@ -147,7 +149,7 @@ export const BackupIntegrityCard: React.FC = () => {
<p className="text-xs text-neutral-500 dark:text-neutral-400 mb-3">
{t('backup.integrity.scannedAt', 'Last checked: {{when}}', {
when: format(new Date(report.scannedAt), 'yyyy-MM-dd HH:mm:ss'),
when: formatDateTime(new Date(report.scannedAt)),
})}
</p>