diff --git a/frontend/src/components/PrintModal/PlateSelector.tsx b/frontend/src/components/PrintModal/PlateSelector.tsx index a586d27be..1cf8dc4dd 100644 --- a/frontend/src/components/PrintModal/PlateSelector.tsx +++ b/frontend/src/components/PrintModal/PlateSelector.tsx @@ -1,6 +1,6 @@ import { Layers, Check, AlertTriangle } from 'lucide-react'; -import { formatTime } from '../../utils/amsHelpers'; import type { PlateSelectorProps } from './types'; +import { formatDuration } from '../../utils/date'; /** * Plate selection grid for multi-plate 3MF files. @@ -61,7 +61,7 @@ export function PlateSelector({ ? plate.objects.slice(0, 3).join(', ') + (plate.objects.length > 3 ? '...' : '') : `${plate.filaments.length} filament${plate.filaments.length !== 1 ? 's' : ''}`} - {plate.print_time_seconds != null ? ` • ${formatTime(plate.print_time_seconds)}` : ''} + {plate.print_time_seconds != null ? ` • ${formatDuration(plate.print_time_seconds)}` : ''}
{selectedPlate === plate.index && ( diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 37ab9ca3e..65eb0bcba 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -86,6 +86,7 @@ export default { unknown: 'Unbekannt', unknownError: 'Unbekannter Fehler', today: 'Heute', + tomorrow: 'Morgen', asap: 'Sofort', overdue: 'Überfällig', now: 'Jetzt', @@ -1875,7 +1876,6 @@ export default { cameraStream: 'Kamera-Stream', progress: 'Fortschritt', eta: 'ETA', - tomorrow: 'Morgen', printerIdle: 'Drucker ist inaktiv', printerOffline: 'Drucker offline', status: { diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 258ef82d0..4e63809c4 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -86,6 +86,7 @@ export default { unknown: 'Unknown', unknownError: 'Unknown error', today: 'Today', + tomorrow: 'Tomorrow', asap: 'ASAP', overdue: 'Overdue', now: 'Now', @@ -1875,7 +1876,6 @@ export default { cameraStream: 'Camera stream', progress: 'Progress', eta: 'ETA', - tomorrow: 'Tomorrow', printerIdle: 'Printer is idle', printerOffline: 'Printer offline', status: { diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index 66930631b..797f3cb52 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -86,6 +86,7 @@ export default { unknown: 'Inconnu', unknownError: 'Erreur inconnue', today: 'Aujourd\'hui', + tomorrow: 'Demain', asap: 'Dès que possible', overdue: 'En retard', now: 'Maintenant', @@ -1871,7 +1872,6 @@ export default { cameraStream: 'Flux caméra', progress: 'Progression', eta: 'Fin estimée', - tomorrow: 'Demain', printerIdle: 'Imprimante inactive', printerOffline: 'Imprimante hors ligne', status: { diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts index 2c85d2044..60b339330 100644 --- a/frontend/src/i18n/locales/it.ts +++ b/frontend/src/i18n/locales/it.ts @@ -83,6 +83,7 @@ export default { unknown: 'Sconosciuto', unknownError: 'Errore sconosciuto', today: 'Oggi', + tomorrow: 'Domani', asap: 'ASAP', overdue: 'Scaduto', now: 'Ora', @@ -1688,7 +1689,6 @@ export default { cameraStream: 'Stream camera', progress: 'Avanzamento', eta: 'ETA', - tomorrow: 'Domani', printerIdle: 'Stampante inattiva', printerOffline: 'Stampante offline', status: { diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index 58baebcf7..ddd4be404 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -74,6 +74,7 @@ export default { unknown: '不明', unknownError: '不明なエラー', today: '今日', + tomorrow: '明日', asap: '即時', now: '今すぐ', collapse: '折りたたむ', @@ -1856,7 +1857,6 @@ export default { }, title: 'ストリームオーバーレイ', progress: '進捗', - tomorrow: '明日', printerIdle: 'プリンター待機中', printerOffline: 'プリンターオフライン', }, diff --git a/frontend/src/pages/ArchivesPage.tsx b/frontend/src/pages/ArchivesPage.tsx index de00a1e38..012f4526d 100644 --- a/frontend/src/pages/ArchivesPage.tsx +++ b/frontend/src/pages/ArchivesPage.tsx @@ -50,7 +50,7 @@ import { } from 'lucide-react'; import { api } from '../api/client'; import { openInSlicer, type SlicerType } from '../utils/slicer'; -import { formatDateTime, formatDateOnly, parseUTCDate, type TimeFormat } from '../utils/date'; +import { formatDateTime, formatDateOnly, parseUTCDate, type TimeFormat, formatDuration } from '../utils/date'; import { useIsMobile } from '../hooks/useIsMobile'; import type { Archive, ProjectListItem } from '../api/client'; import { Card, CardContent } from '../components/Card'; @@ -82,13 +82,6 @@ function formatFileSize(bytes: number): string { return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } -function formatDuration(seconds: number): string { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - if (hours > 0) return `${hours}h ${minutes}m`; - return `${minutes}m`; -} - /** * Check if an archive filename represents a sliced/printable file. * Matches: .gcode, .gcode.3mf, .gcode.anything diff --git a/frontend/src/pages/FileManagerPage.tsx b/frontend/src/pages/FileManagerPage.tsx index 2a6bb76eb..bfa833daa 100644 --- a/frontend/src/pages/FileManagerPage.tsx +++ b/frontend/src/pages/FileManagerPage.tsx @@ -57,6 +57,7 @@ import { ModelViewerModal } from '../components/ModelViewerModal'; import { useToast } from '../contexts/ToastContext'; import { useIsMobile } from '../hooks/useIsMobile'; import { useAuth } from '../contexts/AuthContext'; +import { formatDuration } from '../utils/date'; type SortField = 'name' | 'date' | 'size' | 'type' | 'prints'; type SortDirection = 'asc' | 'desc'; @@ -70,15 +71,6 @@ function formatFileSize(bytes: number): string { return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; } -// Utility to format duration -function formatDuration(seconds: number | null): string { - if (!seconds) return '-'; - const hours = Math.floor(seconds / 3600); - const mins = Math.floor((seconds % 3600) / 60); - if (hours > 0) return `${hours}h ${mins}m`; - return `${mins}m`; -} - // New Folder Modal interface NewFolderModalProps { parentId: number | null; diff --git a/frontend/src/pages/PrintersPage.tsx b/frontend/src/pages/PrintersPage.tsx index d775db965..437b55b0a 100644 --- a/frontend/src/pages/PrintersPage.tsx +++ b/frontend/src/pages/PrintersPage.tsx @@ -46,7 +46,7 @@ import { import { useNavigate } from 'react-router-dom'; import { api, discoveryApi, firmwareApi } from '../api/client'; -import { formatDateOnly } from '../utils/date'; +import { formatDateOnly, formatETA, formatDuration } from '../utils/date'; import type { Printer, PrinterCreate, AMSUnit, DiscoveredPrinter, FirmwareUpdateInfo, FirmwareUploadStatus, LinkedSpoolInfo, SpoolAssignment } from '../api/client'; import { Card, CardContent } from '../components/Card'; import { Button } from '../components/Button'; @@ -1087,42 +1087,6 @@ function getSpoolmanFillLevel( )); } -function formatTime(seconds: number): string { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; -} - -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(); - today.setHours(0, 0, 0, 0); - const etaDay = new Date(eta); - etaDay.setHours(0, 0, 0, 0); - - // 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)); - if (dayDiff === 0) { - return timeStr; - } else if (dayDiff === 1) { - return `Tomorrow ${timeStr}`; - } else { - return eta.toLocaleDateString([], { weekday: 'short' }) + ' ' + timeStr; - } -} - function getPrinterImage(model: string | null | undefined): string { if (!model) return '/img/printers/default.png'; @@ -1348,7 +1312,7 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) { /> {Math.round(nextFinish.progress)}% - ({formatTime(nextFinish.remainingMin * 60)}) + ({formatDuration(nextFinish.remainingMin * 60)}) > @@ -2455,10 +2419,10 @@ function PrinterCard({ <>{t('queue.printingInProgress')}