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:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user