Hybrid cars implementation 1.0(Without stats)

This commit is contained in:
EdiFarcas
2025-07-10 00:09:58 +03:00
parent b3b916f52b
commit cf1f78280c
14 changed files with 247 additions and 50 deletions
@@ -0,0 +1,18 @@
/*
Warnings:
- You are about to drop the column `fuelType` on the `Car` table. All the data in the column will be lost.
- Added the required column `fuelType` to the `FillUp` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Car" DROP COLUMN "fuelType",
ADD COLUMN "fuelTypes" "FuelType"[];
-- AlterTable: add as nullable first
ALTER TABLE "FillUp" ADD COLUMN "fuelType" "FuelType";
-- Set default for existing rows (choose the most common, e.g. GASOLINE)
UPDATE "FillUp" SET "fuelType" = 'GASOLINE' WHERE "fuelType" IS NULL;
-- Make the column required
ALTER TABLE "FillUp" ALTER COLUMN "fuelType" SET NOT NULL;
@@ -0,0 +1,11 @@
-- DropForeignKey
ALTER TABLE "FillUp" DROP CONSTRAINT "FillUp_carId_fkey";
-- DropForeignKey
ALTER TABLE "MileageEntry" DROP CONSTRAINT "MileageEntry_carId_fkey";
-- AddForeignKey
ALTER TABLE "FillUp" ADD CONSTRAINT "FillUp_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MileageEntry" ADD CONSTRAINT "MileageEntry_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+4 -4
View File
@@ -23,27 +23,27 @@ model Car {
make String // Manufacturer (e.g. "BMW")
model String // Model (e.g. "320i")
year Int // Year (e.g. 2019)
fuelType FuelType
fuelTypes FuelType[] // Changed from single fuelType to array for hybrid support
fillUps FillUp[]
mileage MileageEntry[]
}
model FillUp {
id String @id @default(cuid())
car Car @relation(fields: [carId], references: [id])
car Car @relation(fields: [carId], references: [id], onDelete: Cascade)
carId String
mileage Int
liters Float
cost Float
currency Currency
date DateTime @default(now())
fuelType FuelType // Add fuelType to each fill-up
}
model MileageEntry {
id String @id @default(cuid())
car Car @relation(fields: [carId], references: [id])
car Car @relation(fields: [carId], references: [id], onDelete: Cascade)
carId String
mileage Int
date DateTime @default(now())
+1
View File
@@ -39,6 +39,7 @@ export async function GET(req: Request) {
cost: f.cost,
currency: f.currency,
date: f.date,
fuelType: f.fuelType, // Fix: Prisma FillUp model includes fuelType
})),
...mileageEntries.map(m => ({
type: 'mileage',
+22 -2
View File
@@ -12,7 +12,7 @@ export async function GET(req: Request, { params }: { params: { carId: string }
const car = await prisma.car.findFirst({
where: {
id: carId,
user: { email: session.user?.email! },
user: { email: session.user?.email ?? undefined },
},
select: {
id: true,
@@ -20,10 +20,30 @@ export async function GET(req: Request, { params }: { params: { carId: string }
make: true,
model: true,
year: true,
fuelType: true,
fuelTypes: true,
},
});
if (!car) return NextResponse.json({ message: 'Car not found' }, { status: 404 });
return NextResponse.json(car);
}
export async function DELETE(req: Request, { params }: { params: { carId: string } }) {
const session = await getSession();
if (!session) return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
const carId = params.carId;
if (!carId) return NextResponse.json({ message: 'Missing carId' }, { status: 400 });
// Only allow deleting user's own car
const car = await prisma.car.findFirst({
where: {
id: carId,
user: { email: session.user?.email ?? undefined },
},
});
if (!car) return NextResponse.json({ message: 'Car not found' }, { status: 404 });
await prisma.car.delete({ where: { id: carId } });
return NextResponse.json({ message: 'Car deleted' });
}
+4 -4
View File
@@ -7,14 +7,14 @@ export async function POST(req: Request) {
if (!session) return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
const body = await req.json();
const { name, make, model, year, fuelType } = body;
const { name, make, model, year, fuelTypes } = body;
if (!name || !make || !model || !year || !fuelType) {
if (!name || !make || !model || !year || !fuelTypes || !Array.isArray(fuelTypes) || fuelTypes.length === 0) {
return NextResponse.json({ message: 'Missing fields' }, { status: 400 });
}
const user = await prisma.user.findUnique({
where: { email: session.user?.email! },
where: { email: session.user?.email ?? undefined },
});
if (!user) {
@@ -27,7 +27,7 @@ export async function POST(req: Request) {
make,
model,
year: parseInt(year),
fuelType,
fuelTypes: { set: fuelTypes },
userId: user.id,
},
});
+4 -3
View File
@@ -26,15 +26,15 @@ export async function POST(req: Request) {
const session = await getSession();
if (!session) return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
const { carId, mileage, liters, cost, currency } = await req.json();
if (!carId || !mileage || !liters || !cost || !currency) {
const { carId, mileage, liters, cost, currency, fuelType } = await req.json();
if (!carId || !mileage || !liters || !cost || !currency || !fuelType) {
return NextResponse.json({ message: 'Missing fields' }, { status: 400 });
}
const car = await prisma.car.findFirst({
where: {
id: carId,
user: { email: session.user?.email! },
user: { email: session.user?.email ?? undefined },
},
});
@@ -47,6 +47,7 @@ export async function POST(req: Request) {
cost: parseFloat(cost),
currency,
carId,
fuelType,
},
});
+50 -11
View File
@@ -11,6 +11,7 @@ interface FillUp {
cost: number;
currency: string;
date: string;
fuelType: string; // Add fuelType to FillUp interface
}
const currencies = ['EUR', 'USD', 'RON', 'GBP'];
@@ -18,12 +19,13 @@ const currencies = ['EUR', 'USD', 'RON', 'GBP'];
export default function FillUpsPage() {
const { carId } = useParams();
const [fillups, setFillups] = useState<FillUp[]>([]);
const [carFuelType, setCarFuelType] = useState<string | undefined>(undefined);
const [carFuelTypes, setCarFuelTypes] = useState<string[]>([]);
const [form, setForm] = useState({
mileage: '',
liters: '',
cost: '',
currency: 'EUR',
fuelType: '',
});
const [error, setError] = useState('');
@@ -34,11 +36,11 @@ export default function FillUpsPage() {
.then(setFillups);
}, [carId]);
// Fetch car fuel type
// Fetch car fuel types
useEffect(() => {
fetch(`/api/cars/${carId}`)
.then((res) => res.json())
.then((car) => setCarFuelType(car?.fuelType));
.then((car) => setCarFuelTypes(car?.fuelTypes || []));
}, [carId]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
@@ -49,14 +51,14 @@ export default function FillUpsPage() {
e.preventDefault();
const res = await fetch('/api/fillups', {
method: 'POST',
body: JSON.stringify({ ...form, carId }),
body: JSON.stringify({ ...form, carId, fuelType: form.fuelType || carFuelTypes[0] }),
headers: { 'Content-Type': 'application/json' },
});
if (res.ok) {
const newFill = await res.json();
setFillups((prev) => [newFill, ...prev]);
setForm({ mileage: '', liters: '', cost: '', currency: 'EUR' });
setForm({ mileage: '', liters: '', cost: '', currency: 'EUR', fuelType: carFuelTypes[0] || '' });
setError('');
} else {
const data = await res.json();
@@ -64,8 +66,8 @@ export default function FillUpsPage() {
}
};
// Only render fill-ups when carFuelType is loaded
if (carFuelType === undefined) {
// Only render fill-ups when carFuelTypes is loaded
if (!carFuelTypes.length) {
return (
<main className="max-w-2xl mx-auto p-8 space-y-8 bg-[var(--muted)] rounded-xl shadow">
<h1 className="text-2xl font-bold text-[var(--primary)]">Fuel Fill-Ups</h1>
@@ -95,21 +97,39 @@ export default function FillUpsPage() {
</div>
<div className="flex flex-col gap-1">
<label htmlFor="liters" className="font-medium text-[var(--primary)]">
{carFuelType === 'ELECTRIC' ? 'Kilowatt-hours' : 'Liters'}
{(() => {
const fuel = carFuelTypes.length > 1 ? (form.fuelType || carFuelTypes[0]) : carFuelTypes[0];
if (fuel === 'ELECTRIC') return 'Kilowatt-hours';
if (fuel === 'LPG') return 'Liters (LPG)';
if (fuel === 'GASOLINE') return 'Liters (Gasoline)';
if (fuel === 'DIESEL') return 'Liters (Diesel)';
return 'Liters';
})()}
</label>
<input
id="liters"
name="liters"
type="number"
step="0.01"
placeholder={carFuelType === 'ELECTRIC' ? 'e.g. 45.5' : 'e.g. 45.5'}
placeholder={(() => {
const fuel = carFuelTypes.length > 1 ? (form.fuelType || carFuelTypes[0]) : carFuelTypes[0];
if (fuel === 'ELECTRIC') return 'e.g. 45.5';
return 'e.g. 45.5';
})()}
value={form.liters}
onChange={handleChange}
required
className="border border-[var(--border)] px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--primary)] bg-[var(--muted)] text-[var(--foreground)]"
/>
<span className="text-xs text-gray-500">
{carFuelType === 'ELECTRIC' ? 'How many kilowatt-hours did you charge?' : 'How many liters did you fill?'}
{(() => {
const fuel = carFuelTypes.length > 1 ? (form.fuelType || carFuelTypes[0]) : carFuelTypes[0];
if (fuel === 'ELECTRIC') return 'How many kilowatt-hours did you charge?';
if (fuel === 'LPG') return 'How many liters of LPG did you fill?';
if (fuel === 'GASOLINE') return 'How many liters of gasoline did you fill?';
if (fuel === 'DIESEL') return 'How many liters of diesel did you fill?';
return 'How many liters did you fill?';
})()}
</span>
</div>
<div className="flex flex-col gap-1">
@@ -141,6 +161,25 @@ export default function FillUpsPage() {
))}
</select>
</div>
{carFuelTypes.length > 1 && (
<div className="flex flex-col gap-1 col-span-full">
<label className="font-medium text-[var(--primary)]">Fuel Type</label>
<select
name="fuelType"
value={form.fuelType || carFuelTypes[0]}
onChange={handleChange}
required
className="border border-[var(--border)] px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--primary)] bg-[var(--muted)] text-[var(--foreground)]"
>
{carFuelTypes.map((type) => (
<option key={type} value={type}>{type}</option>
))}
</select>
</div>
)}
{carFuelTypes.length === 1 && (
<input type="hidden" name="fuelType" value={carFuelTypes[0]} />
)}
<button type="submit" className="col-span-full bg-[var(--secondary)] text-white py-2 rounded-lg font-semibold shadow hover:bg-green-700 transition mt-2">
Add Fill-Up
</button>
@@ -149,7 +188,7 @@ export default function FillUpsPage() {
<ul className="space-y-3">
{fillups.map((fill) => (
<FillUpCard key={fill.id} fill={{ ...fill, car: { fuelType: carFuelType } }} />
<FillUpCard key={fill.id} fill={{ ...fill, car: { fuelType: fill.fuelType } }} />
))}
</ul>
</main>
+26 -6
View File
@@ -3,6 +3,7 @@ import { prisma } from '@/lib/prisma';
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { FaCarSide, FaGasPump, FaCalendarAlt, FaIndustry, FaBolt } from 'react-icons/fa';
import DeleteCarButton from '@/components/DeleteCarButton';
interface CarDetailPageProps {
params: {
@@ -15,13 +16,21 @@ export default async function CarDetailPage({ params }: CarDetailPageProps) {
const session = await getSession();
if (!session) redirect('/auth/login');
const userEmail = session.user?.email!;
const userEmail = session.user?.email || '';
const car = await prisma.car.findFirst({
where: {
id: resolvedParams.carId,
user: { email: userEmail },
},
});
select: {
id: true,
name: true,
make: true,
model: true,
year: true,
fuelTypes: true,
},
}) as { id: string; name: string; make: string; model: string; year: number; fuelTypes?: string[] };
if (!car) {
return <div className="p-8 text-center text-red-500 text-lg font-semibold">Car not found or unauthorized access.</div>;
@@ -44,8 +53,15 @@ export default async function CarDetailPage({ params }: CarDetailPageProps) {
<h1 className="text-4xl font-extrabold text-[var(--primary)] leading-tight tracking-tight flex items-center gap-3">
{car.name}
<span className="inline-block px-3 py-1 rounded-full text-base font-bold bg-gray-200 text-gray-700 ml-2">{car.year}</span>
{Array.isArray(car.fuelTypes) && car.fuelTypes.length > 1 ? (
<span className="ml-2 flex items-center">
<span className={`px-2 py-1 rounded-l-full text-xs font-bold ${fuelMeta[car.fuelTypes[0]]?.color || 'bg-gray-100 text-gray-700'}`} style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0, borderRight: '1px solid #ccc' }}>{fuelMeta[car.fuelTypes[0]]?.icon}{car.fuelTypes[0]}</span>
<span className={`px-2 py-1 rounded-r-full text-xs font-bold ${fuelMeta[car.fuelTypes[1]]?.color || 'bg-gray-100 text-gray-700'}`} style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}>{fuelMeta[car.fuelTypes[1]]?.icon}{car.fuelTypes[1]}</span>
</span>
) : Array.isArray(car.fuelTypes) && car.fuelTypes.length === 1 ? (
<span className={`ml-2 px-2 py-1 rounded-full text-xs font-bold ${fuelMeta[car.fuelTypes[0]]?.color || 'bg-gray-100 text-gray-700'}`}>{fuelMeta[car.fuelTypes[0]]?.icon}{car.fuelTypes[0]}</span>
) : null}
</h1>
<span className={`mt-2 px-4 py-1 rounded-full text-sm font-bold flex items-center gap-1 ${fuelMeta[car.fuelType]?.color || 'bg-gray-100 text-gray-700'}`}>{fuelMeta[car.fuelType]?.icon}{car.fuelType}</span>
</div>
{/* Car Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 px-8">
@@ -65,9 +81,12 @@ export default async function CarDetailPage({ params }: CarDetailPageProps) {
<span className="ml-1 text-gray-400 font-mono text-lg">{car.year}</span>
</div>
<div className="flex items-center gap-3">
{fuelMeta[car.fuelType]?.icon}
<span className="font-semibold text-[var(--foreground)]">Fuel:</span>
<span className={`ml-1 px-2 py-1 rounded-full text-xs font-bold ${fuelMeta[car.fuelType]?.color || 'bg-gray-100 text-gray-700'}`}>{car.fuelType}</span>
{Array.isArray(car.fuelTypes) && car.fuelTypes.length > 0
? car.fuelTypes.map((ft: string) => (
<span key={ft} className={`ml-1 px-2 py-1 rounded-full text-xs font-bold flex items-center gap-1 ${fuelMeta[ft]?.color || 'bg-gray-100 text-gray-700'}`}>{fuelMeta[ft]?.icon}{ft}</span>
))
: null
}
</div>
</div>
{/* Actions */}
@@ -90,6 +109,7 @@ export default async function CarDetailPage({ params }: CarDetailPageProps) {
>
📊 View Stats
</Link>
<DeleteCarButton carId={car.id} />
</div>
</div>
</main>
+33 -12
View File
@@ -12,16 +12,33 @@ export default function AddCarPage() {
make: '',
model: '',
year: '',
fuelType: 'GASOLINE',
fuelTypes: ['GASOLINE'], // default to one selected
});
const [error, setError] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
setForm({ ...form, [e.target.name]: e.target.value });
const { name, value, type, checked } = e.target;
if (name === 'fuelTypes') {
setForm((prev) => {
let updated = prev.fuelTypes;
if (checked) {
updated = [...prev.fuelTypes, value];
} else {
updated = prev.fuelTypes.filter((t: string) => t !== value);
}
return { ...prev, fuelTypes: updated };
});
} else {
setForm({ ...form, [name]: value });
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!form.fuelTypes.length) {
setError('Please select at least one fuel type.');
return;
}
const res = await fetch('/api/cars', {
method: 'POST',
body: JSON.stringify(form),
@@ -97,18 +114,22 @@ export default function AddCarPage() {
/>
</div>
<div>
<label htmlFor="fuelType" className="block mb-1 font-semibold text-[var(--foreground)]">Fuel Type</label>
<select
id="fuelType"
name="fuelType"
value={form.fuelType}
onChange={handleChange}
className="w-full border border-[var(--border)] px-4 py-3 rounded-lg bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
>
<label className="block mb-1 font-semibold text-[var(--foreground)]">Fuel Types</label>
<div className="flex flex-wrap gap-4">
{fuelTypes.map((type) => (
<option key={type} value={type}>{type}</option>
<label key={type} className="flex items-center gap-2">
<input
type="checkbox"
name="fuelTypes"
value={type}
checked={form.fuelTypes.includes(type)}
onChange={handleChange}
className="accent-[var(--primary)]"
/>
{type}
</label>
))}
</select>
</div>
</div>
<button
type="submit"
+21 -1
View File
@@ -19,12 +19,32 @@ export default function CarCard({ car }: { car: any }) {
}
}
// Show all fuel types as badges if car.fuelTypes is an array
let fuelBadges = null;
if (Array.isArray(car.fuelTypes) && car.fuelTypes.length > 0) {
if (car.fuelTypes.length === 1) {
fuelBadges = (
<span className={`ml-2 px-2 py-1 rounded-full text-xs font-bold ${getFuelBadgeColor(car.fuelTypes[0])}`}>{car.fuelTypes[0]}</span>
);
} else {
// Show both, but visually split: half badge for each
fuelBadges = (
<span className="ml-2 flex items-center">
<span className={`px-2 py-1 rounded-l-full text-xs font-bold ${getFuelBadgeColor(car.fuelTypes[0])}`} style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0, borderRight: '1px solid #ccc' }}>{car.fuelTypes[0]}</span>
<span className={`px-2 py-1 rounded-r-full text-xs font-bold ${getFuelBadgeColor(car.fuelTypes[1])}`} style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}>{car.fuelTypes[1]}</span>
</span>
);
}
} else if (car.fuelType) {
fuelBadges = <span className={`ml-2 px-2 py-1 rounded-full text-xs font-bold ${getFuelBadgeColor(car.fuelType)}`}>{car.fuelType}</span>;
}
return (
<Link href={`/dashboard/cars/${car.id}`}>
<div className="border border-[var(--border)] rounded-xl p-4 shadow-sm hover:shadow-md transition bg-[var(--muted)]">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-[var(--primary)]">{car.name}</h2>
<span className={`ml-2 px-2 py-1 rounded-full text-xs font-bold ${getFuelBadgeColor(car.fuelType)}`}>{car.fuelType}</span>
<span className="flex flex-wrap gap-1">{fuelBadges}</span>
</div>
<p className="text-[var(--foreground)]/80">{car.make} {car.model} ({car.year})</p>
<div className="mt-3 text-sm text-[var(--foreground)]/80">
+24
View File
@@ -0,0 +1,24 @@
"use client";
export default function DeleteCarButton({ carId }: { carId: string }) {
const handleDelete = async (e: React.FormEvent) => {
e.preventDefault();
if (!confirm('Are you sure you want to delete this car? This will delete all fill-ups and mileage logs for this car.')) return;
const res = await fetch(`/api/cars/${carId}`, { method: 'DELETE' });
if (res.ok) {
window.location.href = '/dashboard';
} else {
alert('Failed to delete car.');
}
};
return (
<form onSubmit={handleDelete}>
<button
type="submit"
className="flex items-center gap-2 bg-red-600 text-white px-6 py-3 rounded-lg font-bold shadow-lg hover:bg-red-800 hover:scale-[1.03] transition-all"
>
🗑 Delete Car
</button>
</form>
);
}
+2 -1
View File
@@ -10,6 +10,7 @@ interface ActivityItem {
cost?: number;
currency?: string;
date: string;
fuelType?: string; // Add fuelType for fill-ups
}
export default function RecentActivity() {
@@ -46,7 +47,7 @@ export default function RecentActivity() {
</div>
<div className="text-sm text-[var(--foreground)]/60">
{item.type === 'fillup'
? `${item.liters} L, ${item.mileage} km${item.cost ? `, ${item.cost} ${item.currency}` : ''}`
? `${item.liters} ${item.fuelType === 'ELECTRIC' ? 'kWh' : item.fuelType === 'LPG' ? 'L (LPG)' : item.fuelType === 'GASOLINE' ? 'L (Gasoline)' : item.fuelType === 'DIESEL' ? 'L (Diesel)' : 'L'}, ${item.mileage} km${item.cost ? `, ${item.cost} ${item.currency}` : ''}`
: `${item.mileage} km`}
{' '}on {new Date(item.date).toLocaleDateString()}
</div>
+27 -6
View File
@@ -17,20 +17,28 @@ interface FillUp {
cost: number;
currency: string;
date: string | Date;
car?: { fuelType?: string };
car?: { fuelType?: string; fuelTypes?: string[] };
fuelType?: string;
}
export default function StatsPageClient({ fillUps, carId, error, loading = false }: { fillUps: FillUp[]; carId: string; error?: string; loading?: boolean }) {
const [range, setRange] = useState<number | null>(30);
const [units, setUnits] = useState<Units>('metric');
// Hybrid: fuel type selection
const allFuelTypes = Array.from(new Set(fillUps.map(f => f.fuelType).filter(Boolean)));
const [selectedFuelType, setSelectedFuelType] = useState<string>('ALL');
const filteredByFuel = useMemo(() => {
if (selectedFuelType === 'ALL') return fillUps;
return fillUps.filter(f => f.fuelType === selectedFuelType);
}, [fillUps, selectedFuelType]);
const now = useMemo(() => new Date(), []);
const filtered = useMemo(() => {
if (!range) return fillUps;
return fillUps.filter(f => {
if (!range) return filteredByFuel;
return filteredByFuel.filter(f => {
const d = typeof f.date === 'string' ? new Date(f.date) : f.date;
return d >= new Date(now.getTime() - (range ?? 30) * 24 * 60 * 60 * 1000);
});
}, [fillUps, range, now]);
}, [filteredByFuel, range, now]);
if (error) {
return (
@@ -174,7 +182,7 @@ export default function StatsPageClient({ fillUps, carId, error, loading = false
];
// Get fuel type from first fill-up (all fill-ups for a car have the same type)
const fuelType = fillUps[0]?.car?.fuelType;
const fuelType = filtered[0]?.fuelType;
const isElectric = fuelType === 'ELECTRIC';
// If electric, always use metric units
const displayUnits = isElectric ? 'metric' : units;
@@ -186,6 +194,18 @@ export default function StatsPageClient({ fillUps, carId, error, loading = false
<span role="img" aria-label="stats">📊</span> Fuel Stats
</h1>
<div className="flex flex-col sm:flex-row sm:items-center gap-2 mb-2 w-full justify-center">
{allFuelTypes.length > 1 && (
<select
value={selectedFuelType}
onChange={e => setSelectedFuelType(e.target.value)}
className="border border-[var(--border)] px-2 py-1 rounded-lg bg-[var(--background)] text-[var(--foreground)] font-semibold"
>
<option value="ALL">All Fuels</option>
{allFuelTypes.map(ft => (
<option key={ft} value={ft}>{ft}</option>
))}
</select>
)}
{!isElectric && (
<UnitsToggle units={units} setUnits={setUnits} disabled={isElectric} />
)}
@@ -225,7 +245,8 @@ export default function StatsPageClient({ fillUps, carId, error, loading = false
<StatCard label={`Total Fuel Used`} value={`${totalLiters.toFixed(2)} ${isElectric ? 'kWh' : (displayUnits === 'imperial' ? 'gal' : 'L')}`} icon="⛽" color="secondary" info={isElectric ? 'Total kilowatt-hours used in selected period.' : (displayUnits === 'imperial' ? 'Total gallons used in selected period.' : 'Total liters used in selected period.')} />
<StatCard label="Total Fuel Cost" value={`${totalCost.toFixed(2)} ${filtered[0].currency}`} icon="💸" color="accent" info="Sum of all fill-up costs in selected period." />
<StatCard label="Average Consumption" value={`${avgConsumption.toFixed(2)} ${isElectric ? 'kWh / 100km' : (displayUnits === 'imperial' ? 'MPG' : 'L / 100km')}`} icon="📏" color="primary" info={isElectric ? 'Kilowatt-hours per 100km (lower is better).' : (displayUnits === 'imperial' ? 'Miles per gallon (higher is better).' : 'Liters per 100km (lower is better).')} />
<StatCard label={`Average Cost / ${displayUnits === 'imperial' ? 'mi' : 'km'}`} value={`${costPerDist.toFixed(2)} ${filtered[0].currency}`} icon="💰" color="secondary" info={`Average fuel cost per ${displayUnits === 'imperial' ? 'mile' : 'kilometer'}.`} />
<StatCard label="Cost per Distance" value={`${costPerDist.toFixed(2)} ${filtered[0].currency} / ${displayUnits === 'imperial' ? 'mi' : 'km'}`} icon="💰" color="secondary" info="Fuel cost per unit of distance." />
<StatCard label="Fill-Up Count" value={`${filtered.length}`} icon="⛽️" color="accent" info="Total number of fill-ups in selected period." />
</div>
</div>
</div>