Updated packages. Changed date picker package to Day.JS and applied it across whole app.

This commit is contained in:
Eric Gullickson
2025-12-18 16:07:30 -06:00
parent 843825a956
commit 0e85cf48c3
17 changed files with 197 additions and 135 deletions

View File

@@ -96,7 +96,9 @@
"mcp__playwright__browser_close", "mcp__playwright__browser_close",
"Bash(wc:*)", "Bash(wc:*)",
"mcp__brave-search__brave_web_search", "mcp__brave-search__brave_web_search",
"mcp__firecrawl__firecrawl_search" "mcp__firecrawl__firecrawl_search",
"Bash(ssh:*)",
"Bash(git checkout:*)"
], ],
"deny": [] "deny": []
} }

21
backend/eslint.config.js Normal file
View File

@@ -0,0 +1,21 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist', 'node_modules'] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.ts'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['warn', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
args: 'after-used'
}],
'@typescript-eslint/no-explicit-any': 'warn',
},
},
);

View File

@@ -13,26 +13,26 @@
"migrate:all": "ts-node src/_system/migrations/run-all.ts", "migrate:all": "ts-node src/_system/migrations/run-all.ts",
"migrate:feature": "ts-node src/_system/migrations/run-feature.ts", "migrate:feature": "ts-node src/_system/migrations/run-feature.ts",
"schema:generate": "ts-node src/_system/schema/generate.ts", "schema:generate": "ts-node src/_system/schema/generate.ts",
"lint": "eslint src --ext .ts" "lint": "eslint src"
}, },
"dependencies": { "dependencies": {
"pg": "^8.11.3", "pg": "^8.13.1",
"ioredis": "^5.3.2", "ioredis": "^5.4.2",
"minio": "^7.1.3", "minio": "^7.1.3",
"@fastify/multipart": "^8.1.0", "@fastify/multipart": "^9.0.1",
"axios": "^1.6.2", "axios": "^1.7.9",
"opossum": "^8.0.0", "opossum": "^8.0.0",
"winston": "^3.11.0", "winston": "^3.17.0",
"zod": "^3.22.4", "zod": "^3.24.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"fastify": "^4.24.3", "fastify": "^5.2.0",
"@fastify/cors": "^9.0.1", "@fastify/cors": "^10.0.1",
"@fastify/helmet": "^11.1.1", "@fastify/helmet": "^12.0.1",
"@fastify/jwt": "^8.0.0", "@fastify/jwt": "^9.0.1",
"@fastify/type-provider-typebox": "^4.0.0", "@fastify/type-provider-typebox": "^5.0.0",
"@sinclair/typebox": "^0.31.28", "@sinclair/typebox": "^0.34.0",
"fastify-plugin": "^4.5.1", "fastify-plugin": "^5.0.1",
"@fastify/autoload": "^5.8.0", "@fastify/autoload": "^6.0.1",
"get-jwks": "^9.0.0", "get-jwks": "^9.0.0",
"file-type": "^16.5.4" "file-type": "^16.5.4"
}, },
@@ -40,17 +40,17 @@
"@types/node": "^20.10.0", "@types/node": "^20.10.0",
"@types/pg": "^8.10.9", "@types/pg": "^8.10.9",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"typescript": "^5.6.3", "typescript": "^5.7.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"nodemon": "^3.0.1", "nodemon": "^3.1.9",
"jest": "^29.7.0", "jest": "^29.7.0",
"@types/jest": "^29.5.10", "@types/jest": "^29.5.10",
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"@types/supertest": "^2.0.16", "@types/supertest": "^2.0.16",
"@types/opossum": "^8.0.0", "@types/opossum": "^8.0.0",
"eslint": "^8.54.0", "eslint": "^9.17.0",
"@typescript-eslint/eslint-plugin": "^6.12.0", "@eslint/js": "^9.17.0",
"@typescript-eslint/parser": "^6.12.0" "typescript-eslint": "^8.18.1"
} }
} }

View File

@@ -2,12 +2,12 @@
* @ai-summary Fastify global error handling plugin * @ai-summary Fastify global error handling plugin
* @ai-context Handles uncaught errors with structured logging * @ai-context Handles uncaught errors with structured logging
*/ */
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync, FastifyError } from 'fastify';
import fp from 'fastify-plugin'; import fp from 'fastify-plugin';
import { logger } from '../logging/logger'; import { logger } from '../logging/logger';
const errorPlugin: FastifyPluginAsync = async (fastify) => { const errorPlugin: FastifyPluginAsync = async (fastify) => {
fastify.setErrorHandler((error, request, reply) => { fastify.setErrorHandler((error: FastifyError, request, reply) => {
logger.error('Unhandled error', { logger.error('Unhandled error', {
error: error.message, error: error.message,
stack: error.stack, stack: error.stack,

View File

@@ -43,7 +43,10 @@ def load_pairs(snapshot_path: Path) -> List[sqlite3.Row]:
if not snapshot_path.exists(): if not snapshot_path.exists():
raise FileNotFoundError(f"Snapshot not found: {snapshot_path}") raise FileNotFoundError(f"Snapshot not found: {snapshot_path}")
conn = sqlite3.connect(snapshot_path) # Open in immutable mode to prevent any write attempts on read-only filesystems
absolute_path = snapshot_path.resolve()
uri = f"file:{absolute_path}?immutable=1"
conn = sqlite3.connect(uri, uri=True)
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
try: try:
cursor = conn.execute( cursor = conn.execute(

View File

@@ -20,14 +20,16 @@ Your task is to create a plan that can be dispatched to a seprate set of AI agen
*** ROLE *** *** ROLE ***
You are a senior devops systems reliablity engineer specializing modern web applications. Expert in linux, docker compose and gitlab. You are a senior web deveoper specializing in nodejs, typescript along with CSS and HTML.
Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change.
*** ACTION *** *** ACTION ***
- You need to create a plan for the end user to implement to make this application deployable with GitLab runners. - You need to create a plan to upgrade the node packages to the latest versions that are compatible with each other. Some of the packages are very old and while we might not get to the very latest we need to get as close as possible for security reasons.
- Use context7 mcp to find the latest versions and compatibilities.
- Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change.
*** CONTEXT *** *** CONTEXT ***
- This is a docker compose app that is functioning in the local dev environment. It was developed with the plan to move to Kubernetes eventually but right now it's staying in docker compose. There is a secrets architecture that mirrors k8s that needs to be replicated in gitlab deployment into the docker compose environment. The gitlab version is 18.6.2 and is using the shell runtime on the gitlab runners. - This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. The UX will be different between mobile and desktop due to resolution differences but that's it.
*** EXECUTE *** *** EXECUTE ***
Create a plan the user can execute to make this app deployable with gitlab. Use brave, context7 and firecrawl if needed. Make no assumptions if your data does not have version 18.6 of gitlab. Save the plan to @docs/CICD-DEPLOY.md. Ultrathink. Create a plan that can be tasked to sub agents to explore and find dependancies between all the packages. Make sure nothing breaks. Use context7, brave search and firecrawl MCP's extensively. Make no assumptions.

View File

@@ -2,25 +2,23 @@ import js from '@eslint/js';
import globals from 'globals'; import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks'; import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh'; import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from '@typescript-eslint/eslint-plugin'; import tseslint from 'typescript-eslint';
import parser from '@typescript-eslint/parser';
export default [ export default tseslint.config(
{ ignores: ['dist'] }, { ignores: ['dist', 'node_modules'] },
js.configs.recommended,
...tseslint.configs.recommended,
{ {
files: ['**/*.{ts,tsx}'], files: ['**/*.{ts,tsx}'],
languageOptions: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
parser,
}, },
plugins: { plugins: {
'@typescript-eslint': tseslint,
'react-hooks': reactHooks, 'react-hooks': reactHooks,
'react-refresh': reactRefresh, 'react-refresh': reactRefresh,
}, },
rules: { rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules, ...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [ 'react-refresh/only-export-components': [
'warn', 'warn',
@@ -36,4 +34,4 @@ export default [
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'warn',
}, },
}, },
]; );

View File

@@ -12,47 +12,48 @@
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"react": "^18.3.1", "react": "^19.0.0",
"react-dom": "^18.3.1", "react-dom": "^19.0.0",
"react-router-dom": "^6.8.0", "react-router-dom": "^6.28.1",
"@auth0/auth0-react": "^2.2.3", "@auth0/auth0-react": "^2.2.3",
"axios": "^1.6.2", "axios": "^1.7.9",
"zustand": "^4.4.6", "zustand": "^4.5.6",
"@tanstack/react-query": "^5.8.4", "@tanstack/react-query": "^5.84.1",
"react-hook-form": "^7.48.2", "react-hook-form": "^7.54.2",
"@hookform/resolvers": "^3.3.2", "@hookform/resolvers": "^3.9.1",
"zod": "^3.22.4", "zod": "^3.24.1",
"date-fns": "^2.30.0", "dayjs": "^1.11.13",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-slick": "^0.30.2", "react-slick": "^0.30.2",
"slick-carousel": "^1.8.1", "slick-carousel": "^1.8.1",
"framer-motion": "^11.0.0", "framer-motion": "^11.15.0",
"@mui/material": "^5.15.0", "@mui/material": "^6.3.0",
"@mui/x-date-pickers": "^6.19.0", "@mui/x-date-pickers": "^7.23.0",
"@mui/x-data-grid": "^6.19.1", "@mui/x-data-grid": "^7.23.0",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.14.0",
"@emotion/cache": "^11.11.0", "@emotion/cache": "^11.14.0",
"@mui/icons-material": "^5.15.0" "@mui/icons-material": "^6.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.0", "@types/react": "^19.0.2",
"@types/react-dom": "^18.2.0", "@types/react-dom": "^19.0.2",
"@types/react-slick": "^0.23.13", "@types/react-slick": "^0.23.13",
"@typescript-eslint/eslint-plugin": "^6.12.0", "typescript-eslint": "^8.18.1",
"@typescript-eslint/parser": "^6.12.0", "@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-react": "^4.2.0", "autoprefixer": "^10.4.20",
"autoprefixer": "^10.4.16", "eslint": "^9.17.0",
"eslint": "^8.54.0", "@eslint/js": "^9.17.0",
"eslint-plugin-react-hooks": "^4.6.0", "globals": "^15.14.0",
"eslint-plugin-react-refresh": "^0.4.4", "eslint-plugin-react-hooks": "^5.1.0",
"postcss": "^8.4.32", "eslint-plugin-react-refresh": "^0.4.16",
"tailwindcss": "^3.3.6", "postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"terser": "^5.24.0", "terser": "^5.24.0",
"@emotion/babel-plugin": "^11.11.0", "@emotion/babel-plugin": "^11.11.0",
"typescript": "^5.6.3", "typescript": "^5.7.2",
"vite": "^5.0.6", "vite": "^5.4.11",
"jest": "^29.7.0", "jest": "^29.7.0",
"@types/jest": "^29.5.10", "@types/jest": "^29.5.10",
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",

View File

@@ -15,12 +15,15 @@ import {
Divider, Divider,
} from '@mui/material'; } from '@mui/material';
import { Close, ChevronLeft, ChevronRight } from '@mui/icons-material'; import { Close, ChevronLeft, ChevronRight } from '@mui/icons-material';
import { formatDistanceToNow } from 'date-fns'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { useAuditLogStream } from '../hooks/useAuditLogStream'; import { useAuditLogStream } from '../hooks/useAuditLogStream';
import { AdminSkeleton } from './AdminSkeleton'; import { AdminSkeleton } from './AdminSkeleton';
import { EmptyState } from './EmptyState'; import { EmptyState } from './EmptyState';
import { ErrorState } from './ErrorState'; import { ErrorState } from './ErrorState';
dayjs.extend(relativeTime);
/** /**
* Props for AuditLogDrawer component * Props for AuditLogDrawer component
*/ */
@@ -154,9 +157,7 @@ export const AuditLogDrawer: React.FC<AuditLogDrawerProps> = ({
component="span" component="span"
color="text.secondary" color="text.secondary"
> >
{formatDistanceToNow(new Date(log.createdAt), { {dayjs(log.createdAt).fromNow()}
addSuffix: true,
})}
</Typography> </Typography>
{log.resourceType && ( {log.resourceType && (
<Typography <Typography
@@ -193,10 +194,7 @@ export const AuditLogDrawer: React.FC<AuditLogDrawerProps> = ({
}} }}
> >
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Updated{' '} Updated {dayjs(lastUpdated).fromNow()}
{formatDistanceToNow(lastUpdated, {
addSuffix: true,
})}
</Typography> </Typography>
<Box> <Box>
<IconButton <IconButton

View File

@@ -21,12 +21,15 @@ import {
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { formatDistanceToNow } from 'date-fns'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { useAuditLogStream } from '../hooks/useAuditLogStream'; import { useAuditLogStream } from '../hooks/useAuditLogStream';
import { AdminSkeleton } from './AdminSkeleton'; import { AdminSkeleton } from './AdminSkeleton';
import { EmptyState } from './EmptyState'; import { EmptyState } from './EmptyState';
import { ErrorState } from './ErrorState'; import { ErrorState } from './ErrorState';
dayjs.extend(relativeTime);
/** /**
* Props for AuditLogPanel component * Props for AuditLogPanel component
*/ */
@@ -148,9 +151,7 @@ export const AuditLogPanel: React.FC<AuditLogPanelProps> = ({
component="span" component="span"
color="text.secondary" color="text.secondary"
> >
{formatDistanceToNow(new Date(log.createdAt), { {dayjs(log.createdAt).fromNow()}
addSuffix: true,
})}
</Typography> </Typography>
{log.resourceType && ( {log.resourceType && (
<Typography <Typography
@@ -186,10 +187,7 @@ export const AuditLogPanel: React.FC<AuditLogPanelProps> = ({
}} }}
> >
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
Updated{' '} Updated {dayjs(lastUpdated).fromNow()}
{formatDistanceToNow(lastUpdated, {
addSuffix: true,
})}
</Typography> </Typography>
<Box> <Box>
<IconButton <IconButton

View File

@@ -1,5 +1,9 @@
import React from 'react'; import React from 'react';
import { Button } from '../../../shared-minimal/components/Button'; import { Button } from '../../../shared-minimal/components/Button';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import dayjs from 'dayjs';
import { useCreateDocument } from '../hooks/useDocuments'; import { useCreateDocument } from '../hooks/useDocuments';
import { documentsApi } from '../api/documents.api'; import { documentsApi } from '../api/documents.api';
import type { DocumentType } from '../types/documents.types'; import type { DocumentType } from '../types/documents.types';
@@ -144,6 +148,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
}; };
return ( return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<form onSubmit={handleSubmit} className="w-full"> <form onSubmit={handleSubmit} className="w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex flex-col"> <div className="flex flex-col">
@@ -207,21 +212,39 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<label className="text-sm font-medium text-slate-700 mb-1">Effective Date</label> <DatePicker
<input label="Effective Date"
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500" value={effectiveDate ? dayjs(effectiveDate) : null}
type="date" onChange={(newValue) => setEffectiveDate(newValue?.format('YYYY-MM-DD') || '')}
value={effectiveDate} format="MM/DD/YYYY"
onChange={(e) => setEffectiveDate(e.target.value)} slotProps={{
textField: {
fullWidth: true,
sx: {
'& .MuiOutlinedInput-root': {
minHeight: 44,
},
},
},
}}
/> />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<label className="text-sm font-medium text-slate-700 mb-1">Expiration Date</label> <DatePicker
<input label="Expiration Date"
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500" value={expirationDate ? dayjs(expirationDate) : null}
type="date" onChange={(newValue) => setExpirationDate(newValue?.format('YYYY-MM-DD') || '')}
value={expirationDate} format="MM/DD/YYYY"
onChange={(e) => setExpirationDate(e.target.value)} slotProps={{
textField: {
fullWidth: true,
sx: {
'& .MuiOutlinedInput-root': {
minHeight: 44,
},
},
},
}}
/> />
</div> </div>
@@ -282,12 +305,21 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
/> />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<label className="text-sm font-medium text-slate-700 mb-1">Expiration Date</label> <DatePicker
<input label="Expiration Date"
className="h-11 min-h-[44px] rounded-lg border border-slate-300 px-3 focus:outline-none focus:ring-2 focus:ring-primary-500" value={registrationExpirationDate ? dayjs(registrationExpirationDate) : null}
type="date" onChange={(newValue) => setRegistrationExpirationDate(newValue?.format('YYYY-MM-DD') || '')}
value={registrationExpirationDate} format="MM/DD/YYYY"
onChange={(e) => setRegistrationExpirationDate(e.target.value)} slotProps={{
textField: {
fullWidth: true,
sx: {
'& .MuiOutlinedInput-root': {
minHeight: 44,
},
},
},
}}
/> />
</div> </div>
<div className="flex flex-col md:col-span-2"> <div className="flex flex-col md:col-span-2">
@@ -336,6 +368,7 @@ export const DocumentForm: React.FC<DocumentFormProps> = ({ onSuccess, onCancel
<Button type="button" variant="secondary" onClick={onCancel} className="min-h-[44px]">Cancel</Button> <Button type="button" variant="secondary" onClick={onCancel} className="min-h-[44px]">Cancel</Button>
</div> </div>
</form> </form>
</LocalizationProvider>
); );
}; };

View File

@@ -16,8 +16,9 @@ import {
useMediaQuery useMediaQuery
} from '@mui/material'; } from '@mui/material';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import dayjs from 'dayjs';
import { FuelLogResponse, UpdateFuelLogRequest, FuelType } from '../types/fuel-logs.types'; import { FuelLogResponse, UpdateFuelLogRequest, FuelType } from '../types/fuel-logs.types';
import { useFuelGrades } from '../hooks/useFuelGrades'; import { useFuelGrades } from '../hooks/useFuelGrades';
@@ -135,7 +136,7 @@ export const FuelLogEditDialog: React.FC<FuelLogEditDialogProps> = ({
return ( return (
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDayjs}>
<Dialog <Dialog
open={open} open={open}
onClose={handleCancel} onClose={handleCancel}
@@ -154,9 +155,9 @@ export const FuelLogEditDialog: React.FC<FuelLogEditDialogProps> = ({
<Grid item xs={12}> <Grid item xs={12}>
<DateTimePicker <DateTimePicker
label="Date & Time" label="Date & Time"
value={formData.dateTime ? new Date(formData.dateTime) : null} value={formData.dateTime ? dayjs(formData.dateTime) : null}
onChange={(newValue) => handleInputChange('dateTime', newValue?.toISOString() || '')} onChange={(newValue) => handleInputChange('dateTime', newValue?.toISOString() || '')}
format="MM/dd/yyyy hh:mm a" format="MM/DD/YYYY hh:mm a"
slotProps={{ slotProps={{
textField: { textField: {
fullWidth: true, fullWidth: true,

View File

@@ -4,8 +4,9 @@ import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Grid, Card, CardHeader, CardContent, TextField, Box, Button, CircularProgress, ToggleButton, ToggleButtonGroup } from '@mui/material'; import { Grid, Card, CardHeader, CardContent, TextField, Box, Button, CircularProgress, ToggleButton, ToggleButtonGroup } from '@mui/material';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import dayjs from 'dayjs';
import { VehicleSelector } from './VehicleSelector'; import { VehicleSelector } from './VehicleSelector';
import { DistanceInput } from './DistanceInput'; import { DistanceInput } from './DistanceInput';
import { FuelTypeSelector } from './FuelTypeSelector'; import { FuelTypeSelector } from './FuelTypeSelector';
@@ -143,7 +144,7 @@ const FuelLogFormComponent: React.FC<{ onSuccess?: () => void; initial?: Partial
}, [useOdometer, setValue]); }, [useOdometer, setValue]);
return ( return (
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDayjs}>
<Card> <Card>
<CardHeader title="Add Fuel Log" subheader={<UnitSystemDisplay unitSystem={userSettings?.unitSystem} showLabel="Displaying in" />} /> <CardHeader title="Add Fuel Log" subheader={<UnitSystemDisplay unitSystem={userSettings?.unitSystem} showLabel="Displaying in" />} />
<CardContent> <CardContent>
@@ -161,9 +162,9 @@ const FuelLogFormComponent: React.FC<{ onSuccess?: () => void; initial?: Partial
<Controller name="dateTime" control={control} render={({ field }) => ( <Controller name="dateTime" control={control} render={({ field }) => (
<DateTimePicker <DateTimePicker
label="Date & Time" label="Date & Time"
value={field.value ? new Date(field.value) : null} value={field.value ? dayjs(field.value) : null}
onChange={(newValue) => field.onChange(newValue?.toISOString() || '')} onChange={(newValue) => field.onChange(newValue?.toISOString() || '')}
format="MM/dd/yyyy hh:mm a" format="MM/DD/YYYY hh:mm a"
slotProps={{ slotProps={{
textField: { textField: {
fullWidth: true, fullWidth: true,

View File

@@ -21,8 +21,9 @@ import {
useMediaQuery, useMediaQuery,
} from '@mui/material'; } from '@mui/material';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import dayjs from 'dayjs';
import { import {
MaintenanceRecordResponse, MaintenanceRecordResponse,
UpdateMaintenanceRecordRequest, UpdateMaintenanceRecordRequest,
@@ -149,7 +150,7 @@ export const MaintenanceRecordEditDialog: React.FC<MaintenanceRecordEditDialogPr
} }
return ( return (
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDayjs}>
<Dialog <Dialog
open={open} open={open}
onClose={handleCancel} onClose={handleCancel}
@@ -221,11 +222,11 @@ export const MaintenanceRecordEditDialog: React.FC<MaintenanceRecordEditDialogPr
<Grid item xs={12} sm={6}> <Grid item xs={12} sm={6}>
<DatePicker <DatePicker
label="Service Date *" label="Service Date *"
value={formData.date ? new Date(formData.date) : null} value={formData.date ? dayjs(formData.date) : null}
onChange={(newValue) => onChange={(newValue) =>
handleInputChange('date', newValue?.toISOString().split('T')[0] || '') handleInputChange('date', newValue?.toISOString().split('T')[0] || '')
} }
format="MM/dd/yyyy" format="MM/DD/YYYY"
slotProps={{ slotProps={{
textField: { textField: {
fullWidth: true, fullWidth: true,

View File

@@ -25,8 +25,9 @@ import {
InputAdornment, InputAdornment,
} from '@mui/material'; } from '@mui/material';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import dayjs from 'dayjs';
import { useMaintenanceRecords } from '../hooks/useMaintenanceRecords'; import { useMaintenanceRecords } from '../hooks/useMaintenanceRecords';
import { useVehicles } from '../../vehicles/hooks/useVehicles'; import { useVehicles } from '../../vehicles/hooks/useVehicles';
import { SubtypeCheckboxGroup } from './SubtypeCheckboxGroup'; import { SubtypeCheckboxGroup } from './SubtypeCheckboxGroup';
@@ -135,7 +136,7 @@ export const MaintenanceRecordForm: React.FC = () => {
} }
return ( return (
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDayjs}>
<Card> <Card>
<CardHeader title="Add Maintenance Record" /> <CardHeader title="Add Maintenance Record" />
<CardContent> <CardContent>
@@ -238,11 +239,11 @@ export const MaintenanceRecordForm: React.FC = () => {
render={({ field }) => ( render={({ field }) => (
<DatePicker <DatePicker
label="Date *" label="Date *"
value={field.value ? new Date(field.value) : null} value={field.value ? dayjs(field.value) : null}
onChange={(newValue) => onChange={(newValue) =>
field.onChange(newValue?.toISOString().split('T')[0] || '') field.onChange(newValue?.toISOString().split('T')[0] || '')
} }
format="MM/dd/yyyy" format="MM/DD/YYYY"
slotProps={{ slotProps={{
textField: { textField: {
fullWidth: true, fullWidth: true,

View File

@@ -3,7 +3,7 @@
*/ */
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Box, Typography, Button, Card, CardContent, Divider, FormControl, InputLabel, Select, MenuItem, List, ListItem } from '@mui/material'; import { Box, Typography, Button, Card, CardContent, Divider, FormControl, InputLabel, Select, MenuItem, List, ListItem, ListItemButton } from '@mui/material';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { Vehicle } from '../types/vehicles.types'; import { Vehicle } from '../types/vehicles.types';
import { useFuelLogs } from '../../fuel-logs/hooks/useFuelLogs'; import { useFuelLogs } from '../../fuel-logs/hooks/useFuelLogs';
@@ -250,22 +250,24 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
) : ( ) : (
<List disablePadding> <List disablePadding>
{filteredRecords.map(rec => ( {filteredRecords.map(rec => (
<ListItem key={rec.id} divider button onClick={() => openEditLog(rec.id, rec.type)}> <ListItem key={rec.id} divider disablePadding>
<Box sx={{ flexGrow: 1 }}> <ListItemButton onClick={() => openEditLog(rec.id, rec.type)}>
{/* Primary line: MPG/km-L and amount */} <Box sx={{ flexGrow: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> {/* Primary line: MPG/km-L and amount */}
<Typography variant="body1" color="text.primary"> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
{rec.summary || 'MPG: —'} <Typography variant="body1" color="text.primary">
</Typography> {rec.summary || 'MPG: —'}
<Typography variant="body1" color="text.primary"> </Typography>
{rec.amount || '—'} <Typography variant="body1" color="text.primary">
{rec.amount || '—'}
</Typography>
</Box>
{/* Secondary line: Grade • Date • Type */}
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.25 }}>
{rec.secondary || `${new Date(rec.date).toLocaleDateString()}${rec.type}`}
</Typography> </Typography>
</Box> </Box>
{/* Secondary line: Grade • Date • Type */} </ListItemButton>
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.25 }}>
{rec.secondary || `${new Date(rec.date).toLocaleDateString()}${rec.type}`}
</Typography>
</Box>
</ListItem> </ListItem>
))} ))}
</List> </List>

View File

@@ -46,7 +46,7 @@ export default defineConfig({
'forms': ['react-hook-form', '@hookform/resolvers', 'zod'], 'forms': ['react-hook-form', '@hookform/resolvers', 'zod'],
// Utilities // Utilities
'utils': ['date-fns', 'clsx'], 'utils': ['dayjs', 'clsx'],
// Animation and UI // Animation and UI
'animation': ['framer-motion', 'react-hot-toast'] 'animation': ['framer-motion', 'react-hot-toast']