From 087f7b9fa5eb8f1704081f5c6de7a8600b02fd54 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:56:34 -0500 Subject: [PATCH 1/2] fix: replace toISOString date conversion in OCR parser with local time formatting (refs #237) The parseServiceDate function used toISOString().split('T')[0] which converts to UTC, shifting dates by one day depending on timezone. Standard parsing now uses getFullYear/getMonth/getDate (local time). MM/DD/YYYY parsing now formats directly from regex groups without round-tripping through a Date object. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../maintenance/hooks/useMaintenanceReceiptOcr.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/maintenance/hooks/useMaintenanceReceiptOcr.ts b/frontend/src/features/maintenance/hooks/useMaintenanceReceiptOcr.ts index 03a6a10..54790e9 100644 --- a/frontend/src/features/maintenance/hooks/useMaintenanceReceiptOcr.ts +++ b/frontend/src/features/maintenance/hooks/useMaintenanceReceiptOcr.ts @@ -107,17 +107,17 @@ function parseServiceDate(value: string | number | null): string | undefined { // Try standard parsing const date = new Date(dateStr); if (!isNaN(date.getTime())) { - return date.toISOString().split('T')[0]; + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; } // Try MM/DD/YYYY format const mdyMatch = dateStr.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/); if (mdyMatch) { const [, month, day, year] = mdyMatch; - const parsed = new Date(parseInt(year), parseInt(month) - 1, parseInt(day)); - if (!isNaN(parsed.getTime())) { - return parsed.toISOString().split('T')[0]; - } + return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`; } return undefined; From 1e056f0b019d964922037617336f79edbc6aa3f3 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:18:30 -0500 Subject: [PATCH 2/2] fix: replace new Date() with dayjs for DATE column display and sorting (refs #237) new Date("YYYY-MM-DD") parses as UTC midnight per ES2015. toLocaleDateString() then displays in local time, shifting the date back one day for users west of UTC. This caused the list view and edit dialog to show different dates. Fixed in: MaintenanceRecordsList (display + sort + delete confirm), VehicleDetailPage (display + sort), VehicleDetailMobile (display + sort), MaintenanceRecordForm (receipt title), OwnershipCostsList (formatDate). Sorting now uses string comparison (YYYY-MM-DD is lexicographically sortable). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../maintenance/components/MaintenanceRecordForm.tsx | 2 +- .../maintenance/components/MaintenanceRecordsList.tsx | 7 ++++--- .../ownership-costs/components/OwnershipCostsList.tsx | 3 ++- .../src/features/vehicles/mobile/VehicleDetailMobile.tsx | 5 +++-- frontend/src/features/vehicles/pages/VehicleDetailPage.tsx | 5 +++-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/maintenance/components/MaintenanceRecordForm.tsx b/frontend/src/features/maintenance/components/MaintenanceRecordForm.tsx index 637ce29..6a598ee 100644 --- a/frontend/src/features/maintenance/components/MaintenanceRecordForm.tsx +++ b/frontend/src/features/maintenance/components/MaintenanceRecordForm.tsx @@ -190,7 +190,7 @@ export const MaintenanceRecordForm: React.FC = ({ ve const doc = await documentsApi.create({ vehicleId: data.vehicle_id, documentType: 'manual', - title: `Maintenance Receipt - ${new Date(data.date).toLocaleDateString()}`, + title: `Maintenance Receipt - ${dayjs(data.date).format('M/D/YYYY')}`, }); await documentsApi.upload(doc.id, capturedReceiptFile); receiptDocumentId = doc.id; diff --git a/frontend/src/features/maintenance/components/MaintenanceRecordsList.tsx b/frontend/src/features/maintenance/components/MaintenanceRecordsList.tsx index 0f38234..0bc0180 100644 --- a/frontend/src/features/maintenance/components/MaintenanceRecordsList.tsx +++ b/frontend/src/features/maintenance/components/MaintenanceRecordsList.tsx @@ -4,6 +4,7 @@ */ import React, { useState } from 'react'; +import dayjs from 'dayjs'; import { Card, CardContent, @@ -74,14 +75,14 @@ export const MaintenanceRecordsList: React.FC = ({ // Sort records by date DESC (newest first) const sortedRecords = [...records].sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() + (a, b) => b.date.localeCompare(a.date) ); return ( <> {sortedRecords.map((record) => { - const dateText = new Date(record.date).toLocaleDateString(); + const dateText = dayjs(record.date).format('M/D/YYYY'); const categoryDisplay = getCategoryDisplayName(record.category); const subtypeCount = record.subtypeCount || record.subtypes?.length || 0; @@ -204,7 +205,7 @@ export const MaintenanceRecordsList: React.FC = ({ {recordToDelete && ( - {new Date(recordToDelete.date).toLocaleDateString()} -{' '} + {dayjs(recordToDelete.date).format('M/D/YYYY')} -{' '} {getCategoryDisplayName(recordToDelete.category)} )} diff --git a/frontend/src/features/ownership-costs/components/OwnershipCostsList.tsx b/frontend/src/features/ownership-costs/components/OwnershipCostsList.tsx index f22b40c..6139913 100644 --- a/frontend/src/features/ownership-costs/components/OwnershipCostsList.tsx +++ b/frontend/src/features/ownership-costs/components/OwnershipCostsList.tsx @@ -3,6 +3,7 @@ */ import React from 'react'; +import dayjs from 'dayjs'; import type { OwnershipCost, OwnershipCostType } from '../types/ownership-costs.types'; import { useOwnershipCostsList, useDeleteOwnershipCost } from '../hooks/useOwnershipCosts'; @@ -42,7 +43,7 @@ export const OwnershipCostsList: React.FC = ({ vehicleI }; const formatDate = (dateString: string): string => { - return new Date(dateString).toLocaleDateString(); + return dayjs(dateString).format('M/D/YYYY'); }; if (isLoading) { diff --git a/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx b/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx index 8dbc6c3..7f9d16f 100644 --- a/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx +++ b/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx @@ -3,6 +3,7 @@ */ import React, { useMemo, useState } from 'react'; +import dayjs from 'dayjs'; import { Box, Typography, Button, Card, CardContent, Divider, FormControl, InputLabel, Select, MenuItem, List, ListItem, ListItemButton, Dialog, DialogTitle, DialogContent } from '@mui/material'; import { useQueryClient } from '@tanstack/react-query'; import { Vehicle } from '../types/vehicles.types'; @@ -123,7 +124,7 @@ export const VehicleDetailMobile: React.FC = ({ }); } } - return list.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + return list.sort((a, b) => b.date.localeCompare(a.date)); }, [fuelLogs]); const filteredRecords = useMemo(() => { @@ -286,7 +287,7 @@ export const VehicleDetailMobile: React.FC = ({ {/* Secondary line: Grade • Date • Type */} - {rec.secondary || `${new Date(rec.date).toLocaleDateString()} • ${rec.type}`} + {rec.secondary || `${dayjs(rec.date).format('M/D/YYYY')} • ${rec.type}`} diff --git a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx index a50f37d..179aef4 100644 --- a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx +++ b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx @@ -3,6 +3,7 @@ */ import React, { useMemo, useState, useEffect } from 'react'; +import dayjs from 'dayjs'; import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; import { Box, Typography, Button as MuiButton, Divider, FormControl, InputLabel, Select, MenuItem, Table, TableHead, TableRow, TableCell, TableBody, Dialog, DialogTitle, DialogContent, useMediaQuery, IconButton } from '@mui/material'; import { useQueryClient } from '@tanstack/react-query'; @@ -132,7 +133,7 @@ export const VehicleDetailPage: React.FC = () => { } } - return list.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + return list.sort((a, b) => b.date.localeCompare(a.date)); }, [fuelLogs, documents]); const filteredRecords = useMemo(() => { @@ -490,7 +491,7 @@ export const VehicleDetailPage: React.FC = () => { )} {!isFuelLoading && !isDocumentsLoading && filteredRecords.map((rec) => ( handleRowClick(rec.id, rec.type)}> - {new Date(rec.date).toLocaleDateString()} + {dayjs(rec.date).format('M/D/YYYY')} {rec.type} {rec.summary} {rec.amount || '—'}