Loading fuel logs...
+{:else if error}
+ No fuel refill logs found for this vehicle.
+{:else}
+ Loading insurance details...
+{:else if error}
+ No Insurance found for this vehicle.
+{:else}
+ {#each insurances as ins (ins.id)}
+
-
Maintenance History
- {#if loading}
-
Loading maintenance logs...
- {:else if error}
-
{error}
- {:else if maintenanceLogs.length === 0}
-
No maintenance logs for this vehicle.
- {:else}
-
-
-
-
- | Date |
- Odometer |
- Service |
- Cost |
- Notes |
- Actions |
-
-
-
- {#each maintenanceLogs as log (log.id)}
-
- | {formatDate(log.date)} | Loading maintenance logs...
+{:else if error}
+ {error}
+{:else if maintenanceLogs.length === 0}
+ No maintenance logs for this vehicle.
+{:else}
+
+
+
+
+ | Date |
+ Odometer |
+ Service |
+ Cost |
+ Notes |
+ Actions |
+
+
+
+ {#each maintenanceLogs as log (log.id)}
+
+ | {formatDate(log.date)} |
+ {log.odometer} |
+ {log.service} |
+ {formatCurrency(log.cost)} |
+ {log.notes || '-'} |
+
+ |
- {/each}
-
-
-
- {/if}
-
- {#if showEditModal}
-
- {/if}
-
+
+
+ deleteMaintenenceLog(log.id)}
+ >
+
+
+
+
+ {/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 3ec008d9..00000000
--- a/app/client/src/components/pucc/PollutionCertificateDetails.svelte
+++ /dev/null
@@ -1,187 +0,0 @@
-
-
-
-
- Pollution Certificate Details
-
- {#if loading}
-
Loading pollution certificate details...
- {:else if error}
-
{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.
-
-
- Add Pollution Certificate
-
-
- {/if}
-
- {#if showPollutionCertificateFormModal}
-
- {/if}
-
diff --git a/app/client/src/components/pucc/PollutionCertificateForm.svelte b/app/client/src/components/pucc/PollutionCertificateForm.svelte
index 7ea76a67..90a4779e 100644
--- a/app/client/src/components/pucc/PollutionCertificateForm.svelte
+++ b/app/client/src/components/pucc/PollutionCertificateForm.svelte
@@ -1,68 +1,57 @@
-{#if showModal}
-
-
-
- {initialData ? 'Edit' : 'Add'} Pollution Certificate Details
-
+
-
-
-
+{#if status.message}
+
+ {#if status.type === 'ERROR'}
+ Error: {status.message}
+ {:else}
+ {status.message}
+ {/if}
+
{/if}
diff --git a/app/client/src/components/pucc/PollutionCertificateFormComponent.svelte b/app/client/src/components/pucc/PollutionCertificateFormComponent.svelte
deleted file mode 100644
index 4051168d..00000000
--- a/app/client/src/components/pucc/PollutionCertificateFormComponent.svelte
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
diff --git a/app/client/src/components/pucc/PollutionCertificateList.svelte b/app/client/src/components/pucc/PollutionCertificateList.svelte
new file mode 100644
index 00000000..4686faf9
--- /dev/null
+++ b/app/client/src/components/pucc/PollutionCertificateList.svelte
@@ -0,0 +1,162 @@
+
+
+{#if loading}
+
Loading pollution certificate details...
+{:else if error}
+
{error}
+{:else if pollutionCertificates.length === 0}
+
No maintenance logs for this vehicle.
+{:else}
+ {#each pollutionCertificates as pucc (pucc.id)}
+
+
+
+
+ {pucc.certificateNumber}
+
+
+
{
+ puccModelStore.show(vehicleId, pucc, true, (status: boolean) => {
+ if (status) {
+ fetchPollutionCertificateDetails();
+ }
+ });
+ }}
+ >
+
+
+
{
+ deletePollutionCertificate(pucc.id);
+ }}
+ >
+
+
+
+
+
+
+
+ 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/pucc/PollutionCertificateModal.svelte b/app/client/src/components/pucc/PollutionCertificateModal.svelte
new file mode 100644
index 00000000..86801d30
--- /dev/null
+++ b/app/client/src/components/pucc/PollutionCertificateModal.svelte
@@ -0,0 +1,42 @@
+
+
+{#if showModal}
+
+
+
+{/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)}
+ -
+ (activeTab = tab.id)}
+ type="button"
+ role="tab"
+ aria-controls={tab.name}
+ aria-selected={activeTab === tab.id}>{tab.name}
+
+ {/each}
+
diff --git a/app/client/src/components/vehicle/AddVehicleForm.svelte b/app/client/src/components/vehicle/AddVehicleForm.svelte
deleted file mode 100644
index 3e914b4d..00000000
--- a/app/client/src/components/vehicle/AddVehicleForm.svelte
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-{#if showModal}
-
-{/if}
-
-
diff --git a/app/client/src/components/vehicle/VehicleCard.svelte b/app/client/src/components/vehicle/VehicleCard.svelte
index cd284b92..8a4b025a 100644
--- a/app/client/src/components/vehicle/VehicleCard.svelte
+++ b/app/client/src/components/vehicle/VehicleCard.svelte
@@ -12,22 +12,38 @@
Shield,
BadgeCheck
} from '@lucide/svelte';
- import { createEventDispatcher } from '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';
+ import { insuranceModelStore } from '$lib/stores/insurance';
+ import { puccModelStore } from '$lib/stores/pucc';
- export let vehicle: {
- id: number;
- make: string;
- model: string;
- year: number;
- licensePlate: string;
- vin?: string;
- color?: string;
- odometer?: number;
- insuranceStatus?: string;
- puccStatus?: string;
- };
+ const { vehicle, updateCallback } = $props();
- const dispatch = createEventDispatcher();
+ 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.');
+ }
+ }
{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}
@@ -94,43 +105,65 @@
{/if}
-
-
dispatch('editVehicle', { vehicle })}
- aria-label="Edit vehicle"
- >
-
-
-
dispatch('deleteVehicle', { vehicle })}
- aria-label="Delete vehicle"
- >
-
-
-
dispatch('refillFuel', { vehicle })}
- aria-label="Log fuel refill"
- >
-
-
-
-
dispatch('addMaintenance', { vehicle })}
- aria-label="Log maintenance"
- >
-
-
+
+
+ fuelLogModelStore.show(vehicle.id, null, false, updateCallback)}
+ aria-label="Log fuel refill"
+ >
+
+
+
+ maintenanceModelStore.show(vehicle.id, null, false, updateCallback)}
+ aria-label="Log maintenance"
+ >
+
+
+ insuranceModelStore.show(vehicle.id, null, false, updateCallback)}
+ aria-label="Add Insurance"
+ >
+
+
+ puccModelStore.show(vehicle.id, null, false, updateCallback)}
+ aria-label="Add Pollution Certificate"
+ >
+
+
+
+
+
{
+ vehicleModelStore.show(vehicle, true);
+ }}
+ aria-label="Edit vehicle"
+ >
+
+
+
deleteVehicle(vehicle.id)}
+ aria-label="Delete vehicle"
+ >
+
+
+
diff --git a/app/client/src/components/vehicle/VehicleForm.svelte b/app/client/src/components/vehicle/VehicleForm.svelte
index fb398bf9..88ffc4a4 100644
--- a/app/client/src/components/vehicle/VehicleForm.svelte
+++ b/app/client/src/components/vehicle/VehicleForm.svelte
@@ -9,17 +9,93 @@
Building2
} from '@lucide/svelte';
import FormField from '../common/FormField.svelte';
+ import type { NewVehicle, Vehicle } from '$lib/models/vehicle';
+ import { env } from '$env/dynamic/public';
+ import { onMount } from 'svelte';
+ import FormSubmitButton from '$components/common/FormSubmitButton.svelte';
+ import { simulateNetworkDelay } from '$lib/utils/dev';
+ import { vehiclesStore } from '$lib/stores/vehicle';
- export let vehicle;
- export let onSubmit;
- export let error = '';
- export let success = '';
- export let editMode = false;
+ let { vehicleToEdit = null, editMode = false, modalVisibility = $bindable(), loading } = $props();
+
+ const vehicle: NewVehicle = $state({
+ make: '',
+ model: '',
+ year: null,
+ licensePlate: '',
+ vin: '',
+ color: '',
+ odometer: null
+ });
+
+ let status = $state<{
+ message: string | null;
+ type: 'ERROR' | 'SUCCESS' | null;
+ }>({
+ message: null,
+ type: null
+ });
+
+ $effect(() => {
+ if (vehicleToEdit) {
+ Object.assign(vehicle, vehicleToEdit);
+ }
+ });
+
+ async function persistVehicle() {
+ if (!vehicle.make || !vehicle.model || !vehicle.year || !vehicle.licensePlate) {
+ status.message = 'Please fill in all required fields.';
+ status.type = 'ERROR';
+ return;
+ }
+ try {
+ if (loading) return; // Prevent multiple submissions
+ loading = true;
+ status.message = null;
+ status.type = null;
+ // await simulateNetworkDelay(2000); // Simulate network delay for development
+ const response = await fetch(
+ `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${editMode ? vehicleToEdit.id : ''}`,
+ {
+ method: editMode ? 'PUT' : 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-User-PIN': localStorage.getItem('userPin') || ''
+ },
+ body: JSON.stringify(vehicle)
+ }
+ );
+
+ if (response.ok) {
+ status.message = `Vehicle ${editMode ? 'updated' : 'added'} successfully!`;
+ status.type = 'SUCCESS';
+ Object.assign(vehicle, {
+ make: '',
+ model: '',
+ year: null,
+ licensePlate: '',
+ vin: '',
+ color: '',
+ 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 ${editMode ? 'update' : 'add'} vehicle.`;
+ status.type = 'ERROR';
+ }
+ } catch (e) {
+ status.message = 'Failed to connect to the server.';
+ status.type = 'ERROR';
+ }
+ loading = false;
+ }
-{#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..5bb70051 100644
--- a/app/client/src/components/vehicle/VehicleList.svelte
+++ b/app/client/src/components/vehicle/VehicleList.svelte
@@ -1,17 +1,11 @@
@@ -30,13 +24,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..d81c5d55
--- /dev/null
+++ b/app/client/src/components/vehicle/VehicleModal.svelte
@@ -0,0 +1,27 @@
+
+
+{#if showModal}
+