Explorar o código

fix(frontend): #1602 render print-run / spool-usage / camera-token / SpoolBuddy timestamps in local time

  Four frontend formatDate / formatDateTime helpers called new Date(iso)
  directly on backend timestamps that have no timezone indicator. Per
  ECMAScript, a bare "2026-06-02T07:50:00" is parsed as local time, so
  a UTC-stored value got displayed as if its numeric components were
  already local — visually identical to UTC. Same shape as the #504
  fix from Feb 2026, which patched 13 sites but missed these four:
  PrintLogTable and SpoolUsageHistory hadn't been written yet;
  CameraTokensPage and SpoolBuddySettingsPage existed but were
  overlooked.

  Reporter #2 (@IndividualGhost1905) confirmed with a UTC+3 host: print
  log shows UTC clock value, system date shows local. PrintLogTable is
  the "logs/completion time" they called out; the other three are the
  same pattern in nearby surfaces.

  Replaced the bare new Date(iso) calls with parseUTCDate(iso) from
  utils/date.ts — the same helper every other date formatter in the
  codebase already uses. It appends "Z" to naive ISO strings and
  parses TZ-tagged strings as-is. CameraTokensPage.isExpired got the
  same fix because comparing a misparsed Date against Date.now() would
  produce false "not expired" / "expired" results around the TZ-offset
  boundary.

  Reporter #1's printer-card ETA complaint (10:50 + 57m showing 09:48)
  is NOT addressed by this fix. That ETA comes from
  formatETA(remainingMinutes) which is purely client-side (new Date()
  plus minutes from the WebSocket payload, then toLocaleTimeString)
  — for it to render UTC the browser timezone itself would need to
  be UTC, which is a browser / OS config issue.

  Audit confirmed no other regressions: grepped every new Date( call
  in frontend/src/. Remaining sites either pass an epoch-ms number,
  use the result only for .getTime() arithmetic where the offset
  cancels, already wrap in parseUTCDate fallback, or consume a
  backend timestamp that includes "+00:00" (FailureDetectionSettings
  reads obico_detection.py's tz-aware isoformat).
maziggy hai 20 horas
pai
achega
e38267065c

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
CHANGELOG.md


+ 3 - 2
frontend/src/components/PrintLogTable.tsx

@@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
 import { useTranslation } from 'react-i18next';
 import { Loader2 } from 'lucide-react';
 import { api } from '../api/client';
+import { parseUTCDate } from '../utils/date';
 
 interface PrintLogTableProps {
   archiveId: number;
@@ -17,8 +18,8 @@ function formatDuration(seconds: number | null): string {
 
 function formatDate(isoString: string | null): string {
   if (!isoString) return '—';
-  const d = new Date(isoString);
-  return d.toLocaleString();
+  const d = parseUTCDate(isoString);
+  return d ? d.toLocaleString() : '—';
 }
 
 export function PrintLogTable({ archiveId }: PrintLogTableProps) {

+ 3 - 1
frontend/src/components/SpoolUsageHistory.tsx

@@ -5,8 +5,10 @@ import { api } from '../api/client';
 import type { SpoolUsageRecord } from '../api/client';
 import { Button } from './Button';
 import { useToast } from '../contexts/ToastContext';
+import { parseUTCDate } from '../utils/date';
 function formatDate(dateStr: string): string {
-  const date = new Date(dateStr);
+  const date = parseUTCDate(dateStr);
+  if (!date) return '';
   return date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: '2-digit' }) +
     ' ' + date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
 }

+ 5 - 3
frontend/src/pages/CameraTokensPage.tsx

@@ -21,18 +21,20 @@ import { Copy, Plus, Trash2, AlertTriangle } from 'lucide-react';
 import { api, type LongLivedCameraToken } from '../api/client';
 import { useToast } from '../contexts/ToastContext';
 import { useAuth } from '../contexts/AuthContext';
+import { parseUTCDate } from '../utils/date';
 
 const DEFAULT_LIFETIME_DAYS = 90;
 const MAX_LIFETIME_DAYS = 365;
 
 function formatDate(iso: string | null): string {
   if (!iso) return '—';
-  const d = new Date(iso);
-  return d.toLocaleString();
+  const d = parseUTCDate(iso);
+  return d ? d.toLocaleString() : '—';
 }
 
 function isExpired(iso: string): boolean {
-  return new Date(iso).getTime() < Date.now();
+  const d = parseUTCDate(iso);
+  return d ? d.getTime() < Date.now() : false;
 }
 
 interface CreateTokenFormProps {

+ 5 - 7
frontend/src/pages/spoolbuddy/SpoolBuddySettingsPage.tsx

@@ -6,6 +6,7 @@ import type { SpoolBuddyOutletContext } from '../../components/spoolbuddy/SpoolB
 import { spoolbuddyApi, type SpoolBuddyDevice } from '../../api/client';
 import { DiagnosticModal } from '../../components/spoolbuddy/DiagnosticModal';
 import { FileText, Wand2, Zap } from 'lucide-react';
+import { parseUTCDate } from '../../utils/date';
 
 
 function formatUptime(seconds: number): string {
@@ -18,13 +19,10 @@ function formatUptime(seconds: number): string {
 
 function formatDateTime(iso: string | null): string {
   if (!iso) return '-';
-  try {
-    const d = new Date(iso);
-    return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' +
-      d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
-  } catch {
-    return '-';
-  }
+  const d = parseUTCDate(iso);
+  if (!d) return '-';
+  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' +
+    d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
 }
 
 const BLANK_OPTIONS = [

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
static/assets/index-DLXkorBx.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BuuCTvNJ.js"></script>
+    <script type="module" crossorigin src="/assets/index-DLXkorBx.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-C3FyyVE7.css">
   </head>
   <body>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio