From 96440104c843e09e6920b71f755b4f91fae2bc64 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:03:31 -0600 Subject: [PATCH] fix: remove legacy TCO fields from vehicle forms (refs #37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove CostInterval type and TCOResponse interface from frontend types - Remove insurance/registration cost fields from VehicleForm schema and UI - Keep purchasePrice and purchaseDate fields on vehicle form - Remove TCODisplay component from VehicleDetailPage - Delete TCODisplay.tsx component file - Remove getTCO method from vehicles API client Legacy TCO fields moved to ownership-costs feature in #29. Backend endpoint preserved for future reporting feature. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../src/features/vehicles/api/vehicles.api.ts | 10 +- .../vehicles/components/TCODisplay.tsx | 140 ------------------ .../vehicles/components/VehicleForm.tsx | 127 +--------------- .../vehicles/pages/VehicleDetailPage.tsx | 9 -- .../features/vehicles/types/vehicles.types.ts | 37 ----- 5 files changed, 5 insertions(+), 318 deletions(-) delete mode 100644 frontend/src/features/vehicles/components/TCODisplay.tsx diff --git a/frontend/src/features/vehicles/api/vehicles.api.ts b/frontend/src/features/vehicles/api/vehicles.api.ts index d24d6b0..ed409c7 100644 --- a/frontend/src/features/vehicles/api/vehicles.api.ts +++ b/frontend/src/features/vehicles/api/vehicles.api.ts @@ -3,7 +3,7 @@ */ import { apiClient } from '../../../core/api/client'; -import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, DecodedVehicleData, TCOResponse } from '../types/vehicles.types'; +import { Vehicle, CreateVehicleRequest, UpdateVehicleRequest, DecodedVehicleData } from '../types/vehicles.types'; // All requests (including dropdowns) use authenticated apiClient @@ -88,13 +88,5 @@ export const vehiclesApi = { decodeVin: async (vin: string): Promise => { const response = await apiClient.post('/vehicles/decode-vin', { vin }); return response.data; - }, - - /** - * Get Total Cost of Ownership data for a vehicle - */ - getTCO: async (vehicleId: string): Promise => { - const response = await apiClient.get(`/vehicles/${vehicleId}/tco`); - return response.data; } }; diff --git a/frontend/src/features/vehicles/components/TCODisplay.tsx b/frontend/src/features/vehicles/components/TCODisplay.tsx deleted file mode 100644 index 32a5187..0000000 --- a/frontend/src/features/vehicles/components/TCODisplay.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @ai-summary TCO (Total Cost of Ownership) display component - * Right-justified display showing lifetime cost and cost per mile/km - */ - -import React, { useEffect, useState } from 'react'; -import { TCOResponse } from '../types/vehicles.types'; -import { vehiclesApi } from '../api/vehicles.api'; - -interface TCODisplayProps { - vehicleId: string; - tcoEnabled?: boolean; -} - -// Currency symbol mapping -const CURRENCY_SYMBOLS: Record = { - USD: '$', - EUR: '€', - GBP: '£', - CAD: 'CA$', - AUD: 'A$', -}; - -export const TCODisplay: React.FC = ({ vehicleId, tcoEnabled }) => { - const [tco, setTco] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - if (!tcoEnabled) { - setTco(null); - return; - } - - const fetchTCO = async () => { - setIsLoading(true); - setError(null); - try { - const data = await vehiclesApi.getTCO(vehicleId); - setTco(data); - } catch (err: any) { - console.error('Failed to fetch TCO:', err); - setError('Unable to load TCO data'); - } finally { - setIsLoading(false); - } - }; - - fetchTCO(); - }, [vehicleId, tcoEnabled]); - - // Don't render if TCO is disabled - if (!tcoEnabled) { - return null; - } - - // Loading state - if (isLoading) { - return ( -
-
-
-
-
-
- ); - } - - // Error state - if (error) { - return ( -
- {error} -
- ); - } - - // No data - if (!tco) { - return null; - } - - const currencySymbol = CURRENCY_SYMBOLS[tco.currencyCode] || tco.currencyCode; - - // Format currency with proper separators - const formatCurrency = (value: number): string => { - return value.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - }; - - return ( -
-
- {currencySymbol}{formatCurrency(tco.lifetimeTotal)} -
-
- Lifetime Total -
- {tco.costPerDistance > 0 && ( - <> -
- {currencySymbol}{formatCurrency(tco.costPerDistance)}/{tco.distanceUnit} -
-
- Cost per {tco.distanceUnit} -
- - )} - - {/* Cost breakdown tooltip/details */} -
-
- {tco.purchasePrice > 0 && ( -
Purchase: {currencySymbol}{formatCurrency(tco.purchasePrice)}
- )} - {tco.insuranceCosts > 0 && ( -
Insurance: {currencySymbol}{formatCurrency(tco.insuranceCosts)}
- )} - {tco.registrationCosts > 0 && ( -
Registration: {currencySymbol}{formatCurrency(tco.registrationCosts)}
- )} - {tco.taxCosts > 0 && ( -
Tax: {currencySymbol}{formatCurrency(tco.taxCosts)}
- )} - {tco.otherCosts > 0 && ( -
Other: {currencySymbol}{formatCurrency(tco.otherCosts)}
- )} - {tco.fuelCosts > 0 && ( -
Fuel: {currencySymbol}{formatCurrency(tco.fuelCosts)}
- )} - {tco.maintenanceCosts > 0 && ( -
Maintenance: {currencySymbol}{formatCurrency(tco.maintenanceCosts)}
- )} -
-
-
- ); -}; diff --git a/frontend/src/features/vehicles/components/VehicleForm.tsx b/frontend/src/features/vehicles/components/VehicleForm.tsx index 4158bda..0b61c6f 100644 --- a/frontend/src/features/vehicles/components/VehicleForm.tsx +++ b/frontend/src/features/vehicles/components/VehicleForm.tsx @@ -3,24 +3,16 @@ */ import React, { useState, useEffect, useRef } from 'react'; -import { useForm, Controller } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; -import { Checkbox, FormControlLabel } from '@mui/material'; import { Button } from '../../../shared-minimal/components/Button'; -import { CreateVehicleRequest, Vehicle, CostInterval } from '../types/vehicles.types'; +import { CreateVehicleRequest, Vehicle } from '../types/vehicles.types'; import { vehiclesApi } from '../api/vehicles.api'; import { VehicleImageUpload } from './VehicleImageUpload'; import { useTierAccess } from '../../../core/hooks/useTierAccess'; import { UpgradeRequiredDialog } from '../../../shared-minimal/components/UpgradeRequiredDialog'; -// Cost interval options -const costIntervalOptions: { value: CostInterval; label: string }[] = [ - { value: 'monthly', label: 'Monthly' }, - { value: 'semi_annual', label: 'Semi-Annual (6 months)' }, - { value: 'annual', label: 'Annual' }, -]; - const vehicleSchema = z .object({ vin: z.string().max(17).nullable().optional().transform(val => val ?? undefined), @@ -36,14 +28,8 @@ const vehicleSchema = z color: z.string().nullable().optional(), licensePlate: z.string().nullable().optional(), odometerReading: z.number().min(0).nullable().optional(), - // TCO fields purchasePrice: z.number().min(0).nullable().optional(), purchaseDate: z.string().nullable().optional(), - insuranceCost: z.number().min(0).nullable().optional(), - insuranceInterval: z.enum(['monthly', 'semi_annual', 'annual']).nullable().optional(), - registrationCost: z.number().min(0).nullable().optional(), - registrationInterval: z.enum(['monthly', 'semi_annual', 'annual']).nullable().optional(), - tcoEnabled: z.boolean().nullable().optional(), }) .refine( (data) => { @@ -137,7 +123,6 @@ export const VehicleForm: React.FC = ({ watch, setValue, reset, - control, } = useForm({ resolver: zodResolver(vehicleSchema), defaultValues: initialData, @@ -841,14 +826,11 @@ export const VehicleForm: React.FC = ({ /> - {/* Ownership Costs Section (TCO) */} + {/* Purchase Information Section */}

- Ownership Costs + Purchase Information

-

- Track your total cost of ownership including purchase price and recurring costs. -

@@ -878,107 +860,6 @@ export const VehicleForm: React.FC = ({ />
- -
-
- - -
- -
- - -
-
- -
-
- - -
- -
- - -
-
- -
- ( - field.onChange(e.target.checked)} - color="primary" - sx={{ '& .MuiSvgIcon-root': { fontSize: 24 } }} - /> - } - label="Display Total Cost of Ownership on vehicle details" - sx={{ - minHeight: 44, - '& .MuiFormControlLabel-label': { - fontSize: '0.875rem', - fontWeight: 500, - color: 'text.primary', - }, - }} - /> - )} - /> -

- When enabled, shows lifetime cost and cost per mile/km on the vehicle detail page. -

-
diff --git a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx index 7c895f3..af2a39a 100644 --- a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx +++ b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx @@ -16,7 +16,6 @@ import { vehiclesApi } from '../api/vehicles.api'; import { Card } from '../../../shared-minimal/components/Card'; import { VehicleForm } from '../components/VehicleForm'; import { VehicleImage } from '../components/VehicleImage'; -import { TCODisplay } from '../components/TCODisplay'; import { useFuelLogs } from '../../fuel-logs/hooks/useFuelLogs'; import { FuelLogResponse, UpdateFuelLogRequest } from '../../fuel-logs/types/fuel-logs.types'; import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog'; @@ -383,14 +382,6 @@ export const VehicleDetailPage: React.FC = () => { )} - {/* TCO Display - right-justified */} - - -
diff --git a/frontend/src/features/vehicles/types/vehicles.types.ts b/frontend/src/features/vehicles/types/vehicles.types.ts index 8238c1d..6bd675d 100644 --- a/frontend/src/features/vehicles/types/vehicles.types.ts +++ b/frontend/src/features/vehicles/types/vehicles.types.ts @@ -2,9 +2,6 @@ * @ai-summary Type definitions for vehicles feature */ -// TCO cost interval types -export type CostInterval = 'monthly' | 'semi_annual' | 'annual'; - export interface Vehicle { id: string; userId: string; @@ -25,14 +22,8 @@ export interface Vehicle { createdAt: string; updatedAt: string; imageUrl?: string; - // TCO fields purchasePrice?: number; purchaseDate?: string; - insuranceCost?: number; - insuranceInterval?: CostInterval; - registrationCost?: number; - registrationInterval?: CostInterval; - tcoEnabled?: boolean; } export interface CreateVehicleRequest { @@ -49,14 +40,8 @@ export interface CreateVehicleRequest { color?: string; licensePlate?: string; odometerReading?: number; - // TCO fields purchasePrice?: number; purchaseDate?: string; - insuranceCost?: number; - insuranceInterval?: CostInterval; - registrationCost?: number; - registrationInterval?: CostInterval; - tcoEnabled?: boolean; } export interface UpdateVehicleRequest { @@ -73,30 +58,8 @@ export interface UpdateVehicleRequest { color?: string; licensePlate?: string; odometerReading?: number; - // TCO fields purchasePrice?: number | null; purchaseDate?: string | null; - insuranceCost?: number | null; - insuranceInterval?: CostInterval | null; - registrationCost?: number | null; - registrationInterval?: CostInterval | null; - tcoEnabled?: boolean; -} - -// TCO (Total Cost of Ownership) response -export interface TCOResponse { - vehicleId: string; - purchasePrice: number; - insuranceCosts: number; - registrationCosts: number; - taxCosts: number; - otherCosts: number; - fuelCosts: number; - maintenanceCosts: number; - lifetimeTotal: number; - costPerDistance: number; - distanceUnit: string; - currencyCode: string; } /**