From 41d2fa4f08ca5c6da1385d19508ca1d4630efe77 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Mon, 4 Aug 2025 05:03:15 +0530 Subject: [PATCH 1/3] Refactoring: Updated UI and components --- Dockerfile | 1 + app/client/package.json | 1 + .../src/components/auth/PinInput.svelte | 23 +- .../src/components/common/FormField.svelte | 35 ++- .../components/common/FormSubmitButton.svelte | 20 ++ .../components/common/ModalContainer.svelte | 39 +++ .../src/components/common/ThemeToggle.svelte | 15 +- .../src/components/fuel/FuelLogForm.svelte | 149 +++++++++++ ...elRefillList.svelte => FuelLogList.svelte} | 50 ++-- .../src/components/fuel/FuelLogModal.svelte | 17 ++ .../src/components/fuel/FuelRefillForm.svelte | 112 -------- .../fuel/FuelRefillFormComponent.svelte | 84 ------ .../insurance/InsuranceDetails.svelte | 11 +- .../maintenance/MaintenanceLogForm.svelte | 59 ++--- .../maintenance/MaintenanceLogList.svelte | 13 +- .../pucc/PollutionCertificateDetails.svelte | 10 +- .../components/vehicle/AddVehicleForm.svelte | 71 ----- .../src/components/vehicle/VehicleCard.svelte | 66 ++--- .../src/components/vehicle/VehicleForm.svelte | 118 +++++++-- .../src/components/vehicle/VehicleList.svelte | 29 +- .../components/vehicle/VehicleModal.svelte | 16 ++ app/client/src/lib/models/vehicle.ts | 16 ++ app/client/src/lib/states/config.ts | 26 -- app/client/src/lib/states/dark-mode.ts | 25 ++ app/client/src/lib/utils/dev.ts | 5 + app/client/src/lib/utils/formatting.ts | 97 +++++++ app/client/src/routes/+layout.svelte | 10 +- app/client/src/routes/+page.svelte | 4 +- app/client/src/routes/config/+page.svelte | 34 ++- app/client/src/routes/dashboard/+page.svelte | 250 +++++------------- app/client/src/routes/login/+page.svelte | 34 ++- app/client/src/styles/app.css | 5 + app/client/svelte.config.js | 6 +- app/server/seed.ts | 6 +- .../src/controllers/VehicleController.ts | 9 +- app/server/src/models/Vehicle.ts | 138 +++++----- app/server/undefined | Bin 102400 -> 102400 bytes package-lock.json | 7 + 38 files changed, 845 insertions(+), 766 deletions(-) create mode 100644 app/client/src/components/common/FormSubmitButton.svelte create mode 100644 app/client/src/components/common/ModalContainer.svelte create mode 100644 app/client/src/components/fuel/FuelLogForm.svelte rename app/client/src/components/fuel/{FuelRefillList.svelte => FuelLogList.svelte} (77%) create mode 100644 app/client/src/components/fuel/FuelLogModal.svelte delete mode 100644 app/client/src/components/fuel/FuelRefillForm.svelte delete mode 100644 app/client/src/components/fuel/FuelRefillFormComponent.svelte delete mode 100644 app/client/src/components/vehicle/AddVehicleForm.svelte create mode 100644 app/client/src/components/vehicle/VehicleModal.svelte create mode 100644 app/client/src/lib/models/vehicle.ts create mode 100644 app/client/src/lib/states/dark-mode.ts create mode 100644 app/client/src/lib/utils/dev.ts create mode 100644 app/client/src/lib/utils/formatting.ts diff --git a/Dockerfile b/Dockerfile index 91e52cb7..974635eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ COPY app/client/package*.json ./ RUN npm install COPY app/client/ ./ ENV PUBLIC_API_BASE_URL=/ +ENV PUBLIC_DEMO_MODE=false ENV NODE_ENV=production RUN npm run build diff --git a/app/client/package.json b/app/client/package.json index 54785b13..5fb3e570 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -34,6 +34,7 @@ "chart.js": "^4.5.0", "dayjs": "^1.11.13", "dot-env": "^0.0.1", + "svelte-loading-spinners": "^0.3.6", "svelte5-chartjs": "^1.0.0" } } diff --git a/app/client/src/components/auth/PinInput.svelte b/app/client/src/components/auth/PinInput.svelte index 6fdd6e93..c93a4d11 100644 --- a/app/client/src/components/auth/PinInput.svelte +++ b/app/client/src/components/auth/PinInput.svelte @@ -1,10 +1,8 @@
@@ -22,14 +25,20 @@ {required} aria-label={ariaLabel} {disabled} - on:input={onInput} + oninput={onInput} + autocomplete="off" /> {#if icon} -
+ + diff --git a/app/client/src/components/common/FormSubmitButton.svelte b/app/client/src/components/common/FormSubmitButton.svelte new file mode 100644 index 00000000..64d7607e --- /dev/null +++ b/app/client/src/components/common/FormSubmitButton.svelte @@ -0,0 +1,20 @@ + + +
+ {#if !loading} + + {:else} + + {/if} +
diff --git a/app/client/src/components/common/ModalContainer.svelte b/app/client/src/components/common/ModalContainer.svelte new file mode 100644 index 00000000..4cb6e68c --- /dev/null +++ b/app/client/src/components/common/ModalContainer.svelte @@ -0,0 +1,39 @@ + + +
+
+ +

+ {title} +

+
+ {@render children()} +
+
diff --git a/app/client/src/components/common/ThemeToggle.svelte b/app/client/src/components/common/ThemeToggle.svelte index 248227c0..71367242 100644 --- a/app/client/src/components/common/ThemeToggle.svelte +++ b/app/client/src/components/common/ThemeToggle.svelte @@ -19,7 +19,7 @@ -

- Log Fuel Refill -

-
- - - -{/if} diff --git a/app/client/src/components/fuel/FuelRefillFormComponent.svelte b/app/client/src/components/fuel/FuelRefillFormComponent.svelte deleted file mode 100644 index 2b45640c..00000000 --- a/app/client/src/components/fuel/FuelRefillFormComponent.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - -
- - - - - - {#if error} - - {/if} -
- -
- diff --git a/app/client/src/components/insurance/InsuranceDetails.svelte b/app/client/src/components/insurance/InsuranceDetails.svelte index 5891e44f..f88e850f 100644 --- a/app/client/src/components/insurance/InsuranceDetails.svelte +++ b/app/client/src/components/insurance/InsuranceDetails.svelte @@ -4,13 +4,8 @@ import { env } from '$env/dynamic/public'; import { Shield, Calendar, Hash, DollarSign, Pencil, Trash2 } from '@lucide/svelte'; import InsuranceForm from './InsuranceForm.svelte'; - import { config } from '../../lib/states/config'; - import dayjs from 'dayjs'; - $: formatDate = (date: string) => dayjs(date).format($config.dateFormat); - $: formatCurrency = (amount: number) => `${$config.currency} ${amount.toLocaleString()}`; - - export let vehicleId: number | null = null; + export let vehicleId: string | null = null; interface InsuranceDetails { id: number; @@ -35,7 +30,7 @@ error = ''; try { const response = await fetch( - `${env.PUBLIC_API_BASE_URL||""}/api/vehicles/${vehicleId}/insurance`, + `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/insurance`, { headers: { 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' @@ -61,7 +56,7 @@ } try { const response = await fetch( - `${env.PUBLIC_API_BASE_URL||""}/api/vehicles/${vehicleId}/insurance`, + `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/insurance`, { method: 'DELETE', headers: { diff --git a/app/client/src/components/maintenance/MaintenanceLogForm.svelte b/app/client/src/components/maintenance/MaintenanceLogForm.svelte index 1155a5db..67c2d260 100644 --- a/app/client/src/components/maintenance/MaintenanceLogForm.svelte +++ b/app/client/src/components/maintenance/MaintenanceLogForm.svelte @@ -1,19 +1,9 @@ {#if showModal} -
-
-

- {initialData ? 'Edit Maintenance Log' : 'Add Maintenance Log'} -

- - -
-
+ + + {/if} diff --git a/app/client/src/components/maintenance/MaintenanceLogList.svelte b/app/client/src/components/maintenance/MaintenanceLogList.svelte index bba3b149..e1870163 100644 --- a/app/client/src/components/maintenance/MaintenanceLogList.svelte +++ b/app/client/src/components/maintenance/MaintenanceLogList.svelte @@ -3,13 +3,8 @@ import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; import MaintenanceLogForm from './MaintenanceLogForm.svelte'; - import { config } from '../../lib/states/config'; - import dayjs from 'dayjs'; - $: formatDate = (date: string) => dayjs(date).format($config.dateFormat); - $: formatCurrency = (amount: number) => `${$config.currency} ${amount.toLocaleString()}` - - export let vehicleId: number | null = null; + export let vehicleId: string | null = null; interface MaintenanceLogEntry { id: number; @@ -35,7 +30,7 @@ error = ''; try { const response = await fetch( - `${env.PUBLIC_API_BASE_URL||""}/api/vehicles/${vehicleId}/maintenance-logs`, + `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/maintenance-logs`, { headers: { 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' @@ -143,7 +138,9 @@ > {log.odometer} {log.service} - {formatCurrency(log.cost)} + {formatCurrency(log.cost)} {log.notes || '-'} -

- {editMode ? 'Edit Vehicle Details' : 'Add a New Vehicle'} -

-
- - - -{/if} - - diff --git a/app/client/src/components/vehicle/VehicleCard.svelte b/app/client/src/components/vehicle/VehicleCard.svelte index cd284b92..21f829ca 100644 --- a/app/client/src/components/vehicle/VehicleCard.svelte +++ b/app/client/src/components/vehicle/VehicleCard.svelte @@ -12,22 +12,9 @@ Shield, BadgeCheck } from '@lucide/svelte'; - import { createEventDispatcher } from 'svelte'; + import { formatDistance } from '$lib/utils/formatting'; - export let vehicle: { - id: number; - make: string; - model: string; - year: number; - licensePlate: string; - vin?: string; - color?: string; - odometer?: number; - insuranceStatus?: string; - puccStatus?: string; - }; - - const dispatch = createEventDispatcher(); + const { vehicle, onEditVehicle, onDeleteVehicle, onRefillFuel, onAddMaintenance } = $props();
{vehicle.licensePlate}

- {#if vehicle.vin} -

- VIN: - {vehicle.vin} -

- {/if} - {#if vehicle.color} -

- Color: - {vehicle.color} -

- {/if} - {#if vehicle.odometer} -

- - Odometer: - {vehicle.odometer} km -

- {/if} +

+ VIN: + {vehicle.vin ? vehicle.vin : '-'} +

+ +

+ Color: + {vehicle.color ? vehicle.color : '-'} +

+

+ + Odometer: + {vehicle.odometer ? formatDistance(vehicle.odometer) : '-'} +

{#if vehicle.insuranceStatus}

@@ -98,7 +80,7 @@ -

+ + + {#if editMode} + + {/if} -{#if error} -

{error}

-{/if} -{#if success} -

- {editMode ? 'Vehicle updated successfully!' : success} +{#if status.message} +

+ {#if status.type === 'ERROR'} + Error: {status.message} + {:else} + {status.message} + {/if}

{/if} diff --git a/app/client/src/components/vehicle/VehicleList.svelte b/app/client/src/components/vehicle/VehicleList.svelte index f67dad39..740184dc 100644 --- a/app/client/src/components/vehicle/VehicleList.svelte +++ b/app/client/src/components/vehicle/VehicleList.svelte @@ -1,17 +1,18 @@ @@ -30,13 +31,7 @@ class:ring-blue-500={selectedVehicleId === vehicle.id} class="cursor-pointer rounded-2xl transition-all duration-300 ease-in-out" > - dispatch('editVehicle', e.detail)} - on:deleteVehicle={(e) => dispatch('deleteVehicle', e.detail)} - on:refillFuel={(e) => dispatch('refillFuel', e.detail)} - on:addMaintenance={(e) => dispatch('addMaintenance', e.detail)} - /> + {/each} diff --git a/app/client/src/components/vehicle/VehicleModal.svelte b/app/client/src/components/vehicle/VehicleModal.svelte new file mode 100644 index 00000000..4fa278cd --- /dev/null +++ b/app/client/src/components/vehicle/VehicleModal.svelte @@ -0,0 +1,16 @@ + + +{#if showModal} + + + +{/if} diff --git a/app/client/src/lib/models/vehicle.ts b/app/client/src/lib/models/vehicle.ts new file mode 100644 index 00000000..083237ef --- /dev/null +++ b/app/client/src/lib/models/vehicle.ts @@ -0,0 +1,16 @@ +export interface NewVehicle { + make: string; + model: string; + year: number | null; + licensePlate: string; + vin?: string; + color?: string; + odometer?: number | null; +} + +export interface Vehicle extends NewVehicle { + vehicleType: string; + id: string; + insuranceStatus?: string; + puccStatus?: string; +} diff --git a/app/client/src/lib/states/config.ts b/app/client/src/lib/states/config.ts index 0afd655d..39d0bc80 100644 --- a/app/client/src/lib/states/config.ts +++ b/app/client/src/lib/states/config.ts @@ -8,26 +8,8 @@ export interface Config { description?: string; } -export interface ConfigStore { - dateFormat: string; - currency: string; - theme: string; - language: string; -} - -const defaultConfig: ConfigStore = { - dateFormat: 'DD/MM/YYYY', - currency: 'USD', - theme: 'light', - language: 'en' -}; - -// const defaultConfig: ConfigStore = []; - const createConfigStore = () => { const { subscribe, set } = writable([]); - const configJson = writable(defaultConfig); - async function fetchConfig() { if (browser) { try { @@ -38,13 +20,6 @@ const createConfigStore = () => { }); if (response.ok) { const data: Config[] = await response.json(); - const newConfig: any = {}; - data.forEach((item) => { - if (item.key && item.value !== undefined) { - newConfig[item.key] = item.value; - } - }); - configJson.set(newConfig); set(data); } else { console.error('Failed to fetch config'); @@ -81,7 +56,6 @@ const createConfigStore = () => { return { subscribe, - configJson, save: (localConfig: Config[]) => { console.log('Saving config:', localConfig); updateConfig(localConfig); diff --git a/app/client/src/lib/states/dark-mode.ts b/app/client/src/lib/states/dark-mode.ts new file mode 100644 index 00000000..087d3372 --- /dev/null +++ b/app/client/src/lib/states/dark-mode.ts @@ -0,0 +1,25 @@ +import { writable } from 'svelte/store'; + +const createDarkModeStore = () => { + const { subscribe, set, update } = writable(false); + + function loadDarkModePreference() { + if (typeof window !== 'undefined') { + const darkMode = localStorage.getItem('darkMode'); + if (darkMode !== null) { + set(darkMode === 'true'); + } else { + set(false); // Default to light mode if no preference is set + } + } + } + + return { + subscribe, + toggle: () => update((current) => !current), + enable: () => set(true), + disable: () => set(false) + }; +}; + +export const darkModeStore = createDarkModeStore(); diff --git a/app/client/src/lib/utils/dev.ts b/app/client/src/lib/utils/dev.ts new file mode 100644 index 00000000..fe8c71d2 --- /dev/null +++ b/app/client/src/lib/utils/dev.ts @@ -0,0 +1,5 @@ +const simulateNetworkDelay = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +export { simulateNetworkDelay }; diff --git a/app/client/src/lib/utils/formatting.ts b/app/client/src/lib/utils/formatting.ts new file mode 100644 index 00000000..5a18dd34 --- /dev/null +++ b/app/client/src/lib/utils/formatting.ts @@ -0,0 +1,97 @@ +import dayjs from 'dayjs'; +import { config, type Config } from '../states/config'; + +export interface ConfigStore { + dateFormat: string; + currency: string; + unitOfMeasure?: string; +} + +const configs: ConfigStore = { + dateFormat: 'DD/MM/YYYY', + currency: 'USD', + unitOfMeasure: 'metric' +}; + +config.subscribe((value) => { + if (value && value.length > 0) { + value.forEach((item) => { + if (item.key === 'dateFormat') { + configs.dateFormat = item.value || configs.dateFormat; + } else if (item.key === 'currency') { + configs.currency = item.value || configs.currency; + } else if (item.key === 'unitOfMeasure') { + configs.unitOfMeasure = item.value || configs.unitOfMeasure; + } + }); + } +}); + +const formatDate = (date: Date | string): string => { + return dayjs(date).format(configs.dateFormat); +}; + +const getCurrencySymbol = (): string => { + return ( + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: configs.currency + }) + .formatToParts(0) + .find((part) => part.type === 'currency')?.value || '' + ); +}; + +const formatCurrency = (amount: number): string => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: configs.currency + }).format(amount); +}; + +const formatDistance = (distance: number): string => { + if (configs.unitOfMeasure === 'metric') { + return `${distance} km`; + } else if (configs.unitOfMeasure === 'imperial') { + return `${distance} mi`; + } + return `${distance}`; +}; + +const formatVolume = (volume: number): string => { + if (configs.unitOfMeasure === 'metric') { + return `${volume} l`; + } else if (configs.unitOfMeasure === 'imperial') { + return `${volume} gal`; + } + return `${volume}`; +}; + +const getMileageUnit = (): string => { + if (configs.unitOfMeasure === 'metric') { + return 'kmpl'; + } + if (configs.unitOfMeasure === 'imperial') { + return 'mpg'; + } + return ''; +}; + +const formatMileage = (mileage: number): string => { + if (configs.unitOfMeasure === 'metric') { + return `${mileage} kmpl`; + } else if (configs.unitOfMeasure === 'imperial') { + return `${mileage} mpg`; + } + return `${mileage}`; +}; + +export { + formatDate, + getCurrencySymbol, + formatCurrency, + formatDistance, + formatVolume, + getMileageUnit, + formatMileage +}; diff --git a/app/client/src/routes/+layout.svelte b/app/client/src/routes/+layout.svelte index 7a00e4b0..b8fb0c40 100644 --- a/app/client/src/routes/+layout.svelte +++ b/app/client/src/routes/+layout.svelte @@ -5,7 +5,7 @@ import '../styles/app.css'; import { tick } from 'svelte'; import { Car, LogOut, Tractor, Settings } from '@lucide/svelte'; - import ThemeToggle from '../components/common/ThemeToggle.svelte'; + import ThemeToggle from '$components/common/ThemeToggle.svelte'; import { onMount } from 'svelte'; import { env } from '$env/dynamic/public'; @@ -54,7 +54,7 @@ >🚜 Demo Mode: This is a demo instance. Data will be reset periodically and is not saved permanently. Please avoid adding any persoanl info. -
+
Default PIN : 123456 {/if} @@ -76,7 +76,10 @@
- +
@@ -85,7 +88,6 @@ class="flex items-center gap-1 text-gray-600 transition-colors duration-300 hover:text-red-500 dark:text-gray-300" > - Logout
diff --git a/app/client/src/routes/+page.svelte b/app/client/src/routes/+page.svelte index 5e7f5a3a..bae4370a 100644 --- a/app/client/src/routes/+page.svelte +++ b/app/client/src/routes/+page.svelte @@ -1,6 +1,7 @@ -
+
+

Redirecting...

diff --git a/app/client/src/routes/config/+page.svelte b/app/client/src/routes/config/+page.svelte index 51477d29..a65b826c 100644 --- a/app/client/src/routes/config/+page.svelte +++ b/app/client/src/routes/config/+page.svelte @@ -5,9 +5,10 @@ let saveSuccess = false; const dateFormatOptions = [ - { value: 'DD/MM/YYYY', label: 'DD/MM/YYYY (e.g., 25/12/2024)' }, - { value: 'MM/DD/YYYY', label: 'MM/DD/YYYY (e.g., 12/25/2024)' }, - { value: 'YYYY-MM-DD', label: 'YYYY-MM-DD (e.g., 2024-12-25)' } + { value: 'DD/MM/YYYY', label: 'DD/MM/YYYY (e.g., 31/12/2000)' }, + { value: 'MM/DD/YYYY', label: 'MM/DD/YYYY (e.g., 12/25/2000)' }, + { value: 'YYYY-MM-DD', label: 'YYYY-MM-DD (e.g., 2000-12-31)' }, + { value: 'DD MMM, YYYY', label: 'DD MMM, YYYY (e.g., 31 Dec, 2000)' } ]; const currencyOptions = [ @@ -17,6 +18,11 @@ { value: 'GBP', label: 'GBP (£)' } ]; + const uomOptions = [ + { value: 'metric', label: 'Metric' }, + { value: 'Imperial', label: 'Imperial' } + ]; + config.subscribe((value) => { localConfig = JSON.parse(JSON.stringify(value)); }); @@ -34,9 +40,7 @@

Application Settings

-

- Customize your app experience. -

+

Customize your app experience.

@@ -56,7 +60,7 @@ {#each currencyOptions as option} {/each} + {:else if item.key === 'unitOfMeasure'} + {:else} {/if}
@@ -101,4 +115,4 @@
{/if} - \ No newline at end of file + diff --git a/app/client/src/routes/dashboard/+page.svelte b/app/client/src/routes/dashboard/+page.svelte index 65121641..87d75705 100644 --- a/app/client/src/routes/dashboard/+page.svelte +++ b/app/client/src/routes/dashboard/+page.svelte @@ -3,81 +3,56 @@ import { Line } from 'svelte5-chartjs'; import { Chart, registerables } from 'chart.js'; import { Plus } from '@lucide/svelte'; - import { browser } from '$app/environment'; - import { derived } from 'svelte/store'; import { env } from '$env/dynamic/public'; - import ChartCard from '../../components/chart/ChartCard.svelte'; - import FuelRefillForm from '../../components/fuel/FuelRefillForm.svelte'; - import FuelRefillList from '../../components/fuel/FuelRefillList.svelte'; - import InsuranceDetails from '../../components/insurance/InsuranceDetails.svelte'; - import MaintenanceLogForm from '../../components/maintenance/MaintenanceLogForm.svelte'; - import MaintenanceLogList from '../../components/maintenance/MaintenanceLogList.svelte'; - import PollutionCertificateDetails from '../../components/pucc/PollutionCertificateDetails.svelte'; - import AddVehicleForm from '../../components/vehicle/AddVehicleForm.svelte'; - import VehicleList from '../../components/vehicle/VehicleList.svelte'; - import { config } from '../../lib/states/config'; + import ChartCard from '$components/chart/ChartCard.svelte'; + import FuelLogModal from '$components/fuel/FuelLogModal.svelte'; + import FuelLogList from '$components/fuel/FuelLogList.svelte'; + import InsuranceDetails from '$components/insurance/InsuranceDetails.svelte'; + import MaintenanceLogForm from '$components/maintenance/MaintenanceLogForm.svelte'; + import MaintenanceLogList from '$components/maintenance/MaintenanceLogList.svelte'; + import PollutionCertificateDetails from '$components/pucc/PollutionCertificateDetails.svelte'; + import VehicleModal from '$components/vehicle/VehicleModal.svelte'; + import VehicleList from '$components/vehicle/VehicleList.svelte'; + import { config } from '$lib/states/config'; import dayjs from 'dayjs'; + import type { NewVehicle, Vehicle } from '$lib/models/vehicle'; + import { darkModeStore } from '$lib/states/dark-mode'; + import { formatDate, getCurrencySymbol, getMileageUnit } from '$lib/utils/formatting'; + import { Jumper } from 'svelte-loading-spinners'; + import { simulateNetworkDelay } from '$lib/utils/dev'; - Chart.register(...registerables); - - interface Vehicle { - id: number; - make: string; - model: string; - year: number; - licensePlate: string; - vin?: string; - color?: string; - odometer?: number; - insuranceStatus?: string; - puccStatus?: string; - } + $effect(() => { + Chart.register(...registerables); + }); let vehicles = $state([]); let loading = $state(true); let error = $state(''); - let newVehicle = $state({ - vehicleType: 'car', - make: '', - model: '', - year: null, - licensePlate: '', - vin: '', - color: '', - odometer: null - }); - - let addVehicleError = $state(''); - let addVehicleSuccess = $state(''); - - let showAddVehicleModal = $state(false); + let showVehicleModal = $state(false); + let vehicleToEdit = $state(null); - let editVehicle = $state(null); - let showEditVehicleModal = $state(false); - - let selectedVehicleId = $state(null); - - function handleVehicleSelect(event: CustomEvent<{ vehicleId: number }>) { - selectedVehicleId = event.detail.vehicleId; - fetchChartData(selectedVehicleId); - } + let selectedVehicleId = $state(null); let fuelCostData: any = $state({}); let mileageData: any = $state({}); let showFuelRefillModal = $state(false); let showMaintenanceLogModal = $state(false); - let activeTab = $state('dashboard'); // 'dashboard', 'fuel', 'maintenance', 'insurance', 'pollution' + let activeTab = $state('dashboard'); // Dark mode chart options - let isDarkMode = false; - if (browser) { - isDarkMode = document.documentElement.classList.contains('dark'); - } - + let isDarkMode = $state(false); + darkModeStore.subscribe((isDarkMode) => { + isDarkMode = isDarkMode; + }); let chartOptions = $derived({}); + function handleVehicleSelect(vehicleId: string) { + selectedVehicleId = vehicleId; + fetchChartData(selectedVehicleId); + } + $effect(() => { chartOptions = { responsive: true, @@ -101,8 +76,10 @@ } }; }); + async function fetchVehicles() { loading = true; + // await simulateNetworkDelay(2000); // Simulate network delay for development error = ''; try { const response = await fetch(`${env.PUBLIC_API_BASE_URL || ''}/api/vehicles`, { @@ -124,88 +101,22 @@ loading = false; } - async function addVehicle() { - addVehicleError = ''; - addVehicleSuccess = ''; - try { - const response = await fetch(`${env.PUBLIC_API_BASE_URL || ''}/api/vehicles`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-User-PIN': localStorage.getItem('userPin') || '' - }, - body: JSON.stringify(newVehicle) - }); - - if (response.ok) { - addVehicleSuccess = 'Vehicle added successfully!'; - newVehicle = { - make: '', - model: '', - year: null, - licensePlate: '', - vin: '', - color: '', - odometer: null - }; - fetchVehicles(); - showAddVehicleModal = false; - } else { - const data = await response.json(); - addVehicleError = data.message || 'Failed to add vehicle.'; - } - } catch (e) { - addVehicleError = 'Failed to connect to the server.'; - } - } - function openAddVehicleModal() { - addVehicleError = ''; - addVehicleSuccess = ''; - showAddVehicleModal = true; + showVehicleModal = true; } - function handleEditVehicle(event: CustomEvent<{ vehicle: Vehicle }>) { - editVehicle = { ...event.detail.vehicle }; - showEditVehicleModal = true; + function handleEditVehicle(vehicle: Vehicle) { + vehicleToEdit = vehicle; + showVehicleModal = true; } - async function updateVehicle() { - addVehicleError = ''; - addVehicleSuccess = ''; - try { - if (!editVehicle) return; - const response = await fetch(`http://localhost:3000/api/vehicles/${editVehicle.id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'X-User-PIN': localStorage.getItem('userPin') || '' - }, - body: JSON.stringify(editVehicle) - }); - if (response.ok) { - addVehicleSuccess = 'Vehicle updated successfully!'; - showEditVehicleModal = false; - editVehicle = null; - await fetchVehicles(); - } else { - const data = await response.json(); - addVehicleError = data.message || 'Failed to update vehicle.'; - } - } catch (e) { - addVehicleError = 'Failed to connect to the server.'; - } - } - - function handleDeleteVehicle(event: CustomEvent<{ vehicle: Vehicle }>) { + function handleDeleteVehicle(vehicle: Vehicle) { if (confirm('Are you sure you want to delete this vehicle?')) { - deleteVehicle(event.detail.vehicle.id); + deleteVehicle(vehicle.id); } } - async function deleteVehicle(vehicleId: number) { - addVehicleError = ''; - addVehicleSuccess = ''; + async function deleteVehicle(vehicleId: string) { try { const response = await fetch(`http://localhost:3000/api/vehicles/${vehicleId}`, { method: 'DELETE', @@ -214,29 +125,29 @@ } }); if (response.ok) { - addVehicleSuccess = 'Vehicle deleted successfully!'; await fetchVehicles(); - if (selectedVehicleId === vehicleId) { - selectedVehicleId = vehicles.length > 0 ? vehicles[0].id : null; + if (vehicles.length > 0) { + selectedVehicleId = vehicles[0].id; + fetchChartData(selectedVehicleId); } } else { const data = await response.json(); - addVehicleError = data.message || 'Failed to delete vehicle.'; + alert(data.message || 'Failed to delete vehicle.'); } } catch (e) { - addVehicleError = 'Failed to connect to the server.'; + alert('Failed to connect to the server.'); } } - onMount(() => { - fetchVehicles(); + onMount(async () => { + await fetchVehicles(); if (vehicles.length > 0) { selectedVehicleId = vehicles[0].id; fetchChartData(selectedVehicleId); } }); - async function fetchChartData(vehicleId: number) { + async function fetchChartData(vehicleId: string) { try { const response = await fetch( `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/fuel-logs`, @@ -248,7 +159,7 @@ ); if (response.ok) { const data = await response.json(); - const labels = data.map((log: any) => dayjs(log.date).format($config.dateFormat)); + const labels = data.map((log: any) => formatDate(log.date)); const costData = data.map((log: any) => log.cost); const mileageDataPoints = data.map((log: any) => log.mileage); @@ -256,9 +167,9 @@ labels, datasets: [ { - label: 'Fuel Cost', + label: `Fuel Cost (${getCurrencySymbol()})`, data: costData, - fill: false, + fill: true, borderColor: 'rgb(75, 192, 192)', tension: 0.1 } @@ -269,9 +180,9 @@ labels, datasets: [ { - label: 'Mileage (km/L)', + label: `Mileage (${getMileageUnit()})`, data: mileageDataPoints, - fill: false, + fill: true, borderColor: 'rgb(255, 99, 132)', tension: 0.1 } @@ -299,45 +210,31 @@ {#if loading} -

Loading vehicles...

+

+ + Loading Vehicles... +

{:else if error}

Error: {error}

{:else} { - selectedVehicleId = e.detail.vehicle.id; + onVehicleSelect={handleVehicleSelect} + onEditVehicle={handleEditVehicle} + onDeleteVehicle={handleDeleteVehicle} + onRefillFuel={(vehicle: Vehicle) => { + selectedVehicleId = vehicle.id; showFuelRefillModal = true; }} - on:addMaintenance={(e) => { - selectedVehicleId = e.detail.vehicle.id; + onAddMaintenance={(vehicle: Vehicle) => { + selectedVehicleId = vehicle.id; showMaintenanceLogModal = true; }} /> {/if} - - - {#if showEditVehicleModal} - - {/if} + {#if selectedVehicleId}
@@ -489,7 +386,7 @@ role="tabpanel" aria-labelledby="fuel-logs-tab" > - +
{:else if activeTab === 'maintenance'}
- (showFuelRefillModal = false)} - on:success={() => { - if (selectedVehicleId) fetchChartData(selectedVehicleId); - showFuelRefillModal = false; - }} - /> + (showMaintenanceLogModal = false)} - on:success={() => { + onSuccess={() => { showMaintenanceLogModal = false; }} /> - {:else} + {:else if vehicles.length > 0 && !loading}

Select a vehicle to view fuel and mileage data. diff --git a/app/client/src/routes/login/+page.svelte b/app/client/src/routes/login/+page.svelte index f7ebfca7..71c61aef 100644 --- a/app/client/src/routes/login/+page.svelte +++ b/app/client/src/routes/login/+page.svelte @@ -1,9 +1,12 @@

{#if icon} + const { title, children } = $props(); + + +
+

+ {title} +

+ {@render children()} +
diff --git a/app/client/src/components/common/ThemeToggle.svelte b/app/client/src/components/common/ThemeToggle.svelte index 71367242..0a77f26c 100644 --- a/app/client/src/components/common/ThemeToggle.svelte +++ b/app/client/src/components/common/ThemeToggle.svelte @@ -1,5 +1,6 @@ diff --git a/app/client/src/components/fuel/FuelLogForm.svelte b/app/client/src/components/fuel/FuelLogForm.svelte index fa0a3973..dc16bfe7 100644 --- a/app/client/src/components/fuel/FuelLogForm.svelte +++ b/app/client/src/components/fuel/FuelLogForm.svelte @@ -2,11 +2,18 @@ import FormSubmitButton from '$components/common/FormSubmitButton.svelte'; import { env } from '$env/dynamic/public'; import { simulateNetworkDelay } from '$lib/utils/dev'; - import { getCurrencySymbol } from '$lib/utils/formatting'; + import { formatDate, getCurrencySymbol } from '$lib/utils/formatting'; import FormField from '../common/FormField.svelte'; import { Calendar1, Gauge, Fuel, FileText, BadgeDollarSign } from '@lucide/svelte'; - let { vehicleId, modalVisibility = $bindable(), loading = false, editMode = false } = $props(); + let { + vehicleId, + logToEdit, + modalVisibility = $bindable(), + loading = false, + editMode = false, + callback + } = $props(); let refill = $state({ date: '', @@ -24,7 +31,7 @@ type: null }); - async function handleSubmit() { + async function persistLog() { status.message = ''; if (!vehicleId) { status.message = 'No vehicle selected.'; @@ -41,11 +48,11 @@ loading = true; status.message = null; status.type = null; - await simulateNetworkDelay(2000); // Simulate network delay for development + // await simulateNetworkDelay(2000); // Simulate network delay for development const response = await fetch( - `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/fuel-logs`, + `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/fuel-logs/${editMode ? logToEdit.id : ''}`, { - method: 'POST', + method: `${editMode ? 'PUT' : 'POST'}`, headers: { 'Content-Type': 'application/json', 'X-User-PIN': localStorage.getItem('userPin') || '' @@ -81,12 +88,16 @@ loading = false; if (status.type === 'SUCCESS') { modalVisibility = false; // Close modal on success + callback(true); } } } + $effect(() => { + Object.assign(refill, logToEdit); + }); -
+ import { onMount } from 'svelte'; import { env } from '$env/dynamic/public'; + import { Pencil, Trash2 } from '@lucide/svelte'; import { formatCurrency, formatDate, formatVolume, formatMileage, formatDistance - } from '../../lib/utils/formatting'; + } from '$lib/utils/formatting'; + + import { fuelLogModelStore } from '$lib/stores/fuel-log'; const { vehicleId } = $props(); @@ -59,59 +62,104 @@ loading = false; } + async function deleteFuelLog(logId: number) { + if (!confirm('Are you sure you want to delete this Fuel log?')) { + return; + } + try { + const response = await fetch( + `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/fuel-logs/${logId}`, + { + method: 'DELETE', + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + } + ); + if (response.ok) { + fuelLogs = fuelLogs.filter((log) => log.id !== logId); + } else { + const data = await response.json(); + error = data.message || 'Failed to delete fuel log.'; + } + } catch (e) { + console.error('Failed to connect to the server.', e); + error = 'Failed to connect to the server.'; + } + } + onMount(() => { fetchFuelLogs(); }); -
-

Fuel Refill History

- - {#if loading} -

Loading fuel logs...

- {:else if error} -

Error: {error}

- {:else if fuelLogs.length === 0} -

No fuel refill logs found for this vehicle.

- {:else} -
- - - - - - Loading fuel logs...

+{:else if error} +

Error: {error}

+{:else if fuelLogs.length === 0} +

No fuel refill logs found for this vehicle.

+{:else} +
+
DateOdometerFuel Amount
+ + + + + + + + + + + + + {#each fuelLogs as log (log.id)} + + + - - {formatVolume(log.fuelAmount)} - {formatCurrency(log.cost)} + - - - - {#each fuelLogs as log (log.id)} - - - - {log.notes || '-'} + - + + - - {/each} - -
DateOdometerFuel AmountCostMileageNotesActions
{formatDate(log.date)}{formatDistance(log.odometer)}CostMileageNotes{log.mileage ? formatMileage(log.mileage) : '-'}
{formatDate(log.date)}{formatDistance(log.odometer)}{formatVolume(log.fuelAmount)} + {formatCurrency(log.cost)}{log.mileage ? formatMileage(log.mileage) : 'N/A'}{log.notes || 'N/A'}
-
- {/if} -
+ + + + + {/each} + + +
+{/if} diff --git a/app/client/src/components/fuel/FuelLogModal.svelte b/app/client/src/components/fuel/FuelLogModal.svelte index 40574887..e41d1b71 100644 --- a/app/client/src/components/fuel/FuelLogModal.svelte +++ b/app/client/src/components/fuel/FuelLogModal.svelte @@ -1,17 +1,42 @@ {#if showModal} - closeModal()} title="Log Fuel Refill" {loading}> - + closeModal()} + title={editMode ? 'Edit Fuel Log' : 'Log Fuel Refill'} + {loading} + > + {/if} diff --git a/app/client/src/components/insurance/InsuranceDetails.svelte b/app/client/src/components/insurance/InsuranceDetails.svelte deleted file mode 100644 index f88e850f..00000000 --- a/app/client/src/components/insurance/InsuranceDetails.svelte +++ /dev/null @@ -1,177 +0,0 @@ - - -
-

Insurance Details

- {#if loading} -
Loading insurance details...
- {:else if error} - - {:else if insurance} -
-
-
- - {insurance.provider} -
-
- - -
-
-
-
- - Policy Number: - {insurance.policyNumber} -
-
- - Cost: - {formatCurrency(insurance.cost)} -
-
- - Start Date: - {formatDate(insurance.startDate)} -
-
- - End Date: - {formatDate(insurance.endDate)} -
-
-
- {:else} -
-

No insurance details found for this vehicle.

- -
- {/if} - - {#if showInsuranceFormModal} - - {/if} -
diff --git a/app/client/src/components/insurance/InsuranceDetailsList.svelte b/app/client/src/components/insurance/InsuranceDetailsList.svelte new file mode 100644 index 00000000..f4b00493 --- /dev/null +++ b/app/client/src/components/insurance/InsuranceDetailsList.svelte @@ -0,0 +1,150 @@ + + +{#if loading} +
Loading insurance details...
+{:else if error} + +{:else if insurances.length === 0} +
No Insurance found for this vehicle.
+{:else} + {#each insurances as ins (ins.id)} +
+
+
+ + {ins.provider} +
+
+ + +
+
+
+
+ + Policy Number: + {ins.policyNumber} +
+
+ + Cost: + {formatCurrency(ins.cost)} +
+
+ + Start Date: + {formatDate(ins.startDate)} +
+
+ + End Date: + {formatDate(ins.endDate)} +
+
+
+ {/each} +{/if} diff --git a/app/client/src/components/insurance/InsuranceFormComponent.svelte b/app/client/src/components/insurance/InsuranceFormComponent.svelte index 41b1509e..e581b9bc 100644 --- a/app/client/src/components/insurance/InsuranceFormComponent.svelte +++ b/app/client/src/components/insurance/InsuranceFormComponent.svelte @@ -1,15 +1,15 @@ diff --git a/app/client/src/components/maintenance/MaintenanceLogForm.svelte b/app/client/src/components/maintenance/MaintenanceLogForm.svelte index 67c2d260..09ccda7e 100644 --- a/app/client/src/components/maintenance/MaintenanceLogForm.svelte +++ b/app/client/src/components/maintenance/MaintenanceLogForm.svelte @@ -1,87 +1,152 @@ -{#if showModal} - { + persistLog(); + e.preventDefault(); + }} + class="space-y-6" +> + + + + + + + +{#if status.message} +

- - + {#if status.type === 'ERROR'} + Error: {status.message} + {:else} + {status.message} + {/if} +

{/if} diff --git a/app/client/src/components/maintenance/MaintenanceLogFormComponent.svelte b/app/client/src/components/maintenance/MaintenanceLogFormComponent.svelte deleted file mode 100644 index 4647e3b8..00000000 --- a/app/client/src/components/maintenance/MaintenanceLogFormComponent.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - {#if error} - - {/if} - - {#if success} - - {/if} - -
- - -
-
diff --git a/app/client/src/components/maintenance/MaintenanceLogList.svelte b/app/client/src/components/maintenance/MaintenanceLogList.svelte index e1870163..fd3f0411 100644 --- a/app/client/src/components/maintenance/MaintenanceLogList.svelte +++ b/app/client/src/components/maintenance/MaintenanceLogList.svelte @@ -2,12 +2,14 @@ import { onMount } from 'svelte'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import MaintenanceLogForm from './MaintenanceLogForm.svelte'; + import { formatCurrency, formatDate } from '$lib/utils/formatting'; + import { Pencil, Trash2 } from '@lucide/svelte'; + import { maintenanceModelStore } from '$lib/stores/maintenance'; - export let vehicleId: string | null = null; + let { vehicleId } = $props(); - interface MaintenanceLogEntry { - id: number; + interface MaintenanceLog { + id: string; date: string; odometer: number; service: string; @@ -15,11 +17,18 @@ notes?: string; } - let maintenanceLogs: MaintenanceLogEntry[] = []; - let loading = false; - let error = ''; - let showEditModal = false; - let selectedLog: MaintenanceLogEntry | null = null; + let maintenanceLogs: MaintenanceLog[] = $state([]); + let loading = $state(false); + let error = $state(''); + + $effect(() => { + if (!vehicleId) { + error = 'Vehicle ID is required.'; + loading = false; + } else { + fetchMaintenanceLogs(); + } + }); async function fetchMaintenanceLogs() { if (!vehicleId) { @@ -50,19 +59,22 @@ } } - async function handleDelete(logId: number) { + async function deleteMaintenenceLog(logId: string) { if (!confirm('Are you sure you want to delete this maintenance log?')) { return; } try { - const response = await fetch(`${env.PUBLIC_API_BASE_URL}/api/maintenance-logs/${logId}`, { - method: 'DELETE', - headers: { - 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' + const response = await fetch( + `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/maintenance-logs/${logId}`, + { + method: 'DELETE', + headers: { + 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' + } } - }); + ); if (response.ok) { - await fetchMaintenanceLogs(); // Refresh the list + await fetchMaintenanceLogs(); } else { const data = await response.json(); alert(data.message || 'Failed to delete maintenance log.'); @@ -72,104 +84,68 @@ } } - function handleEdit(log: MaintenanceLogEntry) { - selectedLog = log; - showEditModal = true; - } - - function closeEditModal() { - showEditModal = false; - selectedLog = null; - } - - function handleSuccess() { - fetchMaintenanceLogs(); // Refresh logs after add/edit - closeEditModal(); - } - - // Refetch when vehicleId changes - $: if (vehicleId) { - fetchMaintenanceLogs(); - } - onMount(() => { - if (vehicleId) fetchMaintenanceLogs(); + fetchMaintenanceLogs(); }); -
-

Maintenance History

- {#if loading} -
Loading maintenance logs...
- {:else if error} - - {:else if maintenanceLogs.length === 0} -
No maintenance logs for this vehicle.
- {:else} -
- - - - - - - - - - - - - {#each maintenanceLogs as log (log.id)} - - Loading maintenance logs... +{:else if error} + +{:else if maintenanceLogs.length === 0} +
No maintenance logs for this vehicle.
+{:else} +
+
DateOdometerServiceCostNotesActions
{formatDate(log.date)}
+ + + + + + + + + + + + {#each maintenanceLogs as log (log.id)} + + + + + + + - - + + - - - {/each} - -
DateOdometerServiceCostNotesActions
{formatDate(log.date)}{log.odometer}{log.service}{formatCurrency(log.cost)}{log.notes || '-'} + {log.odometer}{log.service}{formatCurrency(log.cost)}{log.notes || '-'} - - -
-
- {/if} - - {#if showEditModal} - - {/if} -
+ + + + + {/each} + + +
+{/if} diff --git a/app/client/src/components/maintenance/MaintenanceLogModal.svelte b/app/client/src/components/maintenance/MaintenanceLogModal.svelte new file mode 100644 index 00000000..b9ac5514 --- /dev/null +++ b/app/client/src/components/maintenance/MaintenanceLogModal.svelte @@ -0,0 +1,42 @@ + + +{#if showModal} + + + +{/if} diff --git a/app/client/src/components/pucc/PollutionCertificateDetails.svelte b/app/client/src/components/pucc/PollutionCertificateDetails.svelte deleted file mode 100644 index 0e5ac6d8..00000000 --- a/app/client/src/components/pucc/PollutionCertificateDetails.svelte +++ /dev/null @@ -1,183 +0,0 @@ - - -
-

- Pollution Certificate Details -

- {#if loading} -
Loading pollution certificate details...
- {:else if error} - - {:else if pollutionCertificate} -
-
-
- - {pollutionCertificate.certificateNumber} -
-
- - -
-
-
-
- - Issue Date: - {formatDate(pollutionCertificate.issueDate)} -
-
- - Expiry Date: - {formatDate(pollutionCertificate.expiryDate)} -
-
- - Testing Center: - {pollutionCertificate.testingCenter} -
- {#if pollutionCertificate.notes} -
- - Notes: - {pollutionCertificate.notes} -
- {/if} -
-
- {:else} -
-

- No pollution certificate details found for this vehicle. -

- -
- {/if} - - {#if showPollutionCertificateFormModal} - - {/if} -
diff --git a/app/client/src/components/pucc/PollutionCertificateList.svelte b/app/client/src/components/pucc/PollutionCertificateList.svelte new file mode 100644 index 00000000..8cbe2d96 --- /dev/null +++ b/app/client/src/components/pucc/PollutionCertificateList.svelte @@ -0,0 +1,153 @@ + + +{#if loading} +
Loading pollution certificate details...
+{:else if error} + +{:else if pollutionCertificates.length === 0} +
No maintenance logs for this vehicle.
+{:else} + {#each pollutionCertificates as pucc (pucc.id)} +
+
+
+ + {pucc.certificateNumber} +
+
+ + +
+
+
+
+ + Issue Date: + {formatDate(pucc.issueDate)} +
+
+ + Expiry Date: + {formatDate(pucc.expiryDate)} +
+
+ + Testing Center: + {pucc.testingCenter} +
+ {#if pucc.notes} +
+ + Notes: + {pucc.notes} +
+ {/if} +
+
+ {/each} +{/if} diff --git a/app/client/src/components/tabs/DashboardTab.svelte b/app/client/src/components/tabs/DashboardTab.svelte new file mode 100644 index 00000000..1c824a26 --- /dev/null +++ b/app/client/src/components/tabs/DashboardTab.svelte @@ -0,0 +1,135 @@ + + + +
+ {#if fuelCostData?.datasets?.length > 0 && mileageData?.datasets?.length > 0} + + + {:else} +
+

+ No fuel or mileage data available for this vehicle. +

+
+ {/if} +
+
diff --git a/app/client/src/components/tabs/FuelLogTab.svelte b/app/client/src/components/tabs/FuelLogTab.svelte new file mode 100644 index 00000000..7973ddb0 --- /dev/null +++ b/app/client/src/components/tabs/FuelLogTab.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/app/client/src/components/tabs/InsuranceTab.svelte b/app/client/src/components/tabs/InsuranceTab.svelte new file mode 100644 index 00000000..a5d697a1 --- /dev/null +++ b/app/client/src/components/tabs/InsuranceTab.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/app/client/src/components/tabs/MaintenenceLogTab.svelte b/app/client/src/components/tabs/MaintenenceLogTab.svelte new file mode 100644 index 00000000..138ef875 --- /dev/null +++ b/app/client/src/components/tabs/MaintenenceLogTab.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/app/client/src/components/tabs/PollutionTab.svelte b/app/client/src/components/tabs/PollutionTab.svelte new file mode 100644 index 00000000..cff2a952 --- /dev/null +++ b/app/client/src/components/tabs/PollutionTab.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/app/client/src/components/tabs/TabHeader.svelte b/app/client/src/components/tabs/TabHeader.svelte new file mode 100644 index 00000000..dceb70b8 --- /dev/null +++ b/app/client/src/components/tabs/TabHeader.svelte @@ -0,0 +1,53 @@ + + +
    + {#each tabs as tab (tab)} + + {/each} +
diff --git a/app/client/src/components/vehicle/VehicleCard.svelte b/app/client/src/components/vehicle/VehicleCard.svelte index 21f829ca..e9517cec 100644 --- a/app/client/src/components/vehicle/VehicleCard.svelte +++ b/app/client/src/components/vehicle/VehicleCard.svelte @@ -13,8 +13,35 @@ BadgeCheck } from '@lucide/svelte'; import { formatDistance } from '$lib/utils/formatting'; + import { vehicleModelStore, vehiclesStore } from '$lib/stores/vehicle'; + import { maintenanceModelStore } from '$lib/stores/maintenance'; + import { fuelLogModelStore } from '$lib/stores/fuel-log'; - const { vehicle, onEditVehicle, onDeleteVehicle, onRefillFuel, onAddMaintenance } = $props(); + const { vehicle, updateCallback } = $props(); + + async function deleteVehicle(vehicleId: string) { + if (!confirm('Are you sure you want to delete this vehicle?')) { + return; + } + try { + const response = await fetch(`http://localhost:3000/api/vehicles/${vehicleId}`, { + method: 'DELETE', + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + }); + if (response.ok) { + alert('Vehicle deleted successfully.'); + vehicleModelStore.hide(); + vehiclesStore.fetchVehicles(); + } else { + const data = await response.json(); + alert(data.message || 'Failed to delete vehicle.'); + } + } catch (e) { + alert('Failed to connect to the server.'); + } + }
{/if}
-
- - - - +
+
+ + + + +
+
+ + +
diff --git a/app/client/src/components/vehicle/VehicleForm.svelte b/app/client/src/components/vehicle/VehicleForm.svelte index 1078e050..88ffc4a4 100644 --- a/app/client/src/components/vehicle/VehicleForm.svelte +++ b/app/client/src/components/vehicle/VehicleForm.svelte @@ -14,6 +14,9 @@ import { onMount } from 'svelte'; import FormSubmitButton from '$components/common/FormSubmitButton.svelte'; import { simulateNetworkDelay } from '$lib/utils/dev'; + import { vehiclesStore } from '$lib/stores/vehicle'; + + let { vehicleToEdit = null, editMode = false, modalVisibility = $bindable(), loading } = $props(); const vehicle: NewVehicle = $state({ make: '', @@ -25,8 +28,6 @@ odometer: null }); - let { vehicleToEdit = null, editMode = false, modalVisibility = $bindable(), loading } = $props(); - let status = $state<{ message: string | null; type: 'ERROR' | 'SUCCESS' | null; @@ -52,7 +53,7 @@ loading = true; status.message = null; status.type = null; - await simulateNetworkDelay(2000); // Simulate network delay for development + // await simulateNetworkDelay(2000); // Simulate network delay for development const response = await fetch( `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${editMode ? vehicleToEdit.id : ''}`, { @@ -66,7 +67,7 @@ ); if (response.ok) { - status.message = 'Vehicle added successfully!'; + status.message = `Vehicle ${editMode ? 'updated' : 'added'} successfully!`; status.type = 'SUCCESS'; Object.assign(vehicle, { make: '', @@ -78,9 +79,10 @@ odometer: null }); modalVisibility = false; + vehiclesStore.fetchVehicles(); // Refresh the vehicle list after closing the modal } else { const data = await response.json(); - status.message = data.message || 'Failed to add vehicle.'; + status.message = data.message || `Failed to ${editMode ? 'update' : 'add'} vehicle.`; status.type = 'ERROR'; } } catch (e) { @@ -94,7 +96,7 @@
{ persistVehicle(); - // e.preventDefault(); + e.preventDefault(); }} class="space-y-6" > diff --git a/app/client/src/components/vehicle/VehicleList.svelte b/app/client/src/components/vehicle/VehicleList.svelte index 740184dc..5bb70051 100644 --- a/app/client/src/components/vehicle/VehicleList.svelte +++ b/app/client/src/components/vehicle/VehicleList.svelte @@ -1,18 +1,11 @@ @@ -31,7 +24,7 @@ class:ring-blue-500={selectedVehicleId === vehicle.id} class="cursor-pointer rounded-2xl transition-all duration-300 ease-in-out" > - + {/each} diff --git a/app/client/src/components/vehicle/VehicleModal.svelte b/app/client/src/components/vehicle/VehicleModal.svelte index 4fa278cd..d81c5d55 100644 --- a/app/client/src/components/vehicle/VehicleModal.svelte +++ b/app/client/src/components/vehicle/VehicleModal.svelte @@ -1,16 +1,27 @@ {#if showModal} - - + + {/if} diff --git a/app/client/src/lib/models/fuel-log.ts b/app/client/src/lib/models/fuel-log.ts new file mode 100644 index 00000000..b3f789fd --- /dev/null +++ b/app/client/src/lib/models/fuel-log.ts @@ -0,0 +1,11 @@ +export interface NewFuelLog { + date: string; + odometer: number | null; + fuelAmount: number | null; + cost: number | null; + notes?: string; +} + +export interface FuelLog extends NewFuelLog { + id: string; +} diff --git a/app/client/src/lib/states/auth.svelte.ts b/app/client/src/lib/stores/auth.ts similarity index 100% rename from app/client/src/lib/states/auth.svelte.ts rename to app/client/src/lib/stores/auth.ts diff --git a/app/client/src/lib/states/config.ts b/app/client/src/lib/stores/config.ts similarity index 100% rename from app/client/src/lib/states/config.ts rename to app/client/src/lib/stores/config.ts diff --git a/app/client/src/lib/states/dark-mode.ts b/app/client/src/lib/stores/dark-mode.ts similarity index 72% rename from app/client/src/lib/states/dark-mode.ts rename to app/client/src/lib/stores/dark-mode.ts index 087d3372..1d89553a 100644 --- a/app/client/src/lib/states/dark-mode.ts +++ b/app/client/src/lib/stores/dark-mode.ts @@ -1,7 +1,7 @@ import { writable } from 'svelte/store'; const createDarkModeStore = () => { - const { subscribe, set, update } = writable(false); + const { subscribe, set } = writable(false); function loadDarkModePreference() { if (typeof window !== 'undefined') { @@ -14,11 +14,11 @@ const createDarkModeStore = () => { } } + loadDarkModePreference(); + return { subscribe, - toggle: () => update((current) => !current), - enable: () => set(true), - disable: () => set(false) + set }; }; diff --git a/app/client/src/lib/stores/fuel-log.ts b/app/client/src/lib/stores/fuel-log.ts new file mode 100644 index 00000000..6af903db --- /dev/null +++ b/app/client/src/lib/stores/fuel-log.ts @@ -0,0 +1,51 @@ +import { env } from '$env/dynamic/public'; +import type { FuelLog } from '$lib/models/fuel-log'; +import { writable } from 'svelte/store'; + +const createFuelLogModalStore = () => { + const { subscribe, set } = writable<{ + logToEdit?: any; + vehicleId?: string; + editMode: boolean; + show: boolean; + callback: (status: boolean) => void; + }>({ + logToEdit: undefined, + vehicleId: undefined, + editMode: false, + show: false, + callback: () => {} + }); + + function show( + vehicleId: string, + logToEdit?: any, + editMode: boolean = false, + callback: any = undefined + ) { + set({ + logToEdit, + vehicleId, + editMode, + show: true, + callback + }); + } + function hide() { + set({ + logToEdit: undefined, + vehicleId: undefined, + editMode: false, + show: false, + callback: () => {} + }); + } + + return { + subscribe, + show, + hide + }; +}; + +export const fuelLogModelStore = createFuelLogModalStore(); diff --git a/app/client/src/lib/stores/maintenance.ts b/app/client/src/lib/stores/maintenance.ts new file mode 100644 index 00000000..7c4b5d80 --- /dev/null +++ b/app/client/src/lib/stores/maintenance.ts @@ -0,0 +1,49 @@ +import { writable } from 'svelte/store'; + +const createMaintenanceModalStore = () => { + const { subscribe, set } = writable<{ + vehicleId?: string; + logToEdit?: any; + editMode: boolean; + show: boolean; + callback?: any; + }>({ + vehicleId: undefined, + logToEdit: undefined, + editMode: false, + show: false, + callback: undefined + }); + + function show( + vehicleId: string, + logToEdit?: any, + editMode: boolean = false, + callback: any = undefined + ) { + set({ + vehicleId, + logToEdit, + editMode, + show: true, + callback + }); + } + + function hide() { + set({ + vehicleId: undefined, + logToEdit: undefined, + editMode: false, + show: false + }); + } + + return { + subscribe, + show, + hide + }; +}; + +export const maintenanceModelStore = createMaintenanceModalStore(); diff --git a/app/client/src/lib/stores/vehicle.ts b/app/client/src/lib/stores/vehicle.ts new file mode 100644 index 00000000..0fbafce3 --- /dev/null +++ b/app/client/src/lib/stores/vehicle.ts @@ -0,0 +1,131 @@ +import { env } from '$env/dynamic/public'; +import type { Vehicle } from '$lib/models/vehicle'; +import { simulateNetworkDelay } from '$lib/utils/dev'; +import { writable } from 'svelte/store'; + +const createVehicleModalStore = () => { + const { subscribe, set } = writable<{ + vehicleToEdit?: any; + editMode: boolean; + show: boolean; + }>({ + vehicleToEdit: undefined, + editMode: false, + show: false + }); + + function show(vehicleToEdit?: any, editMode: boolean = false) { + set({ + vehicleToEdit, + editMode, + show: true + }); + } + function hide() { + set({ + vehicleToEdit: undefined, + editMode: false, + show: false + }); + } + + return { + subscribe, + show, + hide + }; +}; + +const createVehiclesStore = () => { + const { subscribe, set, update } = writable<{ + loading: boolean; + error: string; + vehicles: Vehicle[]; + selectedVehicleId?: string; + }>({ + loading: true, + error: '', + vehicles: [], + selectedVehicleId: undefined + }); + + async function fetchVehicles() { + let tempSelection: string | undefined = undefined; + update((current) => { + if (current.selectedVehicleId) { + tempSelection = current.selectedVehicleId; + } + return { + loading: true, + error: current.error, + vehicles: [], + selectedVehicleId: undefined + }; + }); + // await simulateNetworkDelay(2000); // Simulate network delay for development + try { + const response = await fetch(`${env.PUBLIC_API_BASE_URL || ''}/api/vehicles`, { + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + }); + if (response.ok) { + const vehicles = await response.json(); + if (Array.isArray(vehicles)) { + set({ + loading: false, + error: '', + vehicles: vehicles + }); + } else { + console.error('Invalid vehicles data format', vehicles); + set({ + loading: false, + error: 'Invalid vehicles data format.', + vehicles: [] + }); + } + } else { + console.log('Failed to fetch vehicles', response); + const data = await response.json(); + const error = data.message || 'Failed to fetch vehicles.'; + set({ + loading: false, + error, + vehicles: [] + }); + } + } catch (e) { + console.error('Failed to connect to the server.', e); + set({ + loading: false, + error: 'Failed to connect to the server.', + vehicles: [] + }); + } + update((current) => ({ + loading: current.loading, + error: '', + vehicles: current.vehicles, + selectedVehicleId: tempSelection + })); + } + + function selectVehicle(vehicleId: string) { + update((current) => ({ + ...current, + selectedVehicleId: vehicleId + })); + } + + fetchVehicles(); + + return { + subscribe, + fetchVehicles, + selectVehicle + }; +}; + +export const vehicleModelStore = createVehicleModalStore(); +export const vehiclesStore = createVehiclesStore(); diff --git a/app/client/src/lib/utils/formatting.ts b/app/client/src/lib/utils/formatting.ts index 5a18dd34..dea32075 100644 --- a/app/client/src/lib/utils/formatting.ts +++ b/app/client/src/lib/utils/formatting.ts @@ -1,5 +1,5 @@ import dayjs from 'dayjs'; -import { config, type Config } from '../states/config'; +import { config, type Config } from '$lib/stores/config'; export interface ConfigStore { dateFormat: string; @@ -49,6 +49,16 @@ const formatCurrency = (amount: number): string => { }).format(amount); }; +const getDistanceUnit = (): string => { + if (configs.unitOfMeasure === 'metric') { + return 'km'; + } + if (configs.unitOfMeasure === 'imperial') { + return 'mi'; + } + return ''; +}; + const formatDistance = (distance: number): string => { if (configs.unitOfMeasure === 'metric') { return `${distance} km`; @@ -90,6 +100,7 @@ export { formatDate, getCurrencySymbol, formatCurrency, + getDistanceUnit, formatDistance, formatVolume, getMileageUnit, diff --git a/app/client/src/routes/config/+page.svelte b/app/client/src/routes/config/+page.svelte index a65b826c..dacce45e 100644 --- a/app/client/src/routes/config/+page.svelte +++ b/app/client/src/routes/config/+page.svelte @@ -1,5 +1,5 @@ @@ -203,7 +46,7 @@ - - - - - - +
{#if activeTab === 'dashboard'} -
-

- Fuel Cost & Mileage Trends -

-
- {#if fuelCostData?.datasets?.length > 0 && mileageData?.datasets?.length > 0} - - - {:else} -
-

- No fuel or mileage data available for this vehicle. -

-
- {/if} -
-
+ {:else if activeTab === 'fuel'} -
- -
+ {:else if activeTab === 'maintenance'} -
- -
+ {:else if activeTab === 'insurance'} -
- -
+ {:else if activeTab === 'pollution'} -
- -
+ {/if}
- - (showMaintenanceLogModal = false)} - onSuccess={() => { - showMaintenanceLogModal = false; - }} - /> {:else if vehicles.length > 0 && !loading}

@@ -432,4 +89,8 @@

{/if} + + + + diff --git a/app/server/src/controllers/InsuranceController.ts b/app/server/src/controllers/InsuranceController.ts index 0da03e24..1e5c1c5f 100644 --- a/app/server/src/controllers/InsuranceController.ts +++ b/app/server/src/controllers/InsuranceController.ts @@ -3,7 +3,7 @@ import * as insuranceService from "../services/insuranceService.js"; import { InsuranceNotFoundError, InsuranceExistsError, - InsuranceServiceError + InsuranceServiceError, } from "../exceptions/InsuranceError.js"; export const addInsurance = async (req: Request, res: Response) => { @@ -15,7 +15,8 @@ export const addInsurance = async (req: Request, res: Response) => { } if (!provider || !policyNumber || !startDate || !endDate || !cost) { return res.status(400).json({ - message: "Provider, Policy Number, Start Date, End Date, and Cost are required.", + message: + "Provider, Policy Number, Start Date, End Date, and Cost are required.", }); } @@ -42,7 +43,7 @@ export const getInsurance = async (req: Request, res: Response) => { return res.status(400).json({ message: "Vehicle ID is required." }); } try { - const insurance = await insuranceService.getInsurance(vehicleId); + const insurance = await insuranceService.getInsurances(vehicleId); res.status(200).json(insurance); } catch (error: any) { if (error instanceof InsuranceNotFoundError) { @@ -64,7 +65,8 @@ export const updateInsurance = async (req: Request, res: Response) => { } if (!provider || !policyNumber || !startDate || !endDate || !cost) { return res.status(400).json({ - message: "Provider, Policy Number, Start Date, End Date, and Cost are required.", + message: + "Provider, Policy Number, Start Date, End Date, and Cost are required.", }); } diff --git a/app/server/src/controllers/PUCCController.ts b/app/server/src/controllers/PUCCController.ts index 45632763..0fd04d55 100644 --- a/app/server/src/controllers/PUCCController.ts +++ b/app/server/src/controllers/PUCCController.ts @@ -3,26 +3,28 @@ import * as pollutionCertificateService from "../services/pollutionCertificateSe import { PollutionCertificateNotFoundError, PollutionCertificateExistsError, - PollutionCertificateServiceError + PollutionCertificateServiceError, } from "../exceptions/PollutionCertificateErrors.js"; export const addPollutionCertificate = async (req: Request, res: Response) => { const { vehicleId } = req.params; - const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = req.body; + const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = + req.body; if (!vehicleId) { return res.status(400).json({ message: "Vehicle ID is required." }); } if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { return res.status(400).json({ - message: "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", + message: + "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", }); } try { const result = await pollutionCertificateService.addPollutionCertificate( vehicleId, - req.body + req.body, ); res.status(201).json(result); } catch (error: any) { @@ -45,9 +47,8 @@ export const getPollutionCertificate = async (req: Request, res: Response) => { return res.status(400).json({ message: "Vehicle ID is required." }); } try { - const pollutionCertificate = await pollutionCertificateService.getPollutionCertificate( - vehicleId - ); + const pollutionCertificate = + await pollutionCertificateService.getPollutionCertificates(vehicleId); res.status(200).json(pollutionCertificate); } catch (error: any) { if (error instanceof PollutionCertificateNotFoundError) { @@ -60,23 +61,28 @@ export const getPollutionCertificate = async (req: Request, res: Response) => { } }; -export const updatePollutionCertificate = async (req: Request, res: Response) => { +export const updatePollutionCertificate = async ( + req: Request, + res: Response, +) => { const { vehicleId } = req.params; - const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = req.body; + const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = + req.body; if (!vehicleId) { return res.status(400).json({ message: "Vehicle ID is required." }); } if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { return res.status(400).json({ - message: "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", + message: + "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", }); } try { const result = await pollutionCertificateService.updatePollutionCertificate( vehicleId, - req.body + req.body, ); res.status(200).json(result); } catch (error: any) { @@ -90,15 +96,17 @@ export const updatePollutionCertificate = async (req: Request, res: Response) => } }; -export const deletePollutionCertificate = async (req: Request, res: Response) => { +export const deletePollutionCertificate = async ( + req: Request, + res: Response, +) => { const { vehicleId } = req.params; if (!vehicleId) { return res.status(400).json({ message: "Vehicle ID is required." }); } try { - const result = await pollutionCertificateService.deletePollutionCertificate( - vehicleId - ); + const result = + await pollutionCertificateService.deletePollutionCertificate(vehicleId); res.status(200).json(result); } catch (error: any) { if (error instanceof PollutionCertificateNotFoundError) { diff --git a/app/server/src/models/FuelLog.ts b/app/server/src/models/FuelLog.ts index 64c82109..55f6f580 100644 --- a/app/server/src/models/FuelLog.ts +++ b/app/server/src/models/FuelLog.ts @@ -1,83 +1,89 @@ -import { DataTypes, Model, Optional } from 'sequelize'; -import sequelize from '../config/database.js'; -import Vehicle from './Vehicle.js'; +import { DataTypes, Model, Optional } from "sequelize"; +import sequelize from "../config/database.js"; +import Vehicle from "./Vehicle.js"; interface FuelLogAttributes { - id: string; - vehicleId: string; - date: string; - odometer: number; - fuelAmount: number; - cost: number; - notes?: string; + id: string; + vehicleId: string; + date: string; + odometer: number; + fuelAmount: number; + cost: number; + notes?: string; } -interface FuelLogCreationAttributes extends Optional {} +interface FuelLogCreationAttributes extends Optional {} -class FuelLog extends Model implements FuelLogAttributes { - public declare id: string; - public declare vehicleId: string; - public declare date: string; - public declare odometer: number; - public declare fuelAmount: number; - public declare cost: number; - public declare notes?: string; +class FuelLog + extends Model + implements FuelLogAttributes +{ + declare public id: string; + declare public vehicleId: string; + declare public date: string; + declare public odometer: number; + declare public fuelAmount: number; + declare public cost: number; + declare public notes?: string; } -FuelLog.init({ +FuelLog.init( + { id: { - type: DataTypes.UUIDV4, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - allowNull: false, + type: DataTypes.UUIDV4, + primaryKey: true, + defaultValue: DataTypes.UUIDV4, + allowNull: false, }, vehicleId: { - type: DataTypes.UUIDV4, - allowNull: false, - references: { - model: Vehicle, - key: 'id', - } + type: DataTypes.UUIDV4, + allowNull: false, + references: { + model: Vehicle, + key: "id", + }, }, date: { - type: DataTypes.DATE, - allowNull: false, - validate: { - isDate: true, - notEmpty: true, - } + type: DataTypes.DATEONLY, + allowNull: false, + validate: { + isDate: true, + notEmpty: true, + }, }, odometer: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - isInt: true, - min: 0, - } + type: DataTypes.INTEGER, + allowNull: false, + validate: { + isInt: true, + min: 0, + }, }, fuelAmount: { - type: DataTypes.FLOAT, - allowNull: false, + type: DataTypes.FLOAT, + allowNull: false, }, cost: { - type: DataTypes.FLOAT, - allowNull: false, - validate: { - isFloat: true, - min: 0, - } + type: DataTypes.FLOAT, + allowNull: false, + validate: { + isFloat: true, + min: 0, + }, }, notes: { - type: DataTypes.STRING, - allowNull: true, - validate: { - len: [0, 500], - } + type: DataTypes.STRING, + allowNull: true, + validate: { + len: [0, 500], + }, }, -}, { - tableName: 'fuel_logs', + }, + { + tableName: "fuel_logs", timestamps: true, underscored: true, sequelize, -}); -export default FuelLog; \ No newline at end of file + }, +); +export default FuelLog; diff --git a/app/server/src/models/Insurance.ts b/app/server/src/models/Insurance.ts index c2e302e7..316f3b2c 100644 --- a/app/server/src/models/Insurance.ts +++ b/app/server/src/models/Insurance.ts @@ -19,13 +19,13 @@ class Insurance extends Model implements InsuranceAttributes { - public declare id: string; - public declare vehicleId: string; - public declare provider: string; - public declare policyNumber: string; - public declare startDate: string; - public declare endDate: string; - public declare cost: number; + declare public id: string; + declare public vehicleId: string; + declare public provider: string; + declare public policyNumber: string; + declare public startDate: string; + declare public endDate: string; + declare public cost: number; } Insurance.init( @@ -59,17 +59,17 @@ Insurance.init( }, }, startDate: { - type: DataTypes.DATE, + type: DataTypes.DATEONLY, allowNull: false, validate: { isDate: true, }, }, endDate: { - type: DataTypes.DATE, + type: DataTypes.DATEONLY, allowNull: false, validate: { - isDate: true + isDate: true, }, }, cost: { @@ -85,8 +85,8 @@ Insurance.init( tableName: "insurances", timestamps: true, underscored: true, - sequelize - } + sequelize, + }, ); export default Insurance; diff --git a/app/server/src/models/MaintenanceLog.ts b/app/server/src/models/MaintenanceLog.ts index 4af547c4..b9c7d452 100644 --- a/app/server/src/models/MaintenanceLog.ts +++ b/app/server/src/models/MaintenanceLog.ts @@ -19,13 +19,13 @@ class MaintenanceLog extends Model implements MaintenanceLogAttributes { - public declare id: string; - public declare vehicleId: string; - public declare date: string; - public declare odometer: number; - public declare service: string; - public declare cost: number; - public declare notes: string; + declare public id: string; + declare public vehicleId: string; + declare public date: string; + declare public odometer: number; + declare public service: string; + declare public cost: number; + declare public notes: string; } MaintenanceLog.init( @@ -39,13 +39,13 @@ MaintenanceLog.init( vehicleId: { type: DataTypes.INTEGER, references: { - model: Vehicle, + model: Vehicle, key: "id", }, allowNull: false, }, date: { - type: DataTypes.DATE, + type: DataTypes.DATEONLY, allowNull: false, validate: { isDate: true, @@ -78,7 +78,7 @@ MaintenanceLog.init( type: DataTypes.STRING, allowNull: true, validate: { - notEmpty: true + notEmpty: true, }, }, }, @@ -87,7 +87,7 @@ MaintenanceLog.init( timestamps: true, underscored: true, sequelize, - } + }, ); export default MaintenanceLog; diff --git a/app/server/src/models/PUCC.ts b/app/server/src/models/PUCC.ts index 75b5db43..c992cacc 100644 --- a/app/server/src/models/PUCC.ts +++ b/app/server/src/models/PUCC.ts @@ -16,16 +16,19 @@ interface PollutionCertificateCreationAttributes extends Optional {} class PollutionCertificate - extends Model + extends Model< + PollutionCertificateAttributes, + PollutionCertificateCreationAttributes + > implements PollutionCertificateAttributes { - public declare id: string; - public declare vehicleId: string; - public declare certificateNumber: string; - public declare issueDate: string; - public declare expiryDate: string; - public declare testingCenter: string; - public declare notes: string; + declare public id: string; + declare public vehicleId: string; + declare public certificateNumber: string; + declare public issueDate: string; + declare public expiryDate: string; + declare public testingCenter: string; + declare public notes: string; } PollutionCertificate.init( @@ -42,7 +45,7 @@ PollutionCertificate.init( model: Vehicle, // Use string reference key: "id", }, - allowNull: false + allowNull: false, }, certificateNumber: { type: DataTypes.STRING, @@ -86,7 +89,7 @@ PollutionCertificate.init( tableName: "pollution_certificates", timestamps: true, underscored: true, - } + }, ); export default PollutionCertificate; diff --git a/app/server/src/services/insuranceService.ts b/app/server/src/services/insuranceService.ts index 1284b9b7..57603ac5 100644 --- a/app/server/src/services/insuranceService.ts +++ b/app/server/src/services/insuranceService.ts @@ -1,4 +1,8 @@ -import { InsuranceNotFoundError, InsuranceExistsError, InsuranceServiceError } from "../exceptions/InsuranceError.js"; +import { + InsuranceNotFoundError, + InsuranceExistsError, + InsuranceServiceError, +} from "../exceptions/InsuranceError.js"; import { Insurance, Vehicle } from "../models/index.js"; import { UniqueConstraintError } from "sequelize"; @@ -28,9 +32,9 @@ export const addInsurance = async (vehicleId: string, insuranceData: any) => { } }; -export const getInsurance = async (vehicleId: string) => { +export const getInsurances = async (vehicleId: string) => { try { - const insurance = await Insurance.findOne({ + const insurance = await Insurance.findAll({ where: { vehicleId: vehicleId }, }); if (!insurance) { @@ -45,7 +49,10 @@ export const getInsurance = async (vehicleId: string) => { } }; -export const updateInsurance = async (vehicleId: string, insuranceData: any) => { +export const updateInsurance = async ( + vehicleId: string, + insuranceData: any, +) => { try { const insurance = await Insurance.findOne({ where: { vehicleId: vehicleId }, diff --git a/app/server/src/services/pollutionCertificateService.ts b/app/server/src/services/pollutionCertificateService.ts index 1ca83b47..c679210d 100644 --- a/app/server/src/services/pollutionCertificateService.ts +++ b/app/server/src/services/pollutionCertificateService.ts @@ -2,13 +2,13 @@ import { Vehicle, PollutionCertificate } from "../models/index.js"; import { PollutionCertificateNotFoundError, PollutionCertificateExistsError, - PollutionCertificateServiceError + PollutionCertificateServiceError, } from "../exceptions/PollutionCertificateErrors.js"; import { UniqueConstraintError } from "sequelize"; export const addPollutionCertificate = async ( vehicleId: string, - pollutionCertificateData: any + pollutionCertificateData: any, ) => { try { const vehicle = await Vehicle.findByPk(vehicleId); @@ -31,13 +31,15 @@ export const addPollutionCertificate = async ( if (error instanceof PollutionCertificateNotFoundError) { throw error; } - throw new PollutionCertificateServiceError("Error adding pollution certificate."); + throw new PollutionCertificateServiceError( + "Error adding pollution certificate.", + ); } }; -export const getPollutionCertificate = async (vehicleId: string) => { +export const getPollutionCertificates = async (vehicleId: string) => { try { - const pollutionCertificate = await PollutionCertificate.findOne({ + const pollutionCertificate = await PollutionCertificate.findAll({ where: { vehicleId: vehicleId }, }); if (!pollutionCertificate) { @@ -48,13 +50,15 @@ export const getPollutionCertificate = async (vehicleId: string) => { if (error instanceof PollutionCertificateNotFoundError) { throw error; } - throw new PollutionCertificateServiceError("Error fetching pollution certificate."); + throw new PollutionCertificateServiceError( + "Error fetching pollution certificate.", + ); } }; export const updatePollutionCertificate = async ( vehicleId: string, - pollutionCertificateData: any + pollutionCertificateData: any, ) => { try { const pollutionCertificate = await PollutionCertificate.findOne({ @@ -70,7 +74,9 @@ export const updatePollutionCertificate = async ( if (error instanceof PollutionCertificateNotFoundError) { throw error; } - throw new PollutionCertificateServiceError("Error updating pollution certificate."); + throw new PollutionCertificateServiceError( + "Error updating pollution certificate.", + ); } }; @@ -87,6 +93,8 @@ export const deletePollutionCertificate = async (vehicleId: string) => { if (error instanceof PollutionCertificateNotFoundError) { throw error; } - throw new PollutionCertificateServiceError("Error deleting pollution certificate."); + throw new PollutionCertificateServiceError( + "Error deleting pollution certificate.", + ); } }; diff --git a/app/server/undefined b/app/server/undefined index 6f0e29e9b5158608b21708fb9c60d4083b312784..066f70bbca49bd9b5b4fae8e62e13a7a7580273f 100644 GIT binary patch literal 102400 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 JXIfT+{}0vHxeEXQ literal 102400 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%0A$&r2%R`|)aQYmKe;Y);g`1fL*Gi| zmw&isVl;=~P=FsaR7lVKi22pDr3*@j@TNu$UilnCW9kO*>p@Du^1;Mcl}bwG`fexj$IbLH?mi9amp<4EAOS0-Hy| zf?X8b7Qsw5LnDklNReg6!j9s^lQQ?A8jeT?+$+`#8Ql~`($cVtP(R-9D{*95(?aer00NT=n?|9Cpp(=Qz`V-UMelu=zvtGPSv0l=%5-2P1K`of z#71Un$VCA|5JfPqT`Xvr5Il}3!x%IKAXkXq^FRGPZ+RXPyQ<_&=oxT2HIhUI(02mm|063V#9*1iXP-$2P zF%+reSeVm{5Hlj7)qs0O03g>jc;~15k=iQkPcAgQmeunAgVO&$+xwN?QqOSrQrF|1 zuXGM{yr=x1%bo4}+CI?wyREIIk(T?4PZSG4m4Pgm!2D^|s0S1r= zXl0LvUSwf?Y-|AYLmzh+{NWV|{B{~UZcYMsX8=6GpmifyQGo}!Gleh$I|&WPL(NPW zg*3p5qP!9!WtZz$EcW?BGF1H3)Bf16r;*F%09=~^h{M3Lf;cfq=)kQAB4`5vNx~|^ z2!aF*v8jdt66C!|9hv*snqC`f|%!GO- z0yhzDKno-7kQ+Ta^>_Zrf030lo7cp#8`zZr5FQL#+oo`fY9Sg36H1nh*d&hY5*7-? z50pj7M%XYf@iX_BKlHmbujr7>KCBkCR~-Q_13>H;x(d;O#EoH*BW7lJaFh%WjvWe_ z!qB4)SRw$BD?%rq@dtvMF?etw9f{`V0M7s*b)L9{3WWItmXUyl26qwyEyQgnfi7}P z^#Bb)aa*oSak9%F+*n)4$M0`i3oi}0832cy)FWYlU;q>lG!n}A9Mr@mf+21f*{F(B zts{dyje zBDxA?Y9c&CVO)s@WC&(_E`qqQ?b@cgJis!kNqCG3LzsV-iEqr5#s}k$fnX(p9t|DT z<|*bKAoDOQvkPr3*<2f)k#Scp4HXWxLYDeE6S4(maNS)+g<(8Nre z@kRjTM*^e^v@Ugh{E35gRKMy7&E+*)j49u_KT>R0aUenL7vW{at_P ztEtNO53GdB7(vNS4^bYh(oS=fJym_yZTGqT?T^g#((? zLRV)1poOeJAipQz@575mR4j~4IMvJxk>A08tbujFl!g5{l}C5~SKmKY(?WH_k%}YW zUX=nU%8dZ9J_ScC+aQq*>%oR9@}P!{B#dHa1qkSc4NPLJy}2TE`afRjACPuiv-RU| z�#{f&1vX21thf6o`iL#0!zF^~?k%oWc?4zv}*r)vSia5r1eoJ3EouZ>vNh)u``k zxu#$iM+;rgcV5>1XYC&T={CMB(U5|DAu>4AQ zS65PbuIJ_Mv#pbT-zbju-qmgOZtneZiB@iH-P-$!_H}I!79a09)AsG+Y}xD^XuYR* zf5%hBzwFuG{b;vNZ*G`&$PtF$J_RnZtD9|;rF|rZT(dHwZ%(2AL@Lu^=rkC zw|uZ`ch>_g*K|JFeM{l{l@IhhQtm9>-!6KNc5JJ3w|}wc>9$QRf77% z_?P9+%9zBq(o_-j+W|Zn!;DdP0rLmm7m_4~V-pgYu$wIrpO6hB{L@PqQic|Cx6I<$ z0Rz=**dfFZ3x|bZ)H{kFFKqX4K{QSf(||LL+FW=Okp;H~sYV#G0>&-5RbCPGX0Q<9 zxq4QND7u9Jf(K&}f9HmlOF6gA=oZ<~agm)wlGPAIKuSU^upVq;Bc!N)C!7)*hd0ZH zD%*LcWkWlGap#ywr8_qIXhZ#aNgQE|4^IF(Kt_cHvLV9bmgOPe8w4PQc?j}{HZrJA zK)DyV2sjgQn`}stIC41@U|`D4IBqCm;Ts%4ghL~a>@m5%bW`Ew5Ni|+v;cla1Rfnk z9S||`4EzGVF_8lvUsviVoI&9Q2PuiXAv^)>6OmW+3~Y@--dVU_V7gQC)|$t$YV zeH;at?JDIJN8}Y%su(6q&lHZT6bl@dS5(Q#PsoNUsqgXDPZf@;WS9@hE2<=N56Xrr z`O*Wjp-NhEOg2=>{_SsjqVTjzl5L;7qDn4nZ`cRBZ3ZdMA_Af%h6+))FD(2tY zWkb`kL)S5pC$e2w0LVBX(t%|-aKqR(SpxGh*xt3fa3&x~UZXY(p-j2N!AwCyC2_EZ zCJ8p1TJUE1HhD#a{axFGSrT#VI%O8cT1Y51#=>t82|RaVCtGpF?SV~97$~rIEF?IY zD>oN$gc`B^7imL_+k(@#rC?kXmc*!&M;kPb_ z$+sOgg=sNVf(UtkX2BiyFpUgUSvXdR=CEykF1@179hV?Bhe`#MAUP;p@ECjsK}dqQ zor6buds}2fU{1IrD>G140=oy{Ee{?xDmZzFfDJYrn?W_bqDO>)p)SkGa76~k56Np` zM2IM}#7$sT(#^6V#sVQs^wD7+OfC`;1PD=xBe%XrUQBUidRh0g02A_zXU6iXP#6x7`QrXb;Fd0k_##j`} zNC#4FJl$&>xfsp>bW`W8Jx8IM;b~y-0>kD6U|=m_WN`nOqdv>BsKcrC7I{S!>YI=g z(38ZtqJ;{CK-$mPA#TB)cLPg^jp?nCp*3TNz|itAgz#xl`-pK&h|OWaxe-b@-`w*E zRI}qn_*#`qGK@xHDEd-#3xasaE6B#XvMZ~YGOY% zq91SS`*7hX_IAJS=HdtL(km)#8tjt|RW|4KrVSM~!S%?7Dx1x^WkZ!sU0s!K zX}ZFZFd^KI3+ZJhav1PMkjp><%C$op!uRTwS5(=g)FB(HYz8VUQuOp zOPg${vWY}KDetJtW{pyMYlTe_En7=dohn-qYW4r$kXd;zP5O!$Y~v}1}VeL&@^C7El1`rIse;d6ujFs(%?4|-{?vpEEDhCpt)fEj>5 z1H$dxJg{(+Cw3Dv8PL6jljvYv|A zmVpfmSY0`mcn%Z5utHm|o7|2ZXnCco$r^&(d|TnOeZF6l9{=Ff8telIGX%&dBMt<^ z%0=u2CLXYa5$v-78<%X`=7^!FR!sr{k`1}#N`=p~`Gd7gn?JD@QWRteR03b>W3bxx zu)=S=?VJ=HltR>GwZ-6V83L7vTq6ipnFpUXegBVY$^P+QO|^DYGnt>LA&^`2R6?!| zA;_NxzrNG=zm?VQZ)7h9K}n90#cVN9q~AzGY(*MgspYgbR&i z8>eta6%oiSS*vZQ`s6qKk=g>_kH2#b5Zss{kO>sjXd>z9$)(Ifx)2XM1E3f=7OZS2 zM^~?VlnC;r=vO}M`>FWf;*YO|IdMaVAVClWJ4rkvfoTVG0zn5JBAaBRqFXo-QX=r5 z)y#=>3|84r^~;C+!Pzu>=pVYZPUc)yimuNPsN{?qL9ohps!x2>_tV0`{r-W!PnWd% z8o&OhOM};C2vm|;>If*d4&Ayx0(AgU2%R zRT7eobe-hxUnqR^Z+!p8)En~;Eu5b;{zq-Fm1KZ_j)plH|1mPPg*2v%QvvAtq^S0D%^~Y;7CVx28 ziZzx7wq*cRk^_wZ$lvcXf50F9wHknl->l<*ssh-W0Z>U=C;~{AynJP6r8Qz`Hd z)Y}5B3gB%S0F`7)BLMQX?c9I!2Ws=&KTs2;=0xbq3;ka0CIhw+voiuX{+Lamt&8o>a@8L*qi~d6q2ru0LXWsle4~G zQ=k5!`%>$>IRKYs092B}>HzTYgWNq|;+_7`H)?Lfp%?1p6I4ZLQwBgKN!<{DToH;t z?C(jRL2|}F{1yZ z;Cg@LuA1L=u(Sqh^eq_xm0|;B0N6sDZ_M5HUVrd-DntH6J?CF_1U6;>REi)P0g!LZ zow&#!da1S&AN=MTjKG^S04R-p-QtW!0OT8U?-=$6mR@6K)x|| z%WwDt*Qe1(|KM{+IwspVK{fFJ3?S6mv&E=cA&F;>wKkNEp`TnjCb&0NPyViAnyYq?82Rr9F_jYdT z_(8|lIzH8LPse0xKIm_n0nLDBKr^5j_?cutmg=b-YIKKesB(7D?Xsa#)tYRmQX?j0 zLzPmjIK8z(#Zx33suUK5X+zb$3@X{t8){d4{ijl^@)lR+{gEogJU7ZARH=4I_Y^Dd cg;m~ppuEFPd7r6D^+39vS$RvL(st(m4|?s~MF0Q* From 5306e9fb0a76ef5c68148de6a683a7388c17bb61 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Mon, 4 Aug 2025 19:40:44 +0530 Subject: [PATCH 3/3] Merging Conflicts --- .../insurance/InsuranceDetailsList.svelte | 13 +- .../components/insurance/InsuranceForm.svelte | 180 ++++++++++++------ .../insurance/InsuranceModal.svelte | 43 +++++ .../maintenance/MaintenanceLogForm.svelte | 2 +- .../pucc/PollutionCertificateForm.svelte | 172 +++++++++++------ .../PollutionCertificateFormComponent.svelte | 95 --------- .../pucc/PollutionCertificateList.svelte | 13 +- .../pucc/PollutionCertificateModal.svelte | 42 ++++ .../src/components/vehicle/VehicleCard.svelte | 10 +- app/client/src/lib/stores/fuel-log.ts | 2 - app/client/src/lib/stores/insurance.ts | 49 +++++ app/client/src/lib/stores/pucc.ts | 49 +++++ app/client/src/routes/dashboard/+page.svelte | 5 + 13 files changed, 449 insertions(+), 226 deletions(-) create mode 100644 app/client/src/components/insurance/InsuranceModal.svelte delete mode 100644 app/client/src/components/pucc/PollutionCertificateFormComponent.svelte create mode 100644 app/client/src/components/pucc/PollutionCertificateModal.svelte create mode 100644 app/client/src/lib/stores/insurance.ts create mode 100644 app/client/src/lib/stores/pucc.ts diff --git a/app/client/src/components/insurance/InsuranceDetailsList.svelte b/app/client/src/components/insurance/InsuranceDetailsList.svelte index f4b00493..2fbbb1bc 100644 --- a/app/client/src/components/insurance/InsuranceDetailsList.svelte +++ b/app/client/src/components/insurance/InsuranceDetailsList.svelte @@ -4,6 +4,7 @@ import { env } from '$env/dynamic/public'; import { Shield, Calendar, Hash, DollarSign, Pencil, Trash2 } from '@lucide/svelte'; import { formatCurrency, formatDate } from '$lib/utils/formatting'; + import { insuranceModelStore } from '$lib/stores/insurance'; let { vehicleId } = $props(); @@ -107,7 +108,13 @@
- -
- diff --git a/app/client/src/components/pucc/PollutionCertificateList.svelte b/app/client/src/components/pucc/PollutionCertificateList.svelte index 8cbe2d96..4686faf9 100644 --- a/app/client/src/components/pucc/PollutionCertificateList.svelte +++ b/app/client/src/components/pucc/PollutionCertificateList.svelte @@ -4,6 +4,7 @@ import { env } from '$env/dynamic/public'; import { FileText, Calendar, MapPin, Pencil, Trash2, BadgeCheck } from '@lucide/svelte'; import { formatDate } from '$lib/utils/formatting'; + import { puccModelStore } from '$lib/stores/pucc'; let { vehicleId } = $props(); @@ -108,7 +109,13 @@
diff --git a/app/client/src/lib/stores/fuel-log.ts b/app/client/src/lib/stores/fuel-log.ts index 6af903db..2059d7b8 100644 --- a/app/client/src/lib/stores/fuel-log.ts +++ b/app/client/src/lib/stores/fuel-log.ts @@ -1,5 +1,3 @@ -import { env } from '$env/dynamic/public'; -import type { FuelLog } from '$lib/models/fuel-log'; import { writable } from 'svelte/store'; const createFuelLogModalStore = () => { diff --git a/app/client/src/lib/stores/insurance.ts b/app/client/src/lib/stores/insurance.ts new file mode 100644 index 00000000..68dd18df --- /dev/null +++ b/app/client/src/lib/stores/insurance.ts @@ -0,0 +1,49 @@ +import { writable } from 'svelte/store'; + +const createInsuranceModalStore = () => { + const { subscribe, set } = writable<{ + entryToEdit?: any; + vehicleId?: string; + editMode: boolean; + show: boolean; + callback: (status: boolean) => void; + }>({ + entryToEdit: undefined, + vehicleId: undefined, + editMode: false, + show: false, + callback: () => {} + }); + + function show( + vehicleId: string, + entryToEdit?: any, + editMode: boolean = false, + callback: any = undefined + ) { + set({ + entryToEdit, + vehicleId, + editMode, + show: true, + callback + }); + } + function hide() { + set({ + entryToEdit: undefined, + vehicleId: undefined, + editMode: false, + show: false, + callback: () => {} + }); + } + + return { + subscribe, + show, + hide + }; +}; + +export const insuranceModelStore = createInsuranceModalStore(); diff --git a/app/client/src/lib/stores/pucc.ts b/app/client/src/lib/stores/pucc.ts new file mode 100644 index 00000000..d59e95b1 --- /dev/null +++ b/app/client/src/lib/stores/pucc.ts @@ -0,0 +1,49 @@ +import { writable } from 'svelte/store'; + +const createPuccModalStore = () => { + const { subscribe, set } = writable<{ + entryToEdit?: any; + vehicleId?: string; + editMode: boolean; + show: boolean; + callback: (status: boolean) => void; + }>({ + entryToEdit: undefined, + vehicleId: undefined, + editMode: false, + show: false, + callback: () => {} + }); + + function show( + vehicleId: string, + entryToEdit?: any, + editMode: boolean = false, + callback: any = undefined + ) { + set({ + entryToEdit, + vehicleId, + editMode, + show: true, + callback + }); + } + function hide() { + set({ + entryToEdit: undefined, + vehicleId: undefined, + editMode: false, + show: false, + callback: () => {} + }); + } + + return { + subscribe, + show, + hide + }; +}; + +export const puccModelStore = createPuccModalStore(); diff --git a/app/client/src/routes/dashboard/+page.svelte b/app/client/src/routes/dashboard/+page.svelte index 51862b77..ecbfd8a1 100644 --- a/app/client/src/routes/dashboard/+page.svelte +++ b/app/client/src/routes/dashboard/+page.svelte @@ -14,6 +14,9 @@ import InsuranceTab from '$components/tabs/InsuranceTab.svelte'; import PollutionTab from '$components/tabs/PollutionTab.svelte'; import { vehicleModelStore, vehiclesStore } from '$lib/stores/vehicle'; + import PollutionCertificateModal from '$components/pucc/PollutionCertificateModal.svelte'; + import InsuranceForm from '$components/insurance/InsuranceForm.svelte'; + import InsuranceModal from '$components/insurance/InsuranceModal.svelte'; let vehicles = $state([]); let loading = $state(true); @@ -93,4 +96,6 @@ + +