Loading...
+diff --git a/frontend/src/app/vehicle-planner/page.tsx b/frontend/src/app/vehicle-planner/page.tsx new file mode 100644 index 00000000..b4757e25 --- /dev/null +++ b/frontend/src/app/vehicle-planner/page.tsx @@ -0,0 +1,255 @@ +'use client' + +import { useEffect } from 'react' +import { useRouter } from 'next/navigation' +import { X } from 'lucide-react' +import clsx from 'clsx' + +import { VehiclePlannerTabs } from '@/components/vehicle-planner/VehiclePlannerTabs' +import { VehicleFinancingTab } from '@/components/vehicle-planner/tabs/VehicleFinancingTab' +import { CostBreakdownTab } from '@/components/vehicle-planner/tabs/CostBreakdownTab' +import { DepreciationTab } from '@/components/vehicle-planner/tabs/DepreciationTab' +import { TotalCostTab } from '@/components/vehicle-planner/tabs/TotalCostTab' +import { ScenariosTab } from '@/components/vehicle-planner/tabs/ScenariosTab' +import { useColorScheme } from '@/stores' +import { + useVehiclePlannerStore, + useVehiclePlannerActions, + useVehicleActiveTab, + useActiveVehicleScenario, +} from '@/stores/vehiclePlannerStore' + +// ============================================================================ +// THEME-AWARE DESIGN (reuses insurance planner theme palette) +// ============================================================================ + +const monetColors = { + bgCream: '#FAF8F5', + bgPaleBlue: '#F0F4F8', + bgWarmWhite: '#FFFEF9', + textPrimary: '#3D3D3D', + textSecondary: '#6B6B6B', + amber: '#D4A574', + amberLight: '#E8D4BC', + sunlightGoldLight: '#EDE6D8', +} + +const darkColors = { + textPrimary: '#f1f5f9', + textSecondary: '#94a3b8', + primary: '#fbbf24', + accent: '#f59e0b', +} + +const canvasTexture = `url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")` + +// ─── Embedded view (Dashboard takeover) ────────────── + +export function VehiclePlannerView({ onClose }: { onClose?: () => void }) { + const activeTab = useVehicleActiveTab() + const activeScenario = useActiveVehicleScenario() + const scenarios = useVehiclePlannerStore((s) => s.scenarios) + const { setActiveTab, addScenario } = useVehiclePlannerActions() + const colorScheme = useColorScheme() + const isMonet = colorScheme === 'monet' + const colors = isMonet ? monetColors : darkColors + + // Auto-create first scenario if none exist + useEffect(() => { + if (scenarios.length === 0) { + addScenario('My Vehicle') + } + }, [scenarios.length, addScenario]) + + return ( +
Loading...
+Loading...
+Analyze coverage gaps and plan your protection.
- {/* Coming Soon Modules */} -Coming soon
-No scenario selected.
++ For used vehicles, the registration cost breakdown is not applicable — the purchase price + is the all-in cost. Check the Depreciation and Total Cost tabs for ownership analysis. +
++ {calculation.vesAmount === 0 + ? 'Neutral' + : calculation.vesAmount < 0 + ? `(${formatCurrency(Math.abs(calculation.vesAmount))}) rebate` + : `${formatCurrency(calculation.vesAmount)} surcharge` + } +
++ CO₂: {inputs.co2EmissionsGkm ?? 0} g/km · {formatFuelType(inputs.fuelType)} +
++ ({formatCurrency(Math.abs(calculation.eeaiRebate))}) rebate +
++ 45% of ARF, capped per VES period +
+ > + ) : ( ++ {inputs.fuelType === 'electric' ? 'Not available for this VES period' : 'EV only incentive'} +
+ )} +No scenario selected.
++ Optimal Deregistration: Year {optimalYear.year} +
++ Market value {formatCurrency(optimalYear.marketValue)} vs scrap value {formatCurrency(optimalYear.scrapValue)}. + The gap is smallest at year {optimalYear.year}, making it the most cost-efficient time to deregister. +
+| Year | +Market Value | +PARF Rebate | +COE Rebate | +Scrap Value | +Annual Dep. | +Cumulative Dep. | +
|---|---|---|---|---|---|---|
| 0 | +{formatCurrency(startingValue)} | +— | +— | +— | +— | +— | +
| + {entry.year} {isOptimal && '★'} + | +{formatCurrency(entry.marketValue)} | ++ {entry.parfRebate > 0 ? formatCurrency(entry.parfRebate) : —} + | ++ {entry.coeRebate > 0 ? formatCurrency(entry.coeRebate) : —} + | ++ {formatCurrency(entry.scrapValue)} + | ++ ({formatCurrency(entry.annualDepreciation)}) + | ++ ({formatCurrency(entry.cumulativeDepreciation)}) + | +
+ {formatVehicleCategoryShort(scenario.inputs.vehicleCategory)} · {formatFuelType(scenario.inputs.fuelType)} · {isNew ? 'New' : 'Used'} +
+| Metric | + {scenarioCalculations.map(({ scenario }) => ( ++ {scenario.name} + | + ))} +
|---|
No scenario selected.
+Net Total Cost
++ {formatCurrency(tco.netTotalCost)} +
+Per Month
++ {formatCurrency(tco.costPerMonth)} +
+Per Year
++ {formatCurrency(tco.costPerYear)} +
++ After deducting residual (scrap) value of {formatCurrency(tco.residualValue)} at year {ownershipYears} +
+No scenario selected.
++ Max LTV: {Math.round(calculation.maxLtvPercent * 100)}% ({formatCurrency(calculation.maxLoanAllowed)}) +
+ )} +