From 209e4c0ab7d6c3fe5176422f0bc630570f610ad3 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Wed, 10 Sep 2025 20:10:03 +0530 Subject: [PATCH 01/27] Removed setup script --- package.json | 3 +- scripts/setup-env.js | 66 -------------------------------------------- 2 files changed, 1 insertion(+), 68 deletions(-) delete mode 100755 scripts/setup-env.js diff --git a/package.json b/package.json index 29f30d4f..6e5effe5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "app/*" ], "scripts": { - "setup": "node scripts/setup-env.js", "test:cors": "node scripts/test-cors.js", "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"", "dev:backend": "npm run dev --workspace=backend", @@ -32,4 +31,4 @@ "dependencies": { "dotenv": "^17.2.2" } -} +} \ No newline at end of file diff --git a/scripts/setup-env.js b/scripts/setup-env.js deleted file mode 100755 index db3497db..00000000 --- a/scripts/setup-env.js +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node - -const fs = require("fs"); -const path = require("path"); - -console.log("Setting up Tracktor development environment..."); - -// Create scripts directory if it doesn't exist -const scriptsDir = path.dirname(__filename); -if (!fs.existsSync(scriptsDir)) { - fs.mkdirSync(scriptsDir, { recursive: true }); -} - -// Check if .env exists in root -const rootEnvPath = path.join(__dirname, "..", ".env"); -const exampleEnvPath = path.join(__dirname, "..", ".env.example"); - -if (!fs.existsSync(rootEnvPath)) { - if (fs.existsSync(exampleEnvPath)) { - console.log("📋 Copying .env.example to .env..."); - fs.copyFileSync(exampleEnvPath, rootEnvPath); - console.log("✅ Created .env file from .env.example"); - } else { - console.log( - "❌ No .env.example file found. Please create a .env file manually." - ); - process.exit(1); - } -} else { - console.log("✅ .env file already exists"); -} - -// Create symbolic link for frontend -const frontendEnvPath = path.join(__dirname, "..", "app", "frontend", ".env"); -const relativeRootEnvPath = "../../.env"; - -try { - // Remove existing symlink or file if it exists - if ( - fs.existsSync(frontendEnvPath) || - fs.lstatSync(frontendEnvPath).isSymbolicLink() - ) { - fs.unlinkSync(frontendEnvPath); - } -} catch (error) { - // File doesn't exist, which is fine -} - -try { - console.log( - "🔗 Creating symbolic link for frontend environment variables..." - ); - fs.symlinkSync(relativeRootEnvPath, frontendEnvPath); - console.log("✅ Created symbolic link: app/frontend/.env -> ../../.env"); -} catch (error) { - console.log("⚠️ Could not create symbolic link:", error.message); - console.log( - " You may need to manually ensure environment variables are available to the frontend." - ); -} - -console.log("🎉 Environment setup complete!"); -console.log(""); -console.log("Next steps:"); -console.log(" 1. Review and update .env file with your configuration"); -console.log(" 2. Run: npm run dev"); From 3721d2a37ef4d9af5d79d5a277dc1d680570231b Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Wed, 10 Sep 2025 20:22:27 +0530 Subject: [PATCH 02/27] Restructure Directories --- .../components/custom}/auth/PinInput.svelte | 0 .../components/custom}/chart/ChartCard.svelte | 0 .../custom}/chart/DashboardCharts.svelte | 0 .../components/custom}/common/Button.svelte | 0 .../components/custom}/common/Checkbox.svelte | 0 .../custom}/common/DeleteConfirmation.svelte | 0 .../custom}/common/FormField.svelte | 0 .../custom}/common/IconButton.svelte | 0 .../custom}/common/ModalContainer.svelte | 0 .../custom}/common/StatusBlock.svelte | 0 .../custom}/common/TabContainer.svelte | 0 .../custom}/common/ThemeToggle.svelte | 0 .../custom}/common/VehicleCard.svelte | 0 .../custom}/forms/ConfigForm.svelte | 0 .../custom}/forms/FuelLogForm.svelte | 0 .../custom}/forms/InsuranceForm.svelte | 0 .../custom}/forms/MaintenanceLogForm.svelte | 0 .../forms/PollutionCertificateForm.svelte | 0 .../custom}/forms/VehicleForm.svelte | 0 .../custom}/lists/FuelLogList.svelte | 0 .../custom}/lists/InsuranceList.svelte | 0 .../custom}/lists/MaintenanceLogList.svelte | 0 .../lists/PollutionCertificateList.svelte | 0 .../custom}/lists/VehicleList.svelte | 0 .../custom}/modals/ConfigModal.svelte | 0 .../custom}/modals/FuelLogModal.svelte | 0 .../custom}/modals/InsuranceModal.svelte | 0 .../custom}/modals/MaintenanceLogModal.svelte | 0 .../modals/PollutionCertificateModal.svelte | 0 .../custom}/modals/VehicleModal.svelte | 0 .../custom}/tabs/DashboardTab.svelte | 0 .../components/custom}/tabs/FuelLogTab.svelte | 0 .../custom}/tabs/InsuranceTab.svelte | 0 .../custom}/tabs/MaintenenceLogTab.svelte | 0 .../custom}/tabs/PollutionTab.svelte | 0 .../components/custom}/tabs/TabHeader.svelte | 0 app/frontend/src/{lib => }/models/Error.ts | 0 app/frontend/src/{lib => }/models/fuel-log.ts | 0 app/frontend/src/{lib => }/models/status.ts | 0 app/frontend/src/{lib => }/models/vehicle.ts | 0 app/frontend/src/services/auth.service.ts | 2 +- app/frontend/src/{lib => }/stores/auth.ts | 0 app/frontend/src/{lib => }/stores/config.ts | 2 +- .../src/{lib => }/stores/dark-mode.ts | 0 app/frontend/src/{lib => }/stores/fuel-log.ts | 0 .../src/{lib => }/stores/insurance.ts | 0 .../src/{lib => }/stores/maintenance.ts | 0 app/frontend/src/{lib => }/stores/pucc.ts | 0 app/frontend/src/{lib => }/stores/vehicle.ts | 4 +- app/frontend/src/styles/app.css | 73 ++++++++++++++++++- app/frontend/src/{lib => }/utils/api.ts | 0 app/frontend/src/{lib => }/utils/dev.ts | 0 .../src/{lib => }/utils/formatting.ts | 2 +- 53 files changed, 76 insertions(+), 7 deletions(-) rename app/frontend/src/{components => lib/components/custom}/auth/PinInput.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/chart/ChartCard.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/chart/DashboardCharts.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/Button.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/Checkbox.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/DeleteConfirmation.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/FormField.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/IconButton.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/ModalContainer.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/StatusBlock.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/TabContainer.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/ThemeToggle.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/common/VehicleCard.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/forms/ConfigForm.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/forms/FuelLogForm.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/forms/InsuranceForm.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/forms/MaintenanceLogForm.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/forms/PollutionCertificateForm.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/forms/VehicleForm.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/lists/FuelLogList.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/lists/InsuranceList.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/lists/MaintenanceLogList.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/lists/PollutionCertificateList.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/lists/VehicleList.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/modals/ConfigModal.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/modals/FuelLogModal.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/modals/InsuranceModal.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/modals/MaintenanceLogModal.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/modals/PollutionCertificateModal.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/modals/VehicleModal.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/tabs/DashboardTab.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/tabs/FuelLogTab.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/tabs/InsuranceTab.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/tabs/MaintenenceLogTab.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/tabs/PollutionTab.svelte (100%) rename app/frontend/src/{components => lib/components/custom}/tabs/TabHeader.svelte (100%) rename app/frontend/src/{lib => }/models/Error.ts (100%) rename app/frontend/src/{lib => }/models/fuel-log.ts (100%) rename app/frontend/src/{lib => }/models/status.ts (100%) rename app/frontend/src/{lib => }/models/vehicle.ts (100%) rename app/frontend/src/{lib => }/stores/auth.ts (100%) rename app/frontend/src/{lib => }/stores/config.ts (97%) rename app/frontend/src/{lib => }/stores/dark-mode.ts (100%) rename app/frontend/src/{lib => }/stores/fuel-log.ts (100%) rename app/frontend/src/{lib => }/stores/insurance.ts (100%) rename app/frontend/src/{lib => }/stores/maintenance.ts (100%) rename app/frontend/src/{lib => }/stores/pucc.ts (100%) rename app/frontend/src/{lib => }/stores/vehicle.ts (96%) rename app/frontend/src/{lib => }/utils/api.ts (100%) rename app/frontend/src/{lib => }/utils/dev.ts (100%) rename app/frontend/src/{lib => }/utils/formatting.ts (98%) diff --git a/app/frontend/src/components/auth/PinInput.svelte b/app/frontend/src/lib/components/custom/auth/PinInput.svelte similarity index 100% rename from app/frontend/src/components/auth/PinInput.svelte rename to app/frontend/src/lib/components/custom/auth/PinInput.svelte diff --git a/app/frontend/src/components/chart/ChartCard.svelte b/app/frontend/src/lib/components/custom/chart/ChartCard.svelte similarity index 100% rename from app/frontend/src/components/chart/ChartCard.svelte rename to app/frontend/src/lib/components/custom/chart/ChartCard.svelte diff --git a/app/frontend/src/components/chart/DashboardCharts.svelte b/app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte similarity index 100% rename from app/frontend/src/components/chart/DashboardCharts.svelte rename to app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte diff --git a/app/frontend/src/components/common/Button.svelte b/app/frontend/src/lib/components/custom/common/Button.svelte similarity index 100% rename from app/frontend/src/components/common/Button.svelte rename to app/frontend/src/lib/components/custom/common/Button.svelte diff --git a/app/frontend/src/components/common/Checkbox.svelte b/app/frontend/src/lib/components/custom/common/Checkbox.svelte similarity index 100% rename from app/frontend/src/components/common/Checkbox.svelte rename to app/frontend/src/lib/components/custom/common/Checkbox.svelte diff --git a/app/frontend/src/components/common/DeleteConfirmation.svelte b/app/frontend/src/lib/components/custom/common/DeleteConfirmation.svelte similarity index 100% rename from app/frontend/src/components/common/DeleteConfirmation.svelte rename to app/frontend/src/lib/components/custom/common/DeleteConfirmation.svelte diff --git a/app/frontend/src/components/common/FormField.svelte b/app/frontend/src/lib/components/custom/common/FormField.svelte similarity index 100% rename from app/frontend/src/components/common/FormField.svelte rename to app/frontend/src/lib/components/custom/common/FormField.svelte diff --git a/app/frontend/src/components/common/IconButton.svelte b/app/frontend/src/lib/components/custom/common/IconButton.svelte similarity index 100% rename from app/frontend/src/components/common/IconButton.svelte rename to app/frontend/src/lib/components/custom/common/IconButton.svelte diff --git a/app/frontend/src/components/common/ModalContainer.svelte b/app/frontend/src/lib/components/custom/common/ModalContainer.svelte similarity index 100% rename from app/frontend/src/components/common/ModalContainer.svelte rename to app/frontend/src/lib/components/custom/common/ModalContainer.svelte diff --git a/app/frontend/src/components/common/StatusBlock.svelte b/app/frontend/src/lib/components/custom/common/StatusBlock.svelte similarity index 100% rename from app/frontend/src/components/common/StatusBlock.svelte rename to app/frontend/src/lib/components/custom/common/StatusBlock.svelte diff --git a/app/frontend/src/components/common/TabContainer.svelte b/app/frontend/src/lib/components/custom/common/TabContainer.svelte similarity index 100% rename from app/frontend/src/components/common/TabContainer.svelte rename to app/frontend/src/lib/components/custom/common/TabContainer.svelte diff --git a/app/frontend/src/components/common/ThemeToggle.svelte b/app/frontend/src/lib/components/custom/common/ThemeToggle.svelte similarity index 100% rename from app/frontend/src/components/common/ThemeToggle.svelte rename to app/frontend/src/lib/components/custom/common/ThemeToggle.svelte diff --git a/app/frontend/src/components/common/VehicleCard.svelte b/app/frontend/src/lib/components/custom/common/VehicleCard.svelte similarity index 100% rename from app/frontend/src/components/common/VehicleCard.svelte rename to app/frontend/src/lib/components/custom/common/VehicleCard.svelte diff --git a/app/frontend/src/components/forms/ConfigForm.svelte b/app/frontend/src/lib/components/custom/forms/ConfigForm.svelte similarity index 100% rename from app/frontend/src/components/forms/ConfigForm.svelte rename to app/frontend/src/lib/components/custom/forms/ConfigForm.svelte diff --git a/app/frontend/src/components/forms/FuelLogForm.svelte b/app/frontend/src/lib/components/custom/forms/FuelLogForm.svelte similarity index 100% rename from app/frontend/src/components/forms/FuelLogForm.svelte rename to app/frontend/src/lib/components/custom/forms/FuelLogForm.svelte diff --git a/app/frontend/src/components/forms/InsuranceForm.svelte b/app/frontend/src/lib/components/custom/forms/InsuranceForm.svelte similarity index 100% rename from app/frontend/src/components/forms/InsuranceForm.svelte rename to app/frontend/src/lib/components/custom/forms/InsuranceForm.svelte diff --git a/app/frontend/src/components/forms/MaintenanceLogForm.svelte b/app/frontend/src/lib/components/custom/forms/MaintenanceLogForm.svelte similarity index 100% rename from app/frontend/src/components/forms/MaintenanceLogForm.svelte rename to app/frontend/src/lib/components/custom/forms/MaintenanceLogForm.svelte diff --git a/app/frontend/src/components/forms/PollutionCertificateForm.svelte b/app/frontend/src/lib/components/custom/forms/PollutionCertificateForm.svelte similarity index 100% rename from app/frontend/src/components/forms/PollutionCertificateForm.svelte rename to app/frontend/src/lib/components/custom/forms/PollutionCertificateForm.svelte diff --git a/app/frontend/src/components/forms/VehicleForm.svelte b/app/frontend/src/lib/components/custom/forms/VehicleForm.svelte similarity index 100% rename from app/frontend/src/components/forms/VehicleForm.svelte rename to app/frontend/src/lib/components/custom/forms/VehicleForm.svelte diff --git a/app/frontend/src/components/lists/FuelLogList.svelte b/app/frontend/src/lib/components/custom/lists/FuelLogList.svelte similarity index 100% rename from app/frontend/src/components/lists/FuelLogList.svelte rename to app/frontend/src/lib/components/custom/lists/FuelLogList.svelte diff --git a/app/frontend/src/components/lists/InsuranceList.svelte b/app/frontend/src/lib/components/custom/lists/InsuranceList.svelte similarity index 100% rename from app/frontend/src/components/lists/InsuranceList.svelte rename to app/frontend/src/lib/components/custom/lists/InsuranceList.svelte diff --git a/app/frontend/src/components/lists/MaintenanceLogList.svelte b/app/frontend/src/lib/components/custom/lists/MaintenanceLogList.svelte similarity index 100% rename from app/frontend/src/components/lists/MaintenanceLogList.svelte rename to app/frontend/src/lib/components/custom/lists/MaintenanceLogList.svelte diff --git a/app/frontend/src/components/lists/PollutionCertificateList.svelte b/app/frontend/src/lib/components/custom/lists/PollutionCertificateList.svelte similarity index 100% rename from app/frontend/src/components/lists/PollutionCertificateList.svelte rename to app/frontend/src/lib/components/custom/lists/PollutionCertificateList.svelte diff --git a/app/frontend/src/components/lists/VehicleList.svelte b/app/frontend/src/lib/components/custom/lists/VehicleList.svelte similarity index 100% rename from app/frontend/src/components/lists/VehicleList.svelte rename to app/frontend/src/lib/components/custom/lists/VehicleList.svelte diff --git a/app/frontend/src/components/modals/ConfigModal.svelte b/app/frontend/src/lib/components/custom/modals/ConfigModal.svelte similarity index 100% rename from app/frontend/src/components/modals/ConfigModal.svelte rename to app/frontend/src/lib/components/custom/modals/ConfigModal.svelte diff --git a/app/frontend/src/components/modals/FuelLogModal.svelte b/app/frontend/src/lib/components/custom/modals/FuelLogModal.svelte similarity index 100% rename from app/frontend/src/components/modals/FuelLogModal.svelte rename to app/frontend/src/lib/components/custom/modals/FuelLogModal.svelte diff --git a/app/frontend/src/components/modals/InsuranceModal.svelte b/app/frontend/src/lib/components/custom/modals/InsuranceModal.svelte similarity index 100% rename from app/frontend/src/components/modals/InsuranceModal.svelte rename to app/frontend/src/lib/components/custom/modals/InsuranceModal.svelte diff --git a/app/frontend/src/components/modals/MaintenanceLogModal.svelte b/app/frontend/src/lib/components/custom/modals/MaintenanceLogModal.svelte similarity index 100% rename from app/frontend/src/components/modals/MaintenanceLogModal.svelte rename to app/frontend/src/lib/components/custom/modals/MaintenanceLogModal.svelte diff --git a/app/frontend/src/components/modals/PollutionCertificateModal.svelte b/app/frontend/src/lib/components/custom/modals/PollutionCertificateModal.svelte similarity index 100% rename from app/frontend/src/components/modals/PollutionCertificateModal.svelte rename to app/frontend/src/lib/components/custom/modals/PollutionCertificateModal.svelte diff --git a/app/frontend/src/components/modals/VehicleModal.svelte b/app/frontend/src/lib/components/custom/modals/VehicleModal.svelte similarity index 100% rename from app/frontend/src/components/modals/VehicleModal.svelte rename to app/frontend/src/lib/components/custom/modals/VehicleModal.svelte diff --git a/app/frontend/src/components/tabs/DashboardTab.svelte b/app/frontend/src/lib/components/custom/tabs/DashboardTab.svelte similarity index 100% rename from app/frontend/src/components/tabs/DashboardTab.svelte rename to app/frontend/src/lib/components/custom/tabs/DashboardTab.svelte diff --git a/app/frontend/src/components/tabs/FuelLogTab.svelte b/app/frontend/src/lib/components/custom/tabs/FuelLogTab.svelte similarity index 100% rename from app/frontend/src/components/tabs/FuelLogTab.svelte rename to app/frontend/src/lib/components/custom/tabs/FuelLogTab.svelte diff --git a/app/frontend/src/components/tabs/InsuranceTab.svelte b/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte similarity index 100% rename from app/frontend/src/components/tabs/InsuranceTab.svelte rename to app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte diff --git a/app/frontend/src/components/tabs/MaintenenceLogTab.svelte b/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte similarity index 100% rename from app/frontend/src/components/tabs/MaintenenceLogTab.svelte rename to app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte diff --git a/app/frontend/src/components/tabs/PollutionTab.svelte b/app/frontend/src/lib/components/custom/tabs/PollutionTab.svelte similarity index 100% rename from app/frontend/src/components/tabs/PollutionTab.svelte rename to app/frontend/src/lib/components/custom/tabs/PollutionTab.svelte diff --git a/app/frontend/src/components/tabs/TabHeader.svelte b/app/frontend/src/lib/components/custom/tabs/TabHeader.svelte similarity index 100% rename from app/frontend/src/components/tabs/TabHeader.svelte rename to app/frontend/src/lib/components/custom/tabs/TabHeader.svelte diff --git a/app/frontend/src/lib/models/Error.ts b/app/frontend/src/models/Error.ts similarity index 100% rename from app/frontend/src/lib/models/Error.ts rename to app/frontend/src/models/Error.ts diff --git a/app/frontend/src/lib/models/fuel-log.ts b/app/frontend/src/models/fuel-log.ts similarity index 100% rename from app/frontend/src/lib/models/fuel-log.ts rename to app/frontend/src/models/fuel-log.ts diff --git a/app/frontend/src/lib/models/status.ts b/app/frontend/src/models/status.ts similarity index 100% rename from app/frontend/src/lib/models/status.ts rename to app/frontend/src/models/status.ts diff --git a/app/frontend/src/lib/models/vehicle.ts b/app/frontend/src/models/vehicle.ts similarity index 100% rename from app/frontend/src/lib/models/vehicle.ts rename to app/frontend/src/models/vehicle.ts diff --git a/app/frontend/src/services/auth.service.ts b/app/frontend/src/services/auth.service.ts index f70dce2c..6e357a96 100644 --- a/app/frontend/src/services/auth.service.ts +++ b/app/frontend/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { getApiUrl } from "$lib/utils/api"; +import { getApiUrl } from "../utils/api"; export const isPinSet = async () => { try { diff --git a/app/frontend/src/lib/stores/auth.ts b/app/frontend/src/stores/auth.ts similarity index 100% rename from app/frontend/src/lib/stores/auth.ts rename to app/frontend/src/stores/auth.ts diff --git a/app/frontend/src/lib/stores/config.ts b/app/frontend/src/stores/config.ts similarity index 97% rename from app/frontend/src/lib/stores/config.ts rename to app/frontend/src/stores/config.ts index 722d1efe..855e6e2b 100644 --- a/app/frontend/src/lib/stores/config.ts +++ b/app/frontend/src/stores/config.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import { browser } from '$app/environment'; -import { getApiUrl } from '$lib/utils/api'; +import { getApiUrl } from '../utils/api'; export interface Config { key: string; diff --git a/app/frontend/src/lib/stores/dark-mode.ts b/app/frontend/src/stores/dark-mode.ts similarity index 100% rename from app/frontend/src/lib/stores/dark-mode.ts rename to app/frontend/src/stores/dark-mode.ts diff --git a/app/frontend/src/lib/stores/fuel-log.ts b/app/frontend/src/stores/fuel-log.ts similarity index 100% rename from app/frontend/src/lib/stores/fuel-log.ts rename to app/frontend/src/stores/fuel-log.ts diff --git a/app/frontend/src/lib/stores/insurance.ts b/app/frontend/src/stores/insurance.ts similarity index 100% rename from app/frontend/src/lib/stores/insurance.ts rename to app/frontend/src/stores/insurance.ts diff --git a/app/frontend/src/lib/stores/maintenance.ts b/app/frontend/src/stores/maintenance.ts similarity index 100% rename from app/frontend/src/lib/stores/maintenance.ts rename to app/frontend/src/stores/maintenance.ts diff --git a/app/frontend/src/lib/stores/pucc.ts b/app/frontend/src/stores/pucc.ts similarity index 100% rename from app/frontend/src/lib/stores/pucc.ts rename to app/frontend/src/stores/pucc.ts diff --git a/app/frontend/src/lib/stores/vehicle.ts b/app/frontend/src/stores/vehicle.ts similarity index 96% rename from app/frontend/src/lib/stores/vehicle.ts rename to app/frontend/src/stores/vehicle.ts index 324104ca..28721c7a 100644 --- a/app/frontend/src/lib/stores/vehicle.ts +++ b/app/frontend/src/stores/vehicle.ts @@ -1,6 +1,6 @@ import { goto } from '$app/navigation'; -import type { Vehicle } from '$lib/models/vehicle'; -import { getApiUrl } from '$lib/utils/api'; +import type { Vehicle } from '../models/vehicle'; +import { getApiUrl } from '../utils/api'; import { writable } from 'svelte/store'; const createVehicleModalStore = () => { diff --git a/app/frontend/src/styles/app.css b/app/frontend/src/styles/app.css index 2fdfdd45..ab21b1aa 100644 --- a/app/frontend/src/styles/app.css +++ b/app/frontend/src/styles/app.css @@ -5,7 +5,76 @@ @custom-variant dark (&:where(.dark, .dark *)); +:root { + --radius: 0.65rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.795 0.184 86.047); + --primary-foreground: oklch(0.421 0.095 57.708); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.795 0.184 86.047); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.795 0.184 86.047); + --sidebar-primary-foreground: oklch(0.421 0.095 57.708); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.795 0.184 86.047); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.795 0.184 86.047); + --primary-foreground: oklch(0.421 0.095 57.708); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.554 0.135 66.442); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.795 0.184 86.047); + --sidebar-primary-foreground: oklch(0.421 0.095 57.708); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.554 0.135 66.442); +} + + * { font-family: 'Finlandica', sans-serif; -} - +} \ No newline at end of file diff --git a/app/frontend/src/lib/utils/api.ts b/app/frontend/src/utils/api.ts similarity index 100% rename from app/frontend/src/lib/utils/api.ts rename to app/frontend/src/utils/api.ts diff --git a/app/frontend/src/lib/utils/dev.ts b/app/frontend/src/utils/dev.ts similarity index 100% rename from app/frontend/src/lib/utils/dev.ts rename to app/frontend/src/utils/dev.ts diff --git a/app/frontend/src/lib/utils/formatting.ts b/app/frontend/src/utils/formatting.ts similarity index 98% rename from app/frontend/src/lib/utils/formatting.ts rename to app/frontend/src/utils/formatting.ts index bb4cffcc..daf89559 100644 --- a/app/frontend/src/lib/utils/formatting.ts +++ b/app/frontend/src/utils/formatting.ts @@ -1,4 +1,4 @@ -import { config } from '$lib/stores/config'; +import { config } from '../stores/config'; import { format } from 'date-fns'; export interface ConfigStore { From 898864d448d0bb555a9e1f15cc322922a27d02b2 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Wed, 10 Sep 2025 20:56:30 +0530 Subject: [PATCH 03/27] Fixed broken links --- app/frontend/components.json | 16 ++++++ app/frontend/package.json | 6 +- .../custom/chart/DashboardCharts.svelte | 4 +- .../custom/common/ThemeToggle.svelte | 2 +- .../custom/common/VehicleCard.svelte | 12 ++-- .../components/custom/forms/ConfigForm.svelte | 4 +- .../custom/forms/FuelLogForm.svelte | 17 ++---- .../custom/forms/InsuranceForm.svelte | 12 ++-- .../custom/forms/MaintenanceLogForm.svelte | 12 ++-- .../forms/PollutionCertificateForm.svelte | 12 ++-- .../custom/forms/VehicleForm.svelte | 14 ++--- .../custom/lists/FuelLogList.svelte | 8 +-- .../custom/lists/InsuranceList.svelte | 8 +-- .../custom/lists/MaintenanceLogList.svelte | 8 +-- .../lists/PollutionCertificateList.svelte | 8 +-- .../custom/lists/VehicleList.svelte | 4 +- .../custom/modals/ConfigModal.svelte | 6 +- .../custom/modals/FuelLogModal.svelte | 6 +- .../custom/modals/InsuranceModal.svelte | 6 +- .../custom/modals/MaintenanceLogModal.svelte | 6 +- .../modals/PollutionCertificateModal.svelte | 6 +- .../custom/modals/VehicleModal.svelte | 6 +- .../custom/tabs/DashboardTab.svelte | 4 +- .../components/custom/tabs/FuelLogTab.svelte | 4 +- .../custom/tabs/InsuranceTab.svelte | 4 +- .../custom/tabs/MaintenenceLogTab.svelte | 4 +- .../custom/tabs/PollutionTab.svelte | 4 +- app/frontend/src/lib/utils.ts | 13 +++++ app/frontend/src/routes/+layout.svelte | 8 +-- .../src/routes/dashboard/+page.svelte | 32 +++++------ app/frontend/src/routes/login/+page.svelte | 12 ++-- app/frontend/src/styles/app.css | 56 +++++++++++++++++-- app/frontend/svelte.config.js | 8 ++- package-lock.json | 54 ++++++++++++++++-- 34 files changed, 255 insertions(+), 131 deletions(-) create mode 100644 app/frontend/components.json create mode 100644 app/frontend/src/lib/utils.ts diff --git a/app/frontend/components.json b/app/frontend/components.json new file mode 100644 index 00000000..c2a47837 --- /dev/null +++ b/app/frontend/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/styles/app.css", + "baseColor": "zinc" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} \ No newline at end of file diff --git a/app/frontend/package.json b/app/frontend/package.json index ae4c9cee..3489a5b4 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@eslint/js": "^9.34.0", + "@lucide/svelte": "^0.543.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -25,6 +26,7 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "chart.js": "^4.5.0", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "eslint": "^9.34.0", "eslint-config-prettier": "^10.1.8", @@ -37,13 +39,15 @@ "svelte": "^5.0.0", "svelte-check": "^4.0.0", "svelte-eslint-parser": "^1.3.1", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^3.1.1", "tailwindcss": "^4.0.0", + "tw-animate-css": "^1.3.8", "typescript": "^5.0.0", "vite": "^7.0.4", "vite-plugin-lucide-preprocess": "^1.4.3" }, "dependencies": { - "@lucide/svelte": "^0.525.0", "dot-env": "^0.0.1", "svelte-loading-spinners": "^0.3.6", "svelte5-chartjs": "^1.0.0" diff --git a/app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte b/app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte index 5fa9114d..833c88e4 100644 --- a/app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte +++ b/app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte @@ -1,8 +1,8 @@ diff --git a/app/frontend/src/lib/components/custom/tabs/FuelLogTab.svelte b/app/frontend/src/lib/components/custom/tabs/FuelLogTab.svelte index deab4476..4edf4edd 100644 --- a/app/frontend/src/lib/components/custom/tabs/FuelLogTab.svelte +++ b/app/frontend/src/lib/components/custom/tabs/FuelLogTab.svelte @@ -1,6 +1,6 @@ diff --git a/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte b/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte index 4536a2c8..24e18037 100644 --- a/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte +++ b/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte @@ -1,6 +1,6 @@ diff --git a/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte b/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte index 3f96f788..14160b0b 100644 --- a/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte +++ b/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte @@ -1,6 +1,6 @@ diff --git a/app/frontend/src/lib/components/custom/tabs/PollutionTab.svelte b/app/frontend/src/lib/components/custom/tabs/PollutionTab.svelte index f1e066ec..541e4ec2 100644 --- a/app/frontend/src/lib/components/custom/tabs/PollutionTab.svelte +++ b/app/frontend/src/lib/components/custom/tabs/PollutionTab.svelte @@ -1,6 +1,6 @@ diff --git a/app/frontend/src/lib/utils.ts b/app/frontend/src/lib/utils.ts new file mode 100644 index 00000000..55b3a918 --- /dev/null +++ b/app/frontend/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte index ee7c2be1..af87d670 100644 --- a/app/frontend/src/routes/+layout.svelte +++ b/app/frontend/src/routes/+layout.svelte @@ -5,12 +5,12 @@ import '../styles/app.css'; import { tick } from 'svelte'; import { LogOut, Tractor, Settings } from '@lucide/svelte'; - import ThemeToggle from '$components/common/ThemeToggle.svelte'; + import ThemeToggle from '$appui/common/ThemeToggle.svelte'; import { env } from '$env/dynamic/public'; import { Jumper } from 'svelte-loading-spinners'; - import { configModelStore } from '$lib/stores/config'; - import { vehiclesStore } from '$lib/stores/vehicle'; - import IconButton from '$components/common/IconButton.svelte'; + import { configModelStore } from '$stores/config'; + import { vehiclesStore } from '$stores/vehicle'; + import IconButton from '$appui/common/IconButton.svelte'; let { children } = $props(); diff --git a/app/frontend/src/routes/dashboard/+page.svelte b/app/frontend/src/routes/dashboard/+page.svelte index c0237cdf..bdc73389 100644 --- a/app/frontend/src/routes/dashboard/+page.svelte +++ b/app/frontend/src/routes/dashboard/+page.svelte @@ -1,23 +1,23 @@ - + diff --git a/app/frontend/src/lib/components/ui/button/button.svelte b/app/frontend/src/lib/components/ui/button/button.svelte new file mode 100644 index 00000000..4daf4535 --- /dev/null +++ b/app/frontend/src/lib/components/ui/button/button.svelte @@ -0,0 +1,80 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/app/frontend/src/lib/components/ui/button/index.ts b/app/frontend/src/lib/components/ui/button/index.ts new file mode 100644 index 00000000..fb585d76 --- /dev/null +++ b/app/frontend/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from "./button.svelte"; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, +}; diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte index af87d670..dbde616e 100644 --- a/app/frontend/src/routes/+layout.svelte +++ b/app/frontend/src/routes/+layout.svelte @@ -2,6 +2,7 @@ import { page } from '$app/state'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; + import { ModeWatcher } from 'mode-watcher'; import '../styles/app.css'; import { tick } from 'svelte'; import { LogOut, Tractor, Settings } from '@lucide/svelte'; @@ -50,6 +51,7 @@ }; + {#if demoMode}
=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "app/frontend/node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", + "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -5498,6 +5527,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", @@ -6379,6 +6414,19 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/mode-watcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz", + "integrity": "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==", + "license": "MIT", + "dependencies": { + "runed": "^0.25.0", + "svelte-toolbelt": "^0.7.1" + }, + "peerDependencies": { + "svelte": "^5.27.0" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -7524,6 +7572,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runed": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.25.0.tgz", + "integrity": "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -8097,6 +8160,15 @@ "node": ">=0.10.0" } }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -8251,6 +8323,41 @@ "integrity": "sha512-mthHQ2TwiwzTWzbFry3CBnVEfzqPOD9WkVw84OfSYzHRq6N9wgQ+yv37u81uPeuLU/ZOIPqhujpXquB1aol5ZQ==", "license": "MIT" }, + "node_modules/svelte-toolbelt": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", + "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.23.2", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/svelte/node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", @@ -8281,26 +8388,6 @@ "url": "https://github.com/sponsors/dcastil" } }, - "node_modules/tailwind-variants": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.1.1.tgz", - "integrity": "sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.x", - "pnpm": ">=7.x" - }, - "peerDependencies": { - "tailwind-merge": ">=3.0.0", - "tailwindcss": "*" - }, - "peerDependenciesMeta": { - "tailwind-merge": { - "optional": true - } - } - }, "node_modules/tailwindcss": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", From 33a1f1ba5571f96bcc32d765a09d72213dba2f13 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Thu, 11 Sep 2025 11:38:44 +0530 Subject: [PATCH 07/27] Fixed Authentication screen --- app/backend/src/db/seeders/index.ts | 3 +- app/backend/src/routes/pinRoutes.ts | 3 +- app/frontend/package.json | 5 +- .../components/custom/auth/PinInput.svelte | 67 ++++--- .../components/custom/forms/login-form.svelte | 24 +++ .../lib/components/ui/card/card-action.svelte | 20 ++ .../components/ui/card/card-content.svelte | 15 ++ .../ui/card/card-description.svelte | 20 ++ .../lib/components/ui/card/card-footer.svelte | 20 ++ .../lib/components/ui/card/card-header.svelte | 23 +++ .../lib/components/ui/card/card-title.svelte | 20 ++ .../src/lib/components/ui/card/card.svelte | 23 +++ .../src/lib/components/ui/card/index.ts | 25 +++ .../src/lib/components/ui/input-otp/index.ts | 15 ++ .../ui/input-otp/input-otp-group.svelte | 20 ++ .../ui/input-otp/input-otp-separator.svelte | 19 ++ .../ui/input-otp/input-otp-slot.svelte | 31 ++++ .../components/ui/input-otp/input-otp.svelte | 22 +++ .../src/lib/components/ui/input/index.ts | 7 + .../src/lib/components/ui/input/input.svelte | 51 ++++++ .../src/lib/components/ui/label/index.ts | 7 + .../src/lib/components/ui/label/label.svelte | 20 ++ .../src/lib/components/ui/sonner/index.ts | 1 + .../lib/components/ui/sonner/sonner.svelte | 13 ++ app/frontend/src/routes/+layout.svelte | 7 +- app/frontend/src/routes/+page.svelte | 2 +- app/frontend/src/routes/login/+page.svelte | 162 +++------------- app/frontend/src/styles/app.css | 4 +- package-lock.json | 173 ++++++++++++++++-- 29 files changed, 628 insertions(+), 194 deletions(-) create mode 100644 app/frontend/src/lib/components/custom/forms/login-form.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card-action.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card-content.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card-description.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card-footer.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card-header.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card-title.svelte create mode 100644 app/frontend/src/lib/components/ui/card/card.svelte create mode 100644 app/frontend/src/lib/components/ui/card/index.ts create mode 100644 app/frontend/src/lib/components/ui/input-otp/index.ts create mode 100644 app/frontend/src/lib/components/ui/input-otp/input-otp-group.svelte create mode 100644 app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte create mode 100644 app/frontend/src/lib/components/ui/input-otp/input-otp-slot.svelte create mode 100644 app/frontend/src/lib/components/ui/input-otp/input-otp.svelte create mode 100644 app/frontend/src/lib/components/ui/input/index.ts create mode 100644 app/frontend/src/lib/components/ui/input/input.svelte create mode 100644 app/frontend/src/lib/components/ui/label/index.ts create mode 100644 app/frontend/src/lib/components/ui/label/label.svelte create mode 100644 app/frontend/src/lib/components/ui/sonner/index.ts create mode 100644 app/frontend/src/lib/components/ui/sonner/sonner.svelte diff --git a/app/backend/src/db/seeders/index.ts b/app/backend/src/db/seeders/index.ts index 1248717c..3fd04c60 100644 --- a/app/backend/src/db/seeders/index.ts +++ b/app/backend/src/db/seeders/index.ts @@ -17,7 +17,7 @@ export const seedData = async () => { const pinEnv = process.env.AUTH_PIN; const enforceEnv = process.env.FORCE_DEMO_SEED_DATA; const demoMode = process.env.PUBLIC_DEMO_MODE; - if (pinEnv && pinEnv.trim().length == 6) await seedAuthPin(pinEnv); + if (!demoMode && (pinEnv && pinEnv.trim().length == 6)) await seedAuthPin(pinEnv); if (demoMode == "true") await seedDemoData(enforceEnv == "true"); }; @@ -32,6 +32,7 @@ const seedAuthPin = async (pin: string) => { }; const seedDemoData = async (enforce: boolean = false) => { + seedAuthPin('123456') if (!enforce) { const existingVehicles = await db.$count(vehicleTable); if (existingVehicles > 0) { diff --git a/app/backend/src/routes/pinRoutes.ts b/app/backend/src/routes/pinRoutes.ts index 87b4247e..9148d92b 100644 --- a/app/backend/src/routes/pinRoutes.ts +++ b/app/backend/src/routes/pinRoutes.ts @@ -5,9 +5,8 @@ import rateLimit from "express-rate-limit"; const router = Router(); -// Rate limiter for /pin/verify to prevent brute-force/DoS attacks const pinVerifyLimiter = rateLimit({ - windowMs: 5 * 60 * 1000, // 5 minutes + windowMs: 5 * 1000, // 5 seconds max: 5, // limit each IP to 5 requests per windowMs message: "Too many PIN verification attempts, please try again later." }); diff --git a/app/frontend/package.json b/app/frontend/package.json index f02d14fd..cdef5e25 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -16,7 +16,8 @@ }, "devDependencies": { "@eslint/js": "^9.34.0", - "@lucide/svelte": "^0.543.0", + "@internationalized/date": "^3.9.0", + "@lucide/svelte": "^0.515.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -25,6 +26,7 @@ "@tailwindcss/vite": "^4.0.0", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", + "bits-ui": "^2.9.6", "chart.js": "^4.5.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -39,6 +41,7 @@ "svelte": "^5.0.0", "svelte-check": "^4.0.0", "svelte-eslint-parser": "^1.3.1", + "svelte-sonner": "^1.0.5", "tailwind-merge": "^3.3.1", "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", diff --git a/app/frontend/src/lib/components/custom/auth/PinInput.svelte b/app/frontend/src/lib/components/custom/auth/PinInput.svelte index 9ba84eac..81da53cb 100644 --- a/app/frontend/src/lib/components/custom/auth/PinInput.svelte +++ b/app/frontend/src/lib/components/custom/auth/PinInput.svelte @@ -1,40 +1,37 @@ + let pin = $state(''); -
- {#each pin as _, i} - onInput(e, i)} - on:keydown={(e) => onKeydown(e, i)} - aria-label={`PIN digit ${i + 1}`} - /> - {/each} -
+ const handleChange = () => { + if (pin.length == 6) { + oncomplete(pin); + } + }; + - + + {#snippet children({ cells })} + {#each cells as cell (cell)} + + + + {/each} + {/snippet} + diff --git a/app/frontend/src/lib/components/custom/forms/login-form.svelte b/app/frontend/src/lib/components/custom/forms/login-form.svelte new file mode 100644 index 00000000..ca70cf92 --- /dev/null +++ b/app/frontend/src/lib/components/custom/forms/login-form.svelte @@ -0,0 +1,24 @@ + + + +
+ +
+ +
+ Tracktor +
+

Welcome to Tracktor

+
+

+ Enter your 6-digit PIN to access the app +

+ +
diff --git a/app/frontend/src/lib/components/ui/card/card-action.svelte b/app/frontend/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 00000000..cc36c566 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/card/card-content.svelte b/app/frontend/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 00000000..bc90b837 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,15 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/card/card-description.svelte b/app/frontend/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 00000000..9b20ac70 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,20 @@ + + +

+ {@render children?.()} +

diff --git a/app/frontend/src/lib/components/ui/card/card-footer.svelte b/app/frontend/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 00000000..cf433539 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/card/card-header.svelte b/app/frontend/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 00000000..8a91abbf --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/card/card-title.svelte b/app/frontend/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 00000000..22586e61 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/card/card.svelte b/app/frontend/src/lib/components/ui/card/card.svelte new file mode 100644 index 00000000..99448cc9 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/card.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/card/index.ts b/app/frontend/src/lib/components/ui/card/index.ts new file mode 100644 index 00000000..4d3fce48 --- /dev/null +++ b/app/frontend/src/lib/components/ui/card/index.ts @@ -0,0 +1,25 @@ +import Root from "./card.svelte"; +import Content from "./card-content.svelte"; +import Description from "./card-description.svelte"; +import Footer from "./card-footer.svelte"; +import Header from "./card-header.svelte"; +import Title from "./card-title.svelte"; +import Action from "./card-action.svelte"; + +export { + Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // + Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, + Header as CardHeader, + Title as CardTitle, + Action as CardAction, +}; diff --git a/app/frontend/src/lib/components/ui/input-otp/index.ts b/app/frontend/src/lib/components/ui/input-otp/index.ts new file mode 100644 index 00000000..e9ae273e --- /dev/null +++ b/app/frontend/src/lib/components/ui/input-otp/index.ts @@ -0,0 +1,15 @@ +import Root from "./input-otp.svelte"; +import Group from "./input-otp-group.svelte"; +import Slot from "./input-otp-slot.svelte"; +import Separator from "./input-otp-separator.svelte"; + +export { + Root, + Group, + Slot, + Separator, + Root as InputOTP, + Group as InputOTPGroup, + Slot as InputOTPSlot, + Separator as InputOTPSeparator, +}; diff --git a/app/frontend/src/lib/components/ui/input-otp/input-otp-group.svelte b/app/frontend/src/lib/components/ui/input-otp/input-otp-group.svelte new file mode 100644 index 00000000..9b311bc6 --- /dev/null +++ b/app/frontend/src/lib/components/ui/input-otp/input-otp-group.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte b/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte new file mode 100644 index 00000000..a4b0a609 --- /dev/null +++ b/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte @@ -0,0 +1,19 @@ + + +
+ {#if children} + {@render children?.()} + {:else} + + {/if} +
diff --git a/app/frontend/src/lib/components/ui/input-otp/input-otp-slot.svelte b/app/frontend/src/lib/components/ui/input-otp/input-otp-slot.svelte new file mode 100644 index 00000000..34d26368 --- /dev/null +++ b/app/frontend/src/lib/components/ui/input-otp/input-otp-slot.svelte @@ -0,0 +1,31 @@ + + + + {cell.char} + {#if cell.hasFakeCaret} +
+ +
+ {/if} +
diff --git a/app/frontend/src/lib/components/ui/input-otp/input-otp.svelte b/app/frontend/src/lib/components/ui/input-otp/input-otp.svelte new file mode 100644 index 00000000..e27cbcc3 --- /dev/null +++ b/app/frontend/src/lib/components/ui/input-otp/input-otp.svelte @@ -0,0 +1,22 @@ + + + diff --git a/app/frontend/src/lib/components/ui/input/index.ts b/app/frontend/src/lib/components/ui/input/index.ts new file mode 100644 index 00000000..f47b6d3f --- /dev/null +++ b/app/frontend/src/lib/components/ui/input/index.ts @@ -0,0 +1,7 @@ +import Root from "./input.svelte"; + +export { + Root, + // + Root as Input, +}; diff --git a/app/frontend/src/lib/components/ui/input/input.svelte b/app/frontend/src/lib/components/ui/input/input.svelte new file mode 100644 index 00000000..19c6daeb --- /dev/null +++ b/app/frontend/src/lib/components/ui/input/input.svelte @@ -0,0 +1,51 @@ + + +{#if type === "file"} + +{:else} + +{/if} diff --git a/app/frontend/src/lib/components/ui/label/index.ts b/app/frontend/src/lib/components/ui/label/index.ts new file mode 100644 index 00000000..8bfca0b3 --- /dev/null +++ b/app/frontend/src/lib/components/ui/label/index.ts @@ -0,0 +1,7 @@ +import Root from "./label.svelte"; + +export { + Root, + // + Root as Label, +}; diff --git a/app/frontend/src/lib/components/ui/label/label.svelte b/app/frontend/src/lib/components/ui/label/label.svelte new file mode 100644 index 00000000..d0afda3d --- /dev/null +++ b/app/frontend/src/lib/components/ui/label/label.svelte @@ -0,0 +1,20 @@ + + + diff --git a/app/frontend/src/lib/components/ui/sonner/index.ts b/app/frontend/src/lib/components/ui/sonner/index.ts new file mode 100644 index 00000000..1ad9f4a2 --- /dev/null +++ b/app/frontend/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/app/frontend/src/lib/components/ui/sonner/sonner.svelte b/app/frontend/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 00000000..1f50e1e7 --- /dev/null +++ b/app/frontend/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,13 @@ + + + diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte index dbde616e..7ee137a4 100644 --- a/app/frontend/src/routes/+layout.svelte +++ b/app/frontend/src/routes/+layout.svelte @@ -12,6 +12,7 @@ import { configModelStore } from '$stores/config'; import { vehiclesStore } from '$stores/vehicle'; import IconButton from '$appui/common/IconButton.svelte'; + import { Toaster } from '$lib/components/ui/sonner'; let { children } = $props(); @@ -52,6 +53,7 @@ + {#if demoMode}
{/if} {#if checkingAuth} -
+

Validating Auth...

{:else if isAuthenticated} -
+ {:else} diff --git a/app/frontend/src/routes/+page.svelte b/app/frontend/src/routes/+page.svelte index 261b7e41..3404950b 100644 --- a/app/frontend/src/routes/+page.svelte +++ b/app/frontend/src/routes/+page.svelte @@ -13,7 +13,7 @@ } -
+

Redirecting...

diff --git a/app/frontend/src/routes/login/+page.svelte b/app/frontend/src/routes/login/+page.svelte index a68ca73c..f173ae25 100644 --- a/app/frontend/src/routes/login/+page.svelte +++ b/app/frontend/src/routes/login/+page.svelte @@ -1,152 +1,34 @@ -
-
- -
-
-
-

- Welcome -

+
+
+
+
- - {#if checkingPinStatus} -

Checking PIN status...

- {:else if pinExists} -

- - Enter your 6-digit PIN to access Tracktor -

- {/if} - - {#if !checkingPinStatus && pinExists} - handlePinComplete(pin)} /> - {/if} - - {#if loading} -

- -

- {/if} - +
diff --git a/app/frontend/src/styles/app.css b/app/frontend/src/styles/app.css index 4c0c574d..6e23e888 100644 --- a/app/frontend/src/styles/app.css +++ b/app/frontend/src/styles/app.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Epunda+Sans:ital,wght@0,300..900;1,300..900&display=swap'); + @import "tailwindcss"; @import "tw-animate-css"; @@ -122,5 +124,5 @@ } * { - font-family: 'Finlandica', sans-serif; + font-family: "Epunda Sans", sans-serif; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 16579b4e..4decadc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -419,7 +419,8 @@ }, "devDependencies": { "@eslint/js": "^9.34.0", - "@lucide/svelte": "^0.543.0", + "@internationalized/date": "^3.9.0", + "@lucide/svelte": "^0.515.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -428,6 +429,7 @@ "@tailwindcss/vite": "^4.0.0", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", + "bits-ui": "^2.9.6", "chart.js": "^4.5.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -442,6 +444,7 @@ "svelte": "^5.0.0", "svelte-check": "^4.0.0", "svelte-eslint-parser": "^1.3.1", + "svelte-sonner": "^1.0.5", "tailwind-merge": "^3.3.1", "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", @@ -451,6 +454,16 @@ "vite-plugin-lucide-preprocess": "^1.4.3" } }, + "app/frontend/node_modules/@lucide/svelte": { + "version": "0.515.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz", + "integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "app/frontend/node_modules/tailwind-variants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", @@ -1546,6 +1559,34 @@ "npm": ">=10" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -1619,6 +1660,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@internationalized/date": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.9.0.tgz", + "integrity": "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1863,16 +1914,6 @@ "win32" ] }, - "node_modules/@lucide/svelte": { - "version": "0.543.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.543.0.tgz", - "integrity": "sha512-V9XJLRuC5pq9o4OuoMlGwdKwBClZyJnygRf9BaiJuetiptWAybQHvMoXDBm5wy+zZFgZb6LIaO/3HIvHF7/2NA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "svelte": "^5" - } - }, "node_modules/@neon-rs/load": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", @@ -2437,6 +2478,16 @@ "vite": "^6.3.0 || ^7.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@tailwindcss/forms": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", @@ -3458,6 +3509,69 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/bits-ui": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.9.6.tgz", + "integrity": "sha512-OzHktsQRsIz/hIMk5VwHo96Wpp/KY68q/ebUPUzTbvuFBrALB/X+QvO4KLgdczj5dfb3xHs9zpWq8yMH8ZbZlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.29.1", + "svelte-toolbelt": "^0.9.3", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/bits-ui/node_modules/runed": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.29.2.tgz", + "integrity": "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/bits-ui/node_modules/svelte-toolbelt": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.9.3.tgz", + "integrity": "sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.29.0", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.30.2" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -8323,6 +8437,36 @@ "integrity": "sha512-mthHQ2TwiwzTWzbFry3CBnVEfzqPOD9WkVw84OfSYzHRq6N9wgQ+yv37u81uPeuLU/ZOIPqhujpXquB1aol5ZQ==", "license": "MIT" }, + "node_modules/svelte-sonner": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz", + "integrity": "sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.28.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-sonner/node_modules/runed": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz", + "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/svelte-toolbelt": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", @@ -8377,6 +8521,13 @@ "svelte": "^5.0.0" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", From e1ffe1a31786051570fac27ba462e9dee2c6c1fb Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Thu, 11 Sep 2025 12:26:47 +0530 Subject: [PATCH 08/27] Fixed demo mode alert with layouts --- .../{login-form.svelte => LoginForm.svelte} | 1 + .../ui/alert/alert-description.svelte | 23 +++++ .../components/ui/alert/alert-title.svelte | 20 +++++ .../src/lib/components/ui/alert/alert.svelte | 44 ++++++++++ .../src/lib/components/ui/alert/index.ts | 14 +++ app/frontend/src/routes/+layout.svelte | 83 +++--------------- .../src/routes/dashboard/+layout.svelte | 85 +++++++++++++++++++ app/frontend/src/routes/login/+page.svelte | 6 +- 8 files changed, 202 insertions(+), 74 deletions(-) rename app/frontend/src/lib/components/custom/forms/{login-form.svelte => LoginForm.svelte} (94%) create mode 100644 app/frontend/src/lib/components/ui/alert/alert-description.svelte create mode 100644 app/frontend/src/lib/components/ui/alert/alert-title.svelte create mode 100644 app/frontend/src/lib/components/ui/alert/alert.svelte create mode 100644 app/frontend/src/lib/components/ui/alert/index.ts create mode 100644 app/frontend/src/routes/dashboard/+layout.svelte diff --git a/app/frontend/src/lib/components/custom/forms/login-form.svelte b/app/frontend/src/lib/components/custom/forms/LoginForm.svelte similarity index 94% rename from app/frontend/src/lib/components/custom/forms/login-form.svelte rename to app/frontend/src/lib/components/custom/forms/LoginForm.svelte index ca70cf92..e5457083 100644 --- a/app/frontend/src/lib/components/custom/forms/login-form.svelte +++ b/app/frontend/src/lib/components/custom/forms/LoginForm.svelte @@ -3,6 +3,7 @@ import { ShieldEllipsis, Tractor } from '@lucide/svelte'; import PinInput from '../auth/PinInput.svelte'; import * as Card from '$lib/components/ui/card'; + import ThemeToggle from '../common/ThemeToggle.svelte'; let { class: className = '', oncomplete, ...restProps } = $props(); diff --git a/app/frontend/src/lib/components/ui/alert/alert-description.svelte b/app/frontend/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 00000000..8b56aed2 --- /dev/null +++ b/app/frontend/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/alert/alert-title.svelte b/app/frontend/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 00000000..77e45ad5 --- /dev/null +++ b/app/frontend/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/alert/alert.svelte b/app/frontend/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 00000000..2b2eff9a --- /dev/null +++ b/app/frontend/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,44 @@ + + + + + diff --git a/app/frontend/src/lib/components/ui/alert/index.ts b/app/frontend/src/lib/components/ui/alert/index.ts new file mode 100644 index 00000000..97e21b4e --- /dev/null +++ b/app/frontend/src/lib/components/ui/alert/index.ts @@ -0,0 +1,14 @@ +import Root from "./alert.svelte"; +import Description from "./alert-description.svelte"; +import Title from "./alert-title.svelte"; +export { alertVariants, type AlertVariant } from "./alert.svelte"; + +export { + Root, + Description, + Title, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle, +}; diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte index 7ee137a4..760877d0 100644 --- a/app/frontend/src/routes/+layout.svelte +++ b/app/frontend/src/routes/+layout.svelte @@ -5,14 +5,11 @@ import { ModeWatcher } from 'mode-watcher'; import '../styles/app.css'; import { tick } from 'svelte'; - import { LogOut, Tractor, Settings } from '@lucide/svelte'; - import ThemeToggle from '$appui/common/ThemeToggle.svelte'; + import { TriangleAlert } from '@lucide/svelte'; import { env } from '$env/dynamic/public'; import { Jumper } from 'svelte-loading-spinners'; - import { configModelStore } from '$stores/config'; - import { vehiclesStore } from '$stores/vehicle'; - import IconButton from '$appui/common/IconButton.svelte'; import { Toaster } from '$lib/components/ui/sonner'; + import * as Alert from '$lib/components/ui/alert/index.js'; let { children } = $props(); @@ -36,35 +33,20 @@ checkingAuth = false; }); }); - - function logout() { - if (browser) { - localStorage.removeItem('userPin'); - isAuthenticated = false; - } - } - - const fetchVehicles = () => { - if (browser) { - const pin = localStorage.getItem('userPin') || undefined; - if (pin) vehiclesStore.fetchVehicles(pin); - } - }; - + {#if demoMode} -
- - ⚠️ NOTICE: This is a demo instance. Data will be reset periodically and is not saved - permanently. Please avoid adding any persoanl info. -
- Default PIN : 123456 +
+ + + + 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} {#if checkingAuth} @@ -72,46 +54,5 @@

Validating Auth...

-{:else if isAuthenticated} -
-
- -
-
- {@render children()} - -
-
-{:else} - - {@render children()} {/if} +{@render children()} diff --git a/app/frontend/src/routes/dashboard/+layout.svelte b/app/frontend/src/routes/dashboard/+layout.svelte new file mode 100644 index 00000000..bc2bc6a9 --- /dev/null +++ b/app/frontend/src/routes/dashboard/+layout.svelte @@ -0,0 +1,85 @@ + + +
+ +
+
+ {@render children()} +
diff --git a/app/frontend/src/routes/login/+page.svelte b/app/frontend/src/routes/login/+page.svelte index f173ae25..f6073dc6 100644 --- a/app/frontend/src/routes/login/+page.svelte +++ b/app/frontend/src/routes/login/+page.svelte @@ -1,8 +1,8 @@ {#if loading} -

+

{:else if error} diff --git a/app/frontend/src/lib/components/custom/chart/MileageChart.svelte b/app/frontend/src/lib/components/custom/chart/MileageChart.svelte new file mode 100644 index 00000000..8da5c77d --- /dev/null +++ b/app/frontend/src/lib/components/custom/chart/MileageChart.svelte @@ -0,0 +1,96 @@ + + + + v.toLocaleDateString('en-US', { month: 'short' }) + // } + }} + xScale={scaleBand()} + y="mileage" + > + {#snippet belowMarks({ series })} + {#each series as s} + d.mileage !== null)} + y={s.value} + class="[stroke-dasharray:3,3]" + stroke={s.color} + /> + {/each} + {/snippet} + {#snippet tooltip()} + + {/snippet} + + diff --git a/app/frontend/src/lib/components/custom/common/IconWithTooltip.svelte b/app/frontend/src/lib/components/custom/common/IconWithTooltip.svelte new file mode 100644 index 00000000..0f16fcd5 --- /dev/null +++ b/app/frontend/src/lib/components/custom/common/IconWithTooltip.svelte @@ -0,0 +1,28 @@ + + + + + + + + + {tooltip} + + + diff --git a/app/frontend/src/lib/components/custom/common/LabelWithIcon.svelte b/app/frontend/src/lib/components/custom/common/LabelWithIcon.svelte new file mode 100644 index 00000000..76ae64d8 --- /dev/null +++ b/app/frontend/src/lib/components/custom/common/LabelWithIcon.svelte @@ -0,0 +1,10 @@ + + +
+ + {label} +
diff --git a/app/frontend/src/lib/components/custom/common/TabContainer.svelte b/app/frontend/src/lib/components/custom/common/TabContainer.svelte index 3aa4fe33..0fd26ce8 100644 --- a/app/frontend/src/lib/components/custom/common/TabContainer.svelte +++ b/app/frontend/src/lib/components/custom/common/TabContainer.svelte @@ -2,12 +2,8 @@ const { title, children } = $props(); -
-

+
+

{title}

{@render children()} diff --git a/app/frontend/src/lib/components/custom/common/VehicleCard copy.svelte b/app/frontend/src/lib/components/custom/common/VehicleCard copy.svelte new file mode 100644 index 00000000..d4cbdda5 --- /dev/null +++ b/app/frontend/src/lib/components/custom/common/VehicleCard copy.svelte @@ -0,0 +1,192 @@ + + + +
+ {vehicle.make} {vehicle.model} + +
+ +
+
+ + {vehicle.make} {vehicle.model} +
+ {vehicle.year} +
+
+ + +
+

+ + License Plate: + {vehicle.licensePlate} +

+

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

+ +

+ + Color: + {#if vehicle.color} + + {:else} + - + {/if} +

+

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

+ {#if vehicle.insuranceStatus} +

+ + Insurance: + + {vehicle.insuranceStatus} + +

+ {/if} + {#if vehicle.puccStatus} +

+ + PUCC: + + {vehicle.puccStatus} + +

+ {/if} +
+
+ + +
+
+ fuelLogModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Log fuel refill" + /> + maintenanceModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Maintenence" + /> + insuranceModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Insurance" + /> + puccModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Pollution Certificate" + /> +
+
+ { + vehicleModelStore.show(vehicle, true); + }} + ariaLabel="Edit" + /> + (deleteDialog = true)} + ariaLabel="Delete" + /> +
+
+
+
+
+
+ deleteVehicle(vehicle.id)} bind:open={deleteDialog} /> diff --git a/app/frontend/src/lib/components/custom/common/VehicleCard.svelte b/app/frontend/src/lib/components/custom/common/VehicleCard.svelte index fade466c..0a9ce5d6 100644 --- a/app/frontend/src/lib/components/custom/common/VehicleCard.svelte +++ b/app/frontend/src/lib/components/custom/common/VehicleCard.svelte @@ -1,16 +1,17 @@ -
-
-
- - {vehicle.make} {vehicle.model} + +
+ car
- {vehicle.year} -
-
-

- License Plate: - {vehicle.licensePlate} -

-

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

+
+
+
+
+ {vehicle.make} {vehicle.model} +
+

+ {#if vehicle.color} + + {:else} + - + {/if} +

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

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

+
+
-

- - Color: - {#if vehicle.color} - - {:else} - - - {/if} -

-

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

- {#if vehicle.insuranceStatus} -

- - Insurance: - - {vehicle.insuranceStatus} - -

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

- - PUCC: - - {vehicle.puccStatus} - -

- {/if} -
-
-
- fuelLogModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Log fuel refill" - /> - maintenanceModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Maintenence" - /> - insuranceModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Insurance" - /> - puccModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Pollution Certificate" - /> +
+ + +
+
+ + +
+
+
+
+ +
+

{vehicle.licensePlate}

+
+
+ {vehicle.year}
-
- { - vehicleModelStore.show(vehicle, true); - }} - ariaLabel="Edit" - /> - (deleteDialog = true)} - ariaLabel="Delete" - /> + + +
+
+ fuelLogModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Log fuel refill" + /> + maintenanceModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Maintenence" + /> + insuranceModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Insurance" + /> + puccModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Pollution Certificate" + /> +
+
+ { + vehicleModelStore.show(vehicle, true); + }} + ariaLabel="Edit" + /> + (deleteDialog = true)} + ariaLabel="Delete" + /> +
-
-
+ + deleteVehicle(vehicle.id)} bind:open={deleteDialog} /> diff --git a/app/frontend/src/lib/components/custom/forms/LoginForm.svelte b/app/frontend/src/lib/components/custom/forms/LoginForm.svelte index e5457083..8c8d84be 100644 --- a/app/frontend/src/lib/components/custom/forms/LoginForm.svelte +++ b/app/frontend/src/lib/components/custom/forms/LoginForm.svelte @@ -1,5 +1,5 @@ {#if loading} -

+

{:else if error} @@ -103,56 +87,72 @@ {:else if fuelLogs.length === 0}

No fuel refill logs found for this vehicle.

{:else} -
- - - - - - + + + Date + Odometer + Fuel Amount + Full Tank + Missed Last + Cost + Mileage + Notes + Actions + + + + {#each fuelLogs as log (log.id)} + + + {formatDate(log.date)} + + + {formatDistance(log.odometer)} + + + {formatVolume(log.fuelAmount)} + + + {#if log.filled} + + {:else} + + {/if} + + + {#if log.missedLast} + + {:else} + + {/if} + + + {formatCurrency(log.cost)} - - - - - - - - {#each fuelLogs as log (log.id)} - - - - - - - - - - {/each} - -
DateOdometerFuel AmountCostMileageNotesActions
{formatDate(log.date)}{formatDistance(log.odometer)}{formatVolume(log.fuelAmount)}{formatCurrency(log.cost)}{log.mileage ? formatMileage(log.mileage) : '-'}{log.notes || '-'} - { - selectedFuelLog = log.id; - deleteDialog = true; - }} - ariaLabel="Delete" - /> -
-
+ + {log.mileage ? formatMileage(log.mileage) : '-'} + + + {log.notes || '-'} + + + { + selectedFuelLog = log.id; + deleteDialog = true; + }} + ariaLabel="Delete" + /> + + + {/each} + + --> + + deleteFuelLog(selectedFuelLog)} bind:open={deleteDialog} /> {/if} diff --git a/app/frontend/src/lib/components/custom/lists/FuelLogTable.svelte b/app/frontend/src/lib/components/custom/lists/FuelLogTable.svelte new file mode 100644 index 00000000..930c217d --- /dev/null +++ b/app/frontend/src/lib/components/custom/lists/FuelLogTable.svelte @@ -0,0 +1,56 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + No results. + + {/each} + + +
diff --git a/app/frontend/src/lib/components/custom/lists/VehicleList.svelte b/app/frontend/src/lib/components/custom/lists/VehicleList.svelte index 8db5b159..028e68fe 100644 --- a/app/frontend/src/lib/components/custom/lists/VehicleList.svelte +++ b/app/frontend/src/lib/components/custom/lists/VehicleList.svelte @@ -1,6 +1,7 @@ {#if vehicles.length > 0} -
- {#each vehicles as vehicle (vehicle.id)} -
selectVehicle(vehicle.id)} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') selectVehicle(vehicle.id); - }} - class:ring-2={selectedVehicleId === vehicle.id} - class:ring-blue-500={selectedVehicleId === vehicle.id} - class="cursor-pointer rounded-2xl transition-all duration-300 ease-in-out" - > - -
- {/each} -
+ +
+ {#each vehicles as vehicle (vehicle.id)} +
selectVehicle(vehicle.id)} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') selectVehicle(vehicle.id); + }} + > + +
+ {/each} +
+
{:else}
diff --git a/app/frontend/src/lib/components/custom/tabs/DashboardTabs.svelte b/app/frontend/src/lib/components/custom/tabs/DashboardTabs.svelte new file mode 100644 index 00000000..f9e041e1 --- /dev/null +++ b/app/frontend/src/lib/components/custom/tabs/DashboardTabs.svelte @@ -0,0 +1,56 @@ + + + + + {#each tabs as tab} + {tab.name} + {/each} + + {#each tabs as { id, Component }} + + + + {/each} + diff --git a/app/frontend/src/lib/components/custom/tabs/DashboardTab.svelte b/app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte similarity index 65% rename from app/frontend/src/lib/components/custom/tabs/DashboardTab.svelte rename to app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte index 9f92b4d6..d0688819 100644 --- a/app/frontend/src/lib/components/custom/tabs/DashboardTab.svelte +++ b/app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte @@ -1,10 +1,12 @@ - + + diff --git a/app/frontend/src/lib/components/custom/tabs/TabHeader.svelte b/app/frontend/src/lib/components/custom/tabs/TabHeader.svelte deleted file mode 100644 index dceb70b8..00000000 --- a/app/frontend/src/lib/components/custom/tabs/TabHeader.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -
    - {#each tabs as tab (tab)} - - {/each} -
diff --git a/app/frontend/src/lib/components/ui/alert/alert.svelte b/app/frontend/src/lib/components/ui/alert/alert.svelte index 2b2eff9a..3683a064 100644 --- a/app/frontend/src/lib/components/ui/alert/alert.svelte +++ b/app/frontend/src/lib/components/ui/alert/alert.svelte @@ -1,31 +1,31 @@ + + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/badge/index.ts b/app/frontend/src/lib/components/ui/badge/index.ts new file mode 100644 index 00000000..64e0aa9b --- /dev/null +++ b/app/frontend/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from "./badge.svelte"; +export { badgeVariants, type BadgeVariant } from "./badge.svelte"; diff --git a/app/frontend/src/lib/components/ui/chart/chart-container.svelte b/app/frontend/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 00000000..563b25b1 --- /dev/null +++ b/app/frontend/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
+ + {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/chart/chart-style.svelte b/app/frontend/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 00000000..864ecc31 --- /dev/null +++ b/app/frontend/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte b/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 00000000..efef55c7 --- /dev/null +++ b/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,159 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
+ {#if typeof formattedLabel === "function"} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
+ {/if} +{/snippet} + + +
+ {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
+ {#each tooltipCtx.payload as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.name || "value"}`} + {@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)} + {@const indicatorColor = color || item.payload?.color || item.color} +
svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5", + indicator === "dot" && "items-center" + )} + > + {#if formatter && item.value !== undefined && item.name} + {@render formatter({ + value: item.value, + name: item.name, + item, + index: i, + payload: tooltipCtx.payload, + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
+ {/if} +
+
+ {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.name} + +
+ {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
+ {/if} +
+ {/each} +
+
+
diff --git a/app/frontend/src/lib/components/ui/chart/chart-utils.ts b/app/frontend/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 00000000..2decbbf7 --- /dev/null +++ b/app/frontend/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,66 @@ +import type { Tooltip } from "layerchart"; +import { getContext, setContext, type Component, type ComponentProps, type Snippet } from "svelte"; + +export const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = ExtractSnippetParams< + ComponentProps["children"] +>["payload"][number]; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string +) { + if (typeof payload !== "object" || payload === null) return undefined; + + const payloadPayload = + "payload" in payload && typeof payload.payload === "object" && payload.payload !== null + ? payload.payload + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.name === key) { + configLabelKey = payload.name; + } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadPayload !== undefined && + key in payloadPayload && + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" + ) { + configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol("chart-context"); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/app/frontend/src/lib/components/ui/chart/index.ts b/app/frontend/src/lib/components/ui/chart/index.ts new file mode 100644 index 00000000..f22375e0 --- /dev/null +++ b/app/frontend/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from "./chart-container.svelte"; +import ChartTooltip from "./chart-tooltip.svelte"; + +export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js"; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; diff --git a/app/frontend/src/lib/components/ui/data-table/data-table.svelte.ts b/app/frontend/src/lib/components/ui/data-table/data-table.svelte.ts new file mode 100644 index 00000000..9e0b9130 --- /dev/null +++ b/app/frontend/src/lib/components/ui/data-table/data-table.svelte.ts @@ -0,0 +1,141 @@ +import { + type RowData, + type TableOptions, + type TableOptionsResolved, + type TableState, + createTable, +} from "@tanstack/table-core"; + +/** + * Creates a reactive TanStack table object for Svelte. + * @param options Table options to create the table with. + * @returns A reactive table object. + * @example + * ```svelte + * + * + * + * + * {#each table.getHeaderGroups() as headerGroup} + * + * {#each headerGroup.headers as header} + * + * {/each} + * + * {/each} + * + * + *
+ * + *
+ * ``` + */ +export function createSvelteTable(options: TableOptions) { + const resolvedOptions: TableOptionsResolved = mergeObjects( + { + state: {}, + onStateChange() {}, + renderFallbackValue: null, + mergeOptions: ( + defaultOptions: TableOptions, + options: Partial> + ) => { + return mergeObjects(defaultOptions, options); + }, + }, + options + ); + + const table = createTable(resolvedOptions); + let state = $state>(table.initialState); + + function updateOptions() { + table.setOptions((prev) => { + return mergeObjects(prev, options, { + state: mergeObjects(state, options.state || {}), + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onStateChange: (updater: any) => { + if (updater instanceof Function) state = updater(state); + else state = mergeObjects(state, updater); + + options.onStateChange?.(updater); + }, + }); + }); + } + + updateOptions(); + + $effect.pre(() => { + updateOptions(); + }); + + return table; +} + +type MaybeThunk = T | (() => T | null | undefined); +type Intersection = (T extends [infer H, ...infer R] + ? H & Intersection + : unknown) & {}; + +/** + * Lazily merges several objects (or thunks) while preserving + * getter semantics from every source. + * + * Proxy-based to avoid known WebKit recursion issue. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeObjects[]>( + ...sources: Sources +): Intersection<{ [K in keyof Sources]: Sources[K] }> { + const resolve = (src: MaybeThunk): T | undefined => + typeof src === "function" ? (src() ?? undefined) : src; + + const findSourceWithKey = (key: PropertyKey) => { + for (let i = sources.length - 1; i >= 0; i--) { + const obj = resolve(sources[i]); + if (obj && key in obj) return obj; + } + return undefined; + }; + + return new Proxy(Object.create(null), { + get(_, key) { + const src = findSourceWithKey(key); + + return src?.[key as never]; + }, + + has(_, key) { + return !!findSourceWithKey(key); + }, + + ownKeys(): (string | symbol)[] { + const all = new Set(); + for (const s of sources) { + const obj = resolve(s); + if (obj) { + for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) { + all.add(k); + } + } + } + return [...all]; + }, + + getOwnPropertyDescriptor(_, key) { + const src = findSourceWithKey(key); + if (!src) return undefined; + return { + configurable: true, + enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: (src as any)[key], + writable: true, + }; + }, + }) as Intersection<{ [K in keyof Sources]: Sources[K] }>; +} diff --git a/app/frontend/src/lib/components/ui/data-table/flex-render.svelte b/app/frontend/src/lib/components/ui/data-table/flex-render.svelte new file mode 100644 index 00000000..12d2af10 --- /dev/null +++ b/app/frontend/src/lib/components/ui/data-table/flex-render.svelte @@ -0,0 +1,36 @@ + + +{#if typeof content === "string"} + {content} +{:else if content instanceof Function} + + + {@const result = content(context as any)} + {#if result instanceof RenderComponentConfig} + {@const { component: Component, props } = result} + + {:else if result instanceof RenderSnippetConfig} + {@const { snippet, params } = result} + {@render snippet(params)} + {:else} + {result} + {/if} +{/if} diff --git a/app/frontend/src/lib/components/ui/data-table/index.ts b/app/frontend/src/lib/components/ui/data-table/index.ts new file mode 100644 index 00000000..5f4e77ea --- /dev/null +++ b/app/frontend/src/lib/components/ui/data-table/index.ts @@ -0,0 +1,3 @@ +export { default as FlexRender } from "./flex-render.svelte"; +export { renderComponent, renderSnippet } from "./render-helpers.js"; +export { createSvelteTable } from "./data-table.svelte.js"; diff --git a/app/frontend/src/lib/components/ui/data-table/render-helpers.ts b/app/frontend/src/lib/components/ui/data-table/render-helpers.ts new file mode 100644 index 00000000..fa036d62 --- /dev/null +++ b/app/frontend/src/lib/components/ui/data-table/render-helpers.ts @@ -0,0 +1,111 @@ +import type { Component, ComponentProps, Snippet } from "svelte"; + +/** + * A helper class to make it easy to identify Svelte components in + * `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderComponentConfig} + * {@const { component: Component, props } = result} + * + * {/if} + * ``` + */ +export class RenderComponentConfig { + component: TComponent; + props: ComponentProps | Record; + constructor( + component: TComponent, + props: ComponentProps | Record = {} + ) { + this.component = component; + this.props = props; + } +} + +/** + * A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderSnippetConfig} + * {@const { snippet, params } = result} + * {@render snippet(params)} + * {/if} + * ``` + */ +export class RenderSnippetConfig { + snippet: Snippet<[TProps]>; + params: TProps; + constructor(snippet: Snippet<[TProps]>, params: TProps) { + this.snippet = snippet; + this.params = params; + } +} + +/** + * A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties. + * + * This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + * + * @param component A Svelte component + * @param props The props to pass to `component` + * @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * header: header => renderComponent(SortHeader, { label: 'Name', header }), + * }), + * columnHelper.accessor('state', { + * header: header => renderComponent(SortHeader, { label: 'State', header }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderComponent< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Component, + Props extends ComponentProps, +>(component: T, props: Props = {} as Props) { + return new RenderComponentConfig(component, props); +} + +/** + * A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties. + * + * The snippet must only take one parameter. + * + * This is only to be used with Snippets - use `renderComponent` for Svelte Components. + * + * @param snippet + * @param params + * @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + * }), + * columnHelper.accessor('state', { + * cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderSnippet(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) { + return new RenderSnippetConfig(snippet, params); +} diff --git a/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte b/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte index a4b0a609..2d08e7de 100644 --- a/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte +++ b/app/frontend/src/lib/components/ui/input-otp/input-otp-separator.svelte @@ -1,6 +1,6 @@ + + + {@render children?.()} + + diff --git a/app/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte b/app/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte new file mode 100644 index 00000000..38a1847f --- /dev/null +++ b/app/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte @@ -0,0 +1,40 @@ + + + + + {@render children?.()} + + {#if orientation === "vertical" || orientation === "both"} + + {/if} + {#if orientation === "horizontal" || orientation === "both"} + + {/if} + + diff --git a/app/frontend/src/lib/components/ui/table/index.ts b/app/frontend/src/lib/components/ui/table/index.ts new file mode 100644 index 00000000..14695c81 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/index.ts @@ -0,0 +1,28 @@ +import Root from "./table.svelte"; +import Body from "./table-body.svelte"; +import Caption from "./table-caption.svelte"; +import Cell from "./table-cell.svelte"; +import Footer from "./table-footer.svelte"; +import Head from "./table-head.svelte"; +import Header from "./table-header.svelte"; +import Row from "./table-row.svelte"; + +export { + Root, + Body, + Caption, + Cell, + Footer, + Head, + Header, + Row, + // + Root as Table, + Body as TableBody, + Caption as TableCaption, + Cell as TableCell, + Footer as TableFooter, + Head as TableHead, + Header as TableHeader, + Row as TableRow, +}; diff --git a/app/frontend/src/lib/components/ui/table/table-body.svelte b/app/frontend/src/lib/components/ui/table/table-body.svelte new file mode 100644 index 00000000..29e96875 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-body.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table-caption.svelte b/app/frontend/src/lib/components/ui/table/table-caption.svelte new file mode 100644 index 00000000..4696cff5 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-caption.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table-cell.svelte b/app/frontend/src/lib/components/ui/table/table-cell.svelte new file mode 100644 index 00000000..1a2f033f --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-cell.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table-footer.svelte b/app/frontend/src/lib/components/ui/table/table-footer.svelte new file mode 100644 index 00000000..b9b14ebf --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-footer.svelte @@ -0,0 +1,20 @@ + + +tr]:last:border-b-0", className)} + {...restProps} +> + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table-head.svelte b/app/frontend/src/lib/components/ui/table/table-head.svelte new file mode 100644 index 00000000..e9dd2378 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-head.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table-header.svelte b/app/frontend/src/lib/components/ui/table/table-header.svelte new file mode 100644 index 00000000..f47d2597 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-header.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table-row.svelte b/app/frontend/src/lib/components/ui/table/table-row.svelte new file mode 100644 index 00000000..0df769e0 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table-row.svelte @@ -0,0 +1,23 @@ + + +svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/table/table.svelte b/app/frontend/src/lib/components/ui/table/table.svelte new file mode 100644 index 00000000..a3349563 --- /dev/null +++ b/app/frontend/src/lib/components/ui/table/table.svelte @@ -0,0 +1,22 @@ + + +
+ + {@render children?.()} +
+
diff --git a/app/frontend/src/lib/components/ui/tabs/index.ts b/app/frontend/src/lib/components/ui/tabs/index.ts new file mode 100644 index 00000000..12d4327a --- /dev/null +++ b/app/frontend/src/lib/components/ui/tabs/index.ts @@ -0,0 +1,16 @@ +import Root from "./tabs.svelte"; +import Content from "./tabs-content.svelte"; +import List from "./tabs-list.svelte"; +import Trigger from "./tabs-trigger.svelte"; + +export { + Root, + Content, + List, + Trigger, + // + Root as Tabs, + Content as TabsContent, + List as TabsList, + Trigger as TabsTrigger, +}; diff --git a/app/frontend/src/lib/components/ui/tabs/tabs-content.svelte b/app/frontend/src/lib/components/ui/tabs/tabs-content.svelte new file mode 100644 index 00000000..340d65cf --- /dev/null +++ b/app/frontend/src/lib/components/ui/tabs/tabs-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/app/frontend/src/lib/components/ui/tabs/tabs-list.svelte b/app/frontend/src/lib/components/ui/tabs/tabs-list.svelte new file mode 100644 index 00000000..08932b60 --- /dev/null +++ b/app/frontend/src/lib/components/ui/tabs/tabs-list.svelte @@ -0,0 +1,20 @@ + + + diff --git a/app/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte b/app/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte new file mode 100644 index 00000000..dced992e --- /dev/null +++ b/app/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -0,0 +1,20 @@ + + + diff --git a/app/frontend/src/lib/components/ui/tabs/tabs.svelte b/app/frontend/src/lib/components/ui/tabs/tabs.svelte new file mode 100644 index 00000000..ef6cada5 --- /dev/null +++ b/app/frontend/src/lib/components/ui/tabs/tabs.svelte @@ -0,0 +1,19 @@ + + + diff --git a/app/frontend/src/lib/components/ui/tooltip/index.ts b/app/frontend/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 00000000..313a7f06 --- /dev/null +++ b/app/frontend/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,21 @@ +import { Tooltip as TooltipPrimitive } from "bits-ui"; +import Trigger from "./tooltip-trigger.svelte"; +import Content from "./tooltip-content.svelte"; + +const Root = TooltipPrimitive.Root; +const Provider = TooltipPrimitive.Provider; +const Portal = TooltipPrimitive.Portal; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal, +}; diff --git a/app/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte b/app/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 00000000..e495efe5 --- /dev/null +++ b/app/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,47 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
+ {/snippet} +
+
+
diff --git a/app/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/app/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 00000000..1acdaa47 --- /dev/null +++ b/app/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/app/frontend/src/lib/services/auth.service.ts b/app/frontend/src/lib/services/auth.service.ts new file mode 100644 index 00000000..5a88eeb5 --- /dev/null +++ b/app/frontend/src/lib/services/auth.service.ts @@ -0,0 +1,26 @@ +import { getApiUrl } from '$lib/utility/api'; + +export const isPinSet = async () => { + try { + const response = await fetch(getApiUrl('/api/pin/status')); + if (response.ok) { + const data = await response.json(); + return data.exists; + } + return false; + } catch (e) { + console.error(e); + return false; + } +}; + +export const verifyPin = async (pin: string) => { + const response = await fetch(getApiUrl('/api/pin/verify'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ pin }) + }); + return response.ok; +}; diff --git a/app/frontend/src/stores/auth.ts b/app/frontend/src/lib/stores/auth.ts similarity index 100% rename from app/frontend/src/stores/auth.ts rename to app/frontend/src/lib/stores/auth.ts diff --git a/app/frontend/src/stores/config.ts b/app/frontend/src/lib/stores/config.ts similarity index 97% rename from app/frontend/src/stores/config.ts rename to app/frontend/src/lib/stores/config.ts index 855e6e2b..d917a92b 100644 --- a/app/frontend/src/stores/config.ts +++ b/app/frontend/src/lib/stores/config.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import { browser } from '$app/environment'; -import { getApiUrl } from '../utils/api'; +import { getApiUrl } from '$utils/api'; export interface Config { key: string; diff --git a/app/frontend/src/stores/dark-mode.ts b/app/frontend/src/lib/stores/dark-mode.ts similarity index 100% rename from app/frontend/src/stores/dark-mode.ts rename to app/frontend/src/lib/stores/dark-mode.ts diff --git a/app/frontend/src/stores/fuel-log.ts b/app/frontend/src/lib/stores/fuel-log.ts similarity index 100% rename from app/frontend/src/stores/fuel-log.ts rename to app/frontend/src/lib/stores/fuel-log.ts diff --git a/app/frontend/src/stores/insurance.ts b/app/frontend/src/lib/stores/insurance.ts similarity index 100% rename from app/frontend/src/stores/insurance.ts rename to app/frontend/src/lib/stores/insurance.ts diff --git a/app/frontend/src/stores/maintenance.ts b/app/frontend/src/lib/stores/maintenance.ts similarity index 100% rename from app/frontend/src/stores/maintenance.ts rename to app/frontend/src/lib/stores/maintenance.ts diff --git a/app/frontend/src/stores/pucc.ts b/app/frontend/src/lib/stores/pucc.ts similarity index 100% rename from app/frontend/src/stores/pucc.ts rename to app/frontend/src/lib/stores/pucc.ts diff --git a/app/frontend/src/stores/vehicle.ts b/app/frontend/src/lib/stores/vehicle.ts similarity index 96% rename from app/frontend/src/stores/vehicle.ts rename to app/frontend/src/lib/stores/vehicle.ts index 28721c7a..9d0d34c8 100644 --- a/app/frontend/src/stores/vehicle.ts +++ b/app/frontend/src/lib/stores/vehicle.ts @@ -1,6 +1,6 @@ import { goto } from '$app/navigation'; -import type { Vehicle } from '../models/vehicle'; -import { getApiUrl } from '../utils/api'; +import type { Vehicle } from '../types/vehicle'; +import { getApiUrl } from '$lib/utility/api'; import { writable } from 'svelte/store'; const createVehicleModalStore = () => { diff --git a/app/frontend/src/models/Error.ts b/app/frontend/src/lib/types/Error.ts similarity index 100% rename from app/frontend/src/models/Error.ts rename to app/frontend/src/lib/types/Error.ts diff --git a/app/frontend/src/lib/types/fuel-log.ts b/app/frontend/src/lib/types/fuel-log.ts new file mode 100644 index 00000000..0c78ecb8 --- /dev/null +++ b/app/frontend/src/lib/types/fuel-log.ts @@ -0,0 +1,168 @@ +import LabelWithIcon from '$lib/components/custom/common/LabelWithIcon.svelte'; +import { Badge } from '$lib/components/ui/badge'; +import { renderComponent, renderSnippet } from '$lib/components/ui/data-table'; +import { formatCurrency, formatDate, formatDistance, formatVolume } from '$utils/formatting'; +import { + Banknote, + Calendar1, + Fuel, + GaugeCircle, + Notebook, + PaintBucket, + SkipBack +} from '@lucide/svelte'; +import type { ColumnDef } from '@tanstack/table-core'; +import { createRawSnippet } from 'svelte'; + +export interface NewFuelLog { + date: string; + odometer: number | null; + fuelAmount: number | null; + cost: number | null; + notes?: string; +} + +export interface FuelLog { + id: string; + date: string; + odometer: number; + fuelAmount: number; + cost: number; + notes?: string; + mileage?: number; + filled?: boolean; + missedLast?: boolean; +} + +export const columns: ColumnDef[] = [ + { + accessorKey: 'date', + header: () => + renderComponent(LabelWithIcon, { + icon: Calendar1, + iconClass: 'h-4 w-4 ', + label: 'Date', + style: 'justify-center' + }), + cell: ({ row }) => { + const dateCellSnippet = createRawSnippet<[string]>((date) => { + return { + render: () => `
${formatDate(date())}
` + }; + }); + + return renderSnippet(dateCellSnippet, row.getValue('date')); + } + }, + { + accessorKey: 'odometer', + header: () => + renderComponent(LabelWithIcon, { + icon: GaugeCircle, + iconClass: 'h-4 w-4 ', + label: 'Odometer', + style: 'justify-center' + }), + cell: ({ row }) => { + const odometerCellSnippet = createRawSnippet<[number]>((getOdometer) => { + const odometer = getOdometer(); + return { + render: () => + `
${formatDistance(odometer)}
` + }; + }); + + return renderSnippet(odometerCellSnippet, row.getValue('odometer')); + } + }, + { + accessorKey: 'fuelAmount', + header: () => + renderComponent(LabelWithIcon, { + icon: PaintBucket, + iconClass: 'h-4 w-4 ', + label: 'Fuel Amount', + style: 'justify-center' + }), + cell: ({ row }) => { + const fuelAmountCellSnippet = createRawSnippet<[number]>((getFuelAmount) => { + const fuelAmount = getFuelAmount(); + return { + render: () => + `
${formatVolume(fuelAmount)}
` + }; + }); + + return renderSnippet(fuelAmountCellSnippet, row.getValue('fuelAmount')); + } + }, + { + accessorKey: 'cost', + header: () => + renderComponent(LabelWithIcon, { + icon: Banknote, + iconClass: 'h-4 w-4 ', + label: 'Cost', + style: 'justify-center' + }), + cell: ({ row }) => { + const costCellSnippet = createRawSnippet<[number]>((getCost) => { + const cost = getCost(); + return { + render: () => `
${formatCurrency(cost)}
` + }; + }); + + return renderSnippet(costCellSnippet, row.getValue('cost')); + } + }, + { + accessorKey: 'filled', + header: () => + renderComponent(LabelWithIcon, { + icon: Fuel, + iconClass: 'h-4 w-4 ', + label: 'Full Tank', + style: 'justify-center' + }), + cell: ({ row }) => + renderComponent(Badge, { + variant: 'outline', + class: 'flex flex-row justify-center', + children: createRawSnippet(() => { + return { + render: () => `${row.getValue('filled') ? 'Yes' : 'No'}` + }; + }) + }) + }, + { + accessorKey: 'missedLast', + header: () => + renderComponent(LabelWithIcon, { + icon: SkipBack, + iconClass: 'h-4 w-4 ', + label: 'Missed Last', + style: 'justify-center' + }), + cell: ({ row }) => + renderComponent(Badge, { + variant: 'outline', + children: createRawSnippet(() => { + return { + render: () => `${row.getValue('missedLast') ? 'Yes' : 'No'}` + }; + }) + }) + }, + { + accessorKey: 'notes', + header: () => + renderComponent(LabelWithIcon, { + icon: Notebook, + iconClass: 'h-4 w-4 ', + label: 'Notes', + style: 'justify-center' + }) + } +]; diff --git a/app/frontend/src/models/status.ts b/app/frontend/src/lib/types/status.ts similarity index 100% rename from app/frontend/src/models/status.ts rename to app/frontend/src/lib/types/status.ts diff --git a/app/frontend/src/models/vehicle.ts b/app/frontend/src/lib/types/vehicle.ts similarity index 100% rename from app/frontend/src/models/vehicle.ts rename to app/frontend/src/lib/types/vehicle.ts diff --git a/app/frontend/src/utils/api.ts b/app/frontend/src/lib/utility/api.ts similarity index 100% rename from app/frontend/src/utils/api.ts rename to app/frontend/src/lib/utility/api.ts diff --git a/app/frontend/src/utils/dev.ts b/app/frontend/src/lib/utility/dev.ts similarity index 100% rename from app/frontend/src/utils/dev.ts rename to app/frontend/src/lib/utility/dev.ts diff --git a/app/frontend/src/lib/utility/formatting.ts b/app/frontend/src/lib/utility/formatting.ts new file mode 100644 index 00000000..268f08e3 --- /dev/null +++ b/app/frontend/src/lib/utility/formatting.ts @@ -0,0 +1,132 @@ +import { config } from '$stores/config'; +import { format } from 'date-fns'; + +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 format(date, 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 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`; + } else if (configs.unitOfMeasure === 'imperial') { + return `${distance} mi`; + } + return `${distance}`; +}; + +const getVolumeUnit = (): string => { + if (configs.unitOfMeasure === 'metric') { + return 'l'; + } + if (configs.unitOfMeasure === 'imperial') { + return 'gal'; + } + return ''; +}; + +const formatVolume = (volume: number): string => { + if (configs.unitOfMeasure === 'metric') { + return `${volume.toFixed(2)} 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, + getDistanceUnit, + formatDistance, + getVolumeUnit, + formatVolume, + getMileageUnit, + formatMileage +}; + +export const cleanup = (obj: Record): Record => { + const result: Record = { ...obj }; + for (const key in result) { + if ( + result.hasOwnProperty(key) && + (String(result[key]).trim() === '' || result[key] === undefined) + ) { + result[key] = null; + } + } + return result; +}; diff --git a/app/frontend/src/lib/utils.ts b/app/frontend/src/lib/utils.ts index 55b3a918..f92bfcbb 100644 --- a/app/frontend/src/lib/utils.ts +++ b/app/frontend/src/lib/utils.ts @@ -1,13 +1,13 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type WithoutChild = T extends { child?: any } ? Omit : T; +export type WithoutChild = T extends { child?: any } ? Omit : T; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildren = T extends { children?: any } ? Omit : T; export type WithoutChildrenOrChild = WithoutChildren>; export type WithElementRef = T & { ref?: U | null }; diff --git a/app/frontend/src/models/fuel-log.ts b/app/frontend/src/models/fuel-log.ts index b3f789fd..0c78ecb8 100644 --- a/app/frontend/src/models/fuel-log.ts +++ b/app/frontend/src/models/fuel-log.ts @@ -1,3 +1,19 @@ +import LabelWithIcon from '$lib/components/custom/common/LabelWithIcon.svelte'; +import { Badge } from '$lib/components/ui/badge'; +import { renderComponent, renderSnippet } from '$lib/components/ui/data-table'; +import { formatCurrency, formatDate, formatDistance, formatVolume } from '$utils/formatting'; +import { + Banknote, + Calendar1, + Fuel, + GaugeCircle, + Notebook, + PaintBucket, + SkipBack +} from '@lucide/svelte'; +import type { ColumnDef } from '@tanstack/table-core'; +import { createRawSnippet } from 'svelte'; + export interface NewFuelLog { date: string; odometer: number | null; @@ -6,6 +22,147 @@ export interface NewFuelLog { notes?: string; } -export interface FuelLog extends NewFuelLog { +export interface FuelLog { id: string; + date: string; + odometer: number; + fuelAmount: number; + cost: number; + notes?: string; + mileage?: number; + filled?: boolean; + missedLast?: boolean; } + +export const columns: ColumnDef[] = [ + { + accessorKey: 'date', + header: () => + renderComponent(LabelWithIcon, { + icon: Calendar1, + iconClass: 'h-4 w-4 ', + label: 'Date', + style: 'justify-center' + }), + cell: ({ row }) => { + const dateCellSnippet = createRawSnippet<[string]>((date) => { + return { + render: () => `
${formatDate(date())}
` + }; + }); + + return renderSnippet(dateCellSnippet, row.getValue('date')); + } + }, + { + accessorKey: 'odometer', + header: () => + renderComponent(LabelWithIcon, { + icon: GaugeCircle, + iconClass: 'h-4 w-4 ', + label: 'Odometer', + style: 'justify-center' + }), + cell: ({ row }) => { + const odometerCellSnippet = createRawSnippet<[number]>((getOdometer) => { + const odometer = getOdometer(); + return { + render: () => + `
${formatDistance(odometer)}
` + }; + }); + + return renderSnippet(odometerCellSnippet, row.getValue('odometer')); + } + }, + { + accessorKey: 'fuelAmount', + header: () => + renderComponent(LabelWithIcon, { + icon: PaintBucket, + iconClass: 'h-4 w-4 ', + label: 'Fuel Amount', + style: 'justify-center' + }), + cell: ({ row }) => { + const fuelAmountCellSnippet = createRawSnippet<[number]>((getFuelAmount) => { + const fuelAmount = getFuelAmount(); + return { + render: () => + `
${formatVolume(fuelAmount)}
` + }; + }); + + return renderSnippet(fuelAmountCellSnippet, row.getValue('fuelAmount')); + } + }, + { + accessorKey: 'cost', + header: () => + renderComponent(LabelWithIcon, { + icon: Banknote, + iconClass: 'h-4 w-4 ', + label: 'Cost', + style: 'justify-center' + }), + cell: ({ row }) => { + const costCellSnippet = createRawSnippet<[number]>((getCost) => { + const cost = getCost(); + return { + render: () => `
${formatCurrency(cost)}
` + }; + }); + + return renderSnippet(costCellSnippet, row.getValue('cost')); + } + }, + { + accessorKey: 'filled', + header: () => + renderComponent(LabelWithIcon, { + icon: Fuel, + iconClass: 'h-4 w-4 ', + label: 'Full Tank', + style: 'justify-center' + }), + cell: ({ row }) => + renderComponent(Badge, { + variant: 'outline', + class: 'flex flex-row justify-center', + children: createRawSnippet(() => { + return { + render: () => `${row.getValue('filled') ? 'Yes' : 'No'}` + }; + }) + }) + }, + { + accessorKey: 'missedLast', + header: () => + renderComponent(LabelWithIcon, { + icon: SkipBack, + iconClass: 'h-4 w-4 ', + label: 'Missed Last', + style: 'justify-center' + }), + cell: ({ row }) => + renderComponent(Badge, { + variant: 'outline', + children: createRawSnippet(() => { + return { + render: () => `${row.getValue('missedLast') ? 'Yes' : 'No'}` + }; + }) + }) + }, + { + accessorKey: 'notes', + header: () => + renderComponent(LabelWithIcon, { + icon: Notebook, + iconClass: 'h-4 w-4 ', + label: 'Notes', + style: 'justify-center' + }) + } +]; diff --git a/app/frontend/src/routes/dashboard/+layout.svelte b/app/frontend/src/routes/dashboard/+layout.svelte index bc2bc6a9..f01e22dc 100644 --- a/app/frontend/src/routes/dashboard/+layout.svelte +++ b/app/frontend/src/routes/dashboard/+layout.svelte @@ -8,7 +8,7 @@ import { env } from '$env/dynamic/public'; import { configModelStore } from '$stores/config'; import { vehiclesStore } from '$stores/vehicle'; - import IconButton from '$appui/common/IconButton.svelte'; + import Button from '$lib/components/ui/button/button.svelte'; let { children } = $props(); @@ -48,38 +48,34 @@ }; -
- +
-
+ +
{@render children()}
diff --git a/app/frontend/src/routes/dashboard/+page.svelte b/app/frontend/src/routes/dashboard/+page.svelte index bdc73389..c7dcbbd5 100644 --- a/app/frontend/src/routes/dashboard/+page.svelte +++ b/app/frontend/src/routes/dashboard/+page.svelte @@ -6,18 +6,15 @@ import type { Vehicle } from '$models/vehicle'; import { Jumper } from 'svelte-loading-spinners'; import MaintenanceLogModal from '$appui/modals/MaintenanceLogModal.svelte'; - import TabHeader from '$appui/tabs/TabHeader.svelte'; - import DashboardTab from '$appui/tabs/DashboardTab.svelte'; - import FuelLogTab from '$appui/tabs/FuelLogTab.svelte'; - import MaintenenceLogTab from '$appui/tabs/MaintenenceLogTab.svelte'; - import InsuranceTab from '$appui/tabs/InsuranceTab.svelte'; - import PollutionTab from '$appui/tabs/PollutionTab.svelte'; import { vehicleModelStore, vehiclesStore } from '$stores/vehicle'; import PollutionCertificateModal from '$appui/modals/PollutionCertificateModal.svelte'; import InsuranceModal from '$appui/modals/InsuranceModal.svelte'; import { browser } from '$app/environment'; import ConfigModal from '$appui/modals/ConfigModal.svelte'; - import Button from '$appui/common/Button.svelte'; + import Button from '$lib/components/ui/button/button.svelte'; + import * as Tabs from '$lib/components/ui/tabs'; + import TabHeader from '$lib/components/custom/tabs/DashboardTabs.svelte'; + import DashboardTabs from '$lib/components/custom/tabs/DashboardTabs.svelte'; let vehicles = $state([]); let loading = $state(true); @@ -55,16 +52,13 @@ fetchVehicles(); -
+
-

Your Vehicles

-
{#if loading}

@@ -78,24 +72,7 @@ {/if} {#if selectedVehicleId} -

-
- -
-
- {#if activeTab === 'dashboard'} - - {:else if activeTab === 'fuel'} - - {:else if activeTab === 'maintenance'} - - {:else if activeTab === 'insurance'} - - {:else if activeTab === 'pollution'} - - {/if} -
-
+ {:else if vehicles.length > 0 && !loading}

diff --git a/app/frontend/src/services/auth.service.ts b/app/frontend/src/services/auth.service.ts deleted file mode 100644 index 6e357a96..00000000 --- a/app/frontend/src/services/auth.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getApiUrl } from "../utils/api"; - -export const isPinSet = async () => { - try { - const response = await fetch(getApiUrl('/api/pin/status')); - if (response.ok) { - const data = await response.json(); - return data.exists; - } - return false; - } catch (e) { - console.error(e); - return false; - } -}; - -export const verifyPin = async (pin: string) => { - const response = await fetch(getApiUrl('/api/pin/verify'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ pin }) - }); - return response.ok; -} diff --git a/app/frontend/src/styles/app.css b/app/frontend/src/styles/app.css index 6e23e888..33a75a96 100644 --- a/app/frontend/src/styles/app.css +++ b/app/frontend/src/styles/app.css @@ -1,7 +1,5 @@ -@import url('https://fonts.googleapis.com/css2?family=Epunda+Sans:ital,wght@0,300..900;1,300..900&display=swap'); - +@import url('https://fonts.googleapis.com/css2?family=Epunda+Sans:ital,wght@0,300..900;1,300..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap'); @import "tailwindcss"; - @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @@ -14,8 +12,8 @@ --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.795 0.184 86.047); - --primary-foreground: oklch(0.421 0.095 57.708); + --primary: oklch(0.623 0.214 259.815); + --primary-foreground: oklch(0.97 0.014 254.604); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); @@ -25,7 +23,7 @@ --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.795 0.184 86.047); + --ring: oklch(0.623 0.214 259.815); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -33,12 +31,12 @@ --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.795 0.184 86.047); - --sidebar-primary-foreground: oklch(0.421 0.095 57.708); + --sidebar-primary: oklch(0.623 0.214 259.815); + --sidebar-primary-foreground: oklch(0.97 0.014 254.604); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.795 0.184 86.047); + --sidebar-ring: oklch(0.623 0.214 259.815); } .dark { @@ -48,8 +46,8 @@ --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.795 0.184 86.047); - --primary-foreground: oklch(0.421 0.095 57.708); + --primary: oklch(0.546 0.245 262.881); + --primary-foreground: oklch(0.379 0.146 265.522); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); @@ -59,7 +57,7 @@ --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); - --ring: oklch(0.554 0.135 66.442); + --ring: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); @@ -67,14 +65,16 @@ --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.795 0.184 86.047); - --sidebar-primary-foreground: oklch(0.421 0.095 57.708); + --sidebar-primary: oklch(0.546 0.245 262.881); + --sidebar-primary-foreground: oklch(0.379 0.146 265.522); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.554 0.135 66.442); + --sidebar-ring: oklch(0.488 0.243 264.376); } + + @theme inline { --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); @@ -125,4 +125,8 @@ * { font-family: "Epunda Sans", sans-serif; -} \ No newline at end of file +} + +.mono { + font-family: "JetBrains Mono", monospace; +} diff --git a/app/frontend/src/utils/formatting.ts b/app/frontend/src/utils/formatting.ts deleted file mode 100644 index daf89559..00000000 --- a/app/frontend/src/utils/formatting.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { config } from '../stores/config'; -import { format } from 'date-fns'; - -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 format(date, 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 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`; - } else if (configs.unitOfMeasure === 'imperial') { - return `${distance} mi`; - } - return `${distance}`; -}; - -const getVolumeUnit = (): string => { - if (configs.unitOfMeasure === 'metric') { - return 'l'; - } - if (configs.unitOfMeasure === 'imperial') { - return 'gal'; - } - return ''; -}; - -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, - getDistanceUnit, - formatDistance, - getVolumeUnit, - formatVolume, - getMileageUnit, - formatMileage -}; - -export const cleanup = (obj: Record): Record => { - const result: Record = { ...obj }; - for (const key in result) { - if ( - result.hasOwnProperty(key) && - (String(result[key]).trim() === '' || result[key] === undefined) - ) { - result[key] = null; - } - } - return result; -}; diff --git a/app/frontend/svelte.config.js b/app/frontend/svelte.config.js index d2168811..87d83725 100644 --- a/app/frontend/svelte.config.js +++ b/app/frontend/svelte.config.js @@ -10,10 +10,10 @@ const config = { alias: { $lib: './src/lib', $appui: './src/lib/components/custom', - $models: './src/models', - $stores: './src/stores', - $services: './src/services', - $utils: './src/utils' + $models: './src/lib/types', + $stores: './src/lib/stores', + $services: './src/lib/services', + $utils: './src/lib/utility' } } }; diff --git a/package-lock.json b/package-lock.json index 4decadc9..de11e287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -412,6 +412,12 @@ "app/frontend": { "version": "0.0.1", "dependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-scale": "^4.0.9", + "@types/d3-shape": "^3.1.7", + "d3-array": "^3.2.4", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", "dot-env": "^0.0.1", "mode-watcher": "^1.1.0", "svelte-loading-spinners": "^0.3.6", @@ -427,6 +433,7 @@ "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", + "@tanstack/table-core": "^8.21.3", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "bits-ui": "^2.9.6", @@ -438,6 +445,7 @@ "eslint-plugin-svelte": "^3.11.0", "eslint-plugin-unused-imports": "^4.2.0", "globals": "^14.0.0", + "layerchart": "^2.0.0-next.27", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.11", @@ -492,6 +500,26 @@ "url": "https://github.com/sponsors/dcastil" } }, + "node_modules/@dagrejs/dagre": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-1.1.5.tgz", + "integrity": "sha512-Ghgrh08s12DCL5SeiR6AoyE80mQELTWhJBRmXfFoqDiFkR458vPEdgTbbjA0T+9ETNxUblnD0QW55tfdvi5pjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "2.2.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.2.4.tgz", + "integrity": "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">17.0.0" + } + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -1744,6 +1772,55 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@layerstack/svelte-actions": { + "version": "1.0.1-next.12", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-actions/-/svelte-actions-1.0.1-next.12.tgz", + "integrity": "sha512-dndWTlYu8b1u6vw2nrO7NssccoACArGG75WoNlyVC13KuENZlWdKE9Q79/wlnbq00NeQMNKMjJwRMsrKQj2ULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.0", + "@layerstack/utils": "2.0.0-next.12", + "d3-scale": "^4.0.2" + } + }, + "node_modules/@layerstack/svelte-state": { + "version": "0.1.0-next.17", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-state/-/svelte-state-0.1.0-next.17.tgz", + "integrity": "sha512-z7e6mPJnypD80LEI/UDuH0bI6s8/nut06MB7rEkRcEfHJekhKSJgFhMnrYzLED7Mc2gTTD0X/wcYlakauWlU8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@layerstack/utils": "2.0.0-next.12" + } + }, + "node_modules/@layerstack/tailwind": { + "version": "2.0.0-next.15", + "resolved": "https://registry.npmjs.org/@layerstack/tailwind/-/tailwind-2.0.0-next.15.tgz", + "integrity": "sha512-7tqKE3OV7/ybeDOORX++USYYCBJa7IgTya2czFpzbgXGo7CQDVyuv+0J1DggjRcEqhhXQA4MUhgnhcRaZvHxWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@layerstack/utils": "^2.0.0-next.12", + "clsx": "^2.1.1", + "d3-array": "^3.2.4", + "lodash-es": "^4.17.21", + "tailwind-merge": "^3.2.0" + } + }, + "node_modules/@layerstack/utils": { + "version": "2.0.0-next.12", + "resolved": "https://registry.npmjs.org/@layerstack/utils/-/utils-2.0.0-next.12.tgz", + "integrity": "sha512-fhGZUlSr3N+D44BYm37WKMGSEFyZBW+dwIqtGU8Cl54mR4TLQ/UwyGhdpgIHyH/x/8q1abE0fP0Dn6ZsrDE3BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/@libsql/client": { "version": "0.15.14", "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.14.tgz", @@ -2794,6 +2871,20 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -2852,6 +2943,42 @@ "@types/node": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -3915,6 +4042,16 @@ "color-support": "bin.js" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4071,6 +4208,329 @@ "node": ">=4" } }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-voronoi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-geo-voronoi/-/d3-geo-voronoi-2.1.0.tgz", + "integrity": "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-delaunay": "6", + "d3-geo": "3", + "d3-tricontour": "1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate-path": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.3.0.tgz", + "integrity": "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-tile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d3-tile/-/d3-tile-1.0.0.tgz", + "integrity": "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-tricontour": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-tricontour/-/d3-tricontour-1.0.2.tgz", + "integrity": "sha512-HIRxHzHagPtUPNabjOlfcyismJYIsc+Xlq4mlsts4e8eAcwyq9Tgk/sYdyhlBpQ0MHwVquc/8j+e29YjXnmxeA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-delaunay": "6", + "d3-scale": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -4149,6 +4609,16 @@ "node": ">=0.10.0" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -5647,6 +6117,15 @@ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", @@ -5845,6 +6324,62 @@ "dev": true, "license": "MIT" }, + "node_modules/layerchart": { + "version": "2.0.0-next.27", + "resolved": "https://registry.npmjs.org/layerchart/-/layerchart-2.0.0-next.27.tgz", + "integrity": "sha512-yt28xU8WzXq0AliX7eiC0JKZGQtO8M9FmHvt8sESNitSc/yC+fYeTghaO9lMRwcYCmi6D1NjbFyD9mWFeazNIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dagrejs/dagre": "^1.1.4", + "@layerstack/svelte-actions": "1.0.1-next.12", + "@layerstack/svelte-state": "0.1.0-next.17", + "@layerstack/tailwind": "2.0.0-next.15", + "@layerstack/utils": "2.0.0-next.12", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-dsv": "^3.0.1", + "d3-force": "^3.0.0", + "d3-geo": "^3.1.1", + "d3-geo-voronoi": "^2.1.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-interpolate-path": "^2.3.0", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-sankey": "^0.12.3", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-tile": "^1.0.0", + "d3-time": "^3.1.0", + "lodash-es": "^4.17.21", + "memoize": "^10.1.0", + "runed": "^0.28.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/layerchart/node_modules/runed": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz", + "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6177,6 +6712,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -6277,6 +6819,22 @@ "node": ">= 0.8" } }, + "node_modules/memoize": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.1.0.tgz", + "integrity": "sha512-MMbFhJzh4Jlg/poq1si90XRlTZRDHVqdlz2mPyGJ6kqMpyHUyVpDd5gpFAvVehW64+RA1eKE9Yt8aSLY7w2Kgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -6371,6 +6929,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -7605,6 +8176,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "dev": true, + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.47.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", @@ -7701,6 +8279,13 @@ "svelte": "^5.7.0" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", From 124d2bed28d206124eca688c1175269f1e334234 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Fri, 12 Sep 2025 18:16:28 +0530 Subject: [PATCH 10/27] WIP: UI refresh --- .../custom/tabs/InsuranceTab.svelte | 10 -- .../custom/tabs/MaintenenceLogTab.svelte | 10 -- .../components/custom/tabs/OverviewTab.svelte | 12 -- .../config}/ConfigForm.svelte | 2 +- .../config}/ConfigModal.svelte | 4 +- .../dashboard/fuel}/FuelLogForm.svelte | 8 +- .../dashboard/fuel}/FuelLogList.svelte | 2 +- .../dashboard/fuel}/FuelLogModal.svelte | 4 +- .../dashboard/fuel}/FuelLogTab.svelte | 4 +- .../dashboard/fuel}/FuelLogTable.svelte | 0 .../dashboard/insurance}/InsuranceForm.svelte | 6 +- .../dashboard/insurance}/InsuranceList.svelte | 4 +- .../insurance}/InsuranceModal.svelte | 4 +- .../dashboard/insurance/InsuranceTab.svelte | 10 ++ .../maintenance}/MaintenanceLogForm.svelte | 6 +- .../maintenance}/MaintenanceLogList.svelte | 4 +- .../maintenance}/MaintenanceLogModal.svelte | 4 +- .../maintenance/MaintenenceLogTab.svelte | 10 ++ .../dashboard/overview}/ChartCard.svelte | 0 .../overview}/DashboardCharts.svelte | 0 .../dashboard/overview}/DashboardTabs.svelte | 8 +- .../dashboard/overview}/MileageChart.svelte | 0 .../dashboard/overview/OverviewTab.svelte | 11 ++ .../PollutionCertificateForm.svelte | 6 +- .../PollutionCertificateList.svelte | 4 +- .../PollutionCertificateModal.svelte | 4 +- .../dashboard/pullution}/PollutionTab.svelte | 4 +- .../forms => features/login}/LoginForm.svelte | 3 +- .../auth => features/login}/PinInput.svelte | 0 .../vehicle}/VehicleCard copy.svelte | 4 +- .../vehicle}/VehicleCard.svelte | 9 +- .../vehicle}/VehicleForm.svelte | 6 +- .../vehicle}/VehicleList.svelte | 2 +- .../vehicle}/VehicleModal.svelte | 4 +- .../{custom/common => ui/app}/Button.svelte | 0 .../{custom/common => ui/app}/Checkbox.svelte | 0 .../app}/DeleteConfirmation.svelte | 0 .../common => ui/app}/FormField.svelte | 0 .../common => ui/app}/IconButton.svelte | 0 .../common => ui/app}/IconWithTooltip.svelte | 0 .../common => ui/app}/LabelWithIcon.svelte | 0 .../common => ui/app}/ModalContainer.svelte | 0 .../common => ui/app}/StatusBlock.svelte | 0 .../common => ui/app}/TabContainer.svelte | 0 .../common => ui/app}/ThemeToggle.svelte | 0 app/frontend/src/lib/types/fuel-log.ts | 2 +- app/frontend/src/models/fuel-log.ts | 168 ------------------ .../src/routes/dashboard/+layout.svelte | 2 +- .../src/routes/dashboard/+page.svelte | 18 +- app/frontend/src/routes/login/+page.svelte | 4 +- app/frontend/svelte.config.js | 5 +- app/frontend/vite.config.ts | 5 +- 52 files changed, 100 insertions(+), 273 deletions(-) delete mode 100644 app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte delete mode 100644 app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte delete mode 100644 app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte rename app/frontend/src/lib/components/{custom/forms => features/config}/ConfigForm.svelte (98%) rename app/frontend/src/lib/components/{custom/modals => features/config}/ConfigModal.svelte (79%) rename app/frontend/src/lib/components/{custom/forms => features/dashboard/fuel}/FuelLogForm.svelte (94%) rename app/frontend/src/lib/components/{custom/lists => features/dashboard/fuel}/FuelLogList.svelte (98%) rename app/frontend/src/lib/components/{custom/modals => features/dashboard/fuel}/FuelLogModal.svelte (85%) rename app/frontend/src/lib/components/{custom/tabs => features/dashboard/fuel}/FuelLogTab.svelte (51%) rename app/frontend/src/lib/components/{custom/lists => features/dashboard/fuel}/FuelLogTable.svelte (100%) rename app/frontend/src/lib/components/{custom/forms => features/dashboard/insurance}/InsuranceForm.svelte (95%) rename app/frontend/src/lib/components/{custom/lists => features/dashboard/insurance}/InsuranceList.svelte (97%) rename app/frontend/src/lib/components/{custom/modals => features/dashboard/insurance}/InsuranceModal.svelte (84%) create mode 100644 app/frontend/src/lib/components/features/dashboard/insurance/InsuranceTab.svelte rename app/frontend/src/lib/components/{custom/forms => features/dashboard/maintenance}/MaintenanceLogForm.svelte (95%) rename app/frontend/src/lib/components/{custom/lists => features/dashboard/maintenance}/MaintenanceLogList.svelte (97%) rename app/frontend/src/lib/components/{custom/modals => features/dashboard/maintenance}/MaintenanceLogModal.svelte (84%) create mode 100644 app/frontend/src/lib/components/features/dashboard/maintenance/MaintenenceLogTab.svelte rename app/frontend/src/lib/components/{custom/chart => features/dashboard/overview}/ChartCard.svelte (100%) rename app/frontend/src/lib/components/{custom/chart => features/dashboard/overview}/DashboardCharts.svelte (100%) rename app/frontend/src/lib/components/{custom/tabs => features/dashboard/overview}/DashboardTabs.svelte (81%) rename app/frontend/src/lib/components/{custom/chart => features/dashboard/overview}/MileageChart.svelte (100%) create mode 100644 app/frontend/src/lib/components/features/dashboard/overview/OverviewTab.svelte rename app/frontend/src/lib/components/{custom/forms => features/dashboard/pullution}/PollutionCertificateForm.svelte (95%) rename app/frontend/src/lib/components/{custom/lists => features/dashboard/pullution}/PollutionCertificateList.svelte (97%) rename app/frontend/src/lib/components/{custom/modals => features/dashboard/pullution}/PollutionCertificateModal.svelte (83%) rename app/frontend/src/lib/components/{custom/tabs => features/dashboard/pullution}/PollutionTab.svelte (50%) rename app/frontend/src/lib/components/{custom/forms => features/login}/LoginForm.svelte (89%) rename app/frontend/src/lib/components/{custom/auth => features/login}/PinInput.svelte (100%) rename app/frontend/src/lib/components/{custom/common => features/vehicle}/VehicleCard copy.svelte (97%) rename app/frontend/src/lib/components/{custom/common => features/vehicle}/VehicleCard.svelte (95%) rename app/frontend/src/lib/components/{custom/forms => features/vehicle}/VehicleForm.svelte (96%) rename app/frontend/src/lib/components/{custom/lists => features/vehicle}/VehicleList.svelte (94%) rename app/frontend/src/lib/components/{custom/modals => features/vehicle}/VehicleModal.svelte (82%) rename app/frontend/src/lib/components/{custom/common => ui/app}/Button.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/Checkbox.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/DeleteConfirmation.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/FormField.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/IconButton.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/IconWithTooltip.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/LabelWithIcon.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/ModalContainer.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/StatusBlock.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/TabContainer.svelte (100%) rename app/frontend/src/lib/components/{custom/common => ui/app}/ThemeToggle.svelte (100%) delete mode 100644 app/frontend/src/models/fuel-log.ts diff --git a/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte b/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte deleted file mode 100644 index 24e18037..00000000 --- a/app/frontend/src/lib/components/custom/tabs/InsuranceTab.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte b/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte deleted file mode 100644 index 14160b0b..00000000 --- a/app/frontend/src/lib/components/custom/tabs/MaintenenceLogTab.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte b/app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte deleted file mode 100644 index d0688819..00000000 --- a/app/frontend/src/lib/components/custom/tabs/OverviewTab.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/app/frontend/src/lib/components/custom/forms/ConfigForm.svelte b/app/frontend/src/lib/components/features/config/ConfigForm.svelte similarity index 98% rename from app/frontend/src/lib/components/custom/forms/ConfigForm.svelte rename to app/frontend/src/lib/components/features/config/ConfigForm.svelte index 40f08118..056714bc 100644 --- a/app/frontend/src/lib/components/custom/forms/ConfigForm.svelte +++ b/app/frontend/src/lib/components/features/config/ConfigForm.svelte @@ -1,5 +1,5 @@ diff --git a/app/frontend/src/lib/components/custom/lists/FuelLogTable.svelte b/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogTable.svelte similarity index 100% rename from app/frontend/src/lib/components/custom/lists/FuelLogTable.svelte rename to app/frontend/src/lib/components/features/dashboard/fuel/FuelLogTable.svelte diff --git a/app/frontend/src/lib/components/custom/forms/InsuranceForm.svelte b/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceForm.svelte similarity index 95% rename from app/frontend/src/lib/components/custom/forms/InsuranceForm.svelte rename to app/frontend/src/lib/components/features/dashboard/insurance/InsuranceForm.svelte index c020b220..13a332c5 100644 --- a/app/frontend/src/lib/components/custom/forms/InsuranceForm.svelte +++ b/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceForm.svelte @@ -1,7 +1,7 @@ + + + + diff --git a/app/frontend/src/lib/components/custom/forms/MaintenanceLogForm.svelte b/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogForm.svelte similarity index 95% rename from app/frontend/src/lib/components/custom/forms/MaintenanceLogForm.svelte rename to app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogForm.svelte index 8f931b16..cb275808 100644 --- a/app/frontend/src/lib/components/custom/forms/MaintenanceLogForm.svelte +++ b/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogForm.svelte @@ -1,7 +1,7 @@ + + + + diff --git a/app/frontend/src/lib/components/custom/chart/ChartCard.svelte b/app/frontend/src/lib/components/features/dashboard/overview/ChartCard.svelte similarity index 100% rename from app/frontend/src/lib/components/custom/chart/ChartCard.svelte rename to app/frontend/src/lib/components/features/dashboard/overview/ChartCard.svelte diff --git a/app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte b/app/frontend/src/lib/components/features/dashboard/overview/DashboardCharts.svelte similarity index 100% rename from app/frontend/src/lib/components/custom/chart/DashboardCharts.svelte rename to app/frontend/src/lib/components/features/dashboard/overview/DashboardCharts.svelte diff --git a/app/frontend/src/lib/components/custom/tabs/DashboardTabs.svelte b/app/frontend/src/lib/components/features/dashboard/overview/DashboardTabs.svelte similarity index 81% rename from app/frontend/src/lib/components/custom/tabs/DashboardTabs.svelte rename to app/frontend/src/lib/components/features/dashboard/overview/DashboardTabs.svelte index f9e041e1..d17bd508 100644 --- a/app/frontend/src/lib/components/custom/tabs/DashboardTabs.svelte +++ b/app/frontend/src/lib/components/features/dashboard/overview/DashboardTabs.svelte @@ -3,10 +3,10 @@ import { ToyBrick } from '@lucide/svelte'; import OverviewTab from './OverviewTab.svelte'; import type { Component } from 'svelte'; - import FuelLogTab from './FuelLogTab.svelte'; - import MaintenenceLogTab from './MaintenenceLogTab.svelte'; - import InsuranceTab from './InsuranceTab.svelte'; - import PollutionTab from './PollutionTab.svelte'; + import FuelLogTab from '../fuel/FuelLogTab.svelte'; + import MaintenenceLogTab from '../maintenance/MaintenenceLogTab.svelte'; + import InsuranceTab from '../insurance/InsuranceTab.svelte'; + import PollutionTab from '../pullution/PollutionTab.svelte'; let { vehicleId = $bindable() } = $props(); const tabs: { diff --git a/app/frontend/src/lib/components/custom/chart/MileageChart.svelte b/app/frontend/src/lib/components/features/dashboard/overview/MileageChart.svelte similarity index 100% rename from app/frontend/src/lib/components/custom/chart/MileageChart.svelte rename to app/frontend/src/lib/components/features/dashboard/overview/MileageChart.svelte diff --git a/app/frontend/src/lib/components/features/dashboard/overview/OverviewTab.svelte b/app/frontend/src/lib/components/features/dashboard/overview/OverviewTab.svelte new file mode 100644 index 00000000..c056701c --- /dev/null +++ b/app/frontend/src/lib/components/features/dashboard/overview/OverviewTab.svelte @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/frontend/src/lib/components/custom/forms/PollutionCertificateForm.svelte b/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateForm.svelte similarity index 95% rename from app/frontend/src/lib/components/custom/forms/PollutionCertificateForm.svelte rename to app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateForm.svelte index 69daca8c..a1eda5b3 100644 --- a/app/frontend/src/lib/components/custom/forms/PollutionCertificateForm.svelte +++ b/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateForm.svelte @@ -1,7 +1,7 @@ diff --git a/app/frontend/src/lib/components/custom/forms/LoginForm.svelte b/app/frontend/src/lib/components/features/login/LoginForm.svelte similarity index 89% rename from app/frontend/src/lib/components/custom/forms/LoginForm.svelte rename to app/frontend/src/lib/components/features/login/LoginForm.svelte index 8c8d84be..6939344d 100644 --- a/app/frontend/src/lib/components/custom/forms/LoginForm.svelte +++ b/app/frontend/src/lib/components/features/login/LoginForm.svelte @@ -1,9 +1,8 @@ diff --git a/app/frontend/src/lib/components/custom/auth/PinInput.svelte b/app/frontend/src/lib/components/features/login/PinInput.svelte similarity index 100% rename from app/frontend/src/lib/components/custom/auth/PinInput.svelte rename to app/frontend/src/lib/components/features/login/PinInput.svelte diff --git a/app/frontend/src/lib/components/custom/common/VehicleCard copy.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleCard copy.svelte similarity index 97% rename from app/frontend/src/lib/components/custom/common/VehicleCard copy.svelte rename to app/frontend/src/lib/components/features/vehicle/VehicleCard copy.svelte index d4cbdda5..c0f77254 100644 --- a/app/frontend/src/lib/components/custom/common/VehicleCard copy.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleCard copy.svelte @@ -20,8 +20,8 @@ import { puccModelStore } from '$stores/pucc'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import IconButton from './IconButton.svelte'; - import DeleteConfirmation from './DeleteConfirmation.svelte'; + import IconButton from '../../ui/app/IconButton.svelte'; + import DeleteConfirmation from '../../ui/app/DeleteConfirmation.svelte'; import * as Card from '$lib/components/ui/card'; import Badge from '$lib/components/ui/badge/badge.svelte'; diff --git a/app/frontend/src/lib/components/custom/common/VehicleCard.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte similarity index 95% rename from app/frontend/src/lib/components/custom/common/VehicleCard.svelte rename to app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte index 0a9ce5d6..5417654e 100644 --- a/app/frontend/src/lib/components/custom/common/VehicleCard.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte @@ -9,7 +9,6 @@ BadgeCheck, CircleDotDashed, ShieldCheck, - ShieldClose, BadgeAlert, ShieldAlert } from '@lucide/svelte'; @@ -21,13 +20,11 @@ import { puccModelStore } from '$stores/pucc'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import IconButton from './IconButton.svelte'; - import DeleteConfirmation from './DeleteConfirmation.svelte'; + import IconButton from '$appui/IconButton.svelte'; + import DeleteConfirmation from '$appui/DeleteConfirmation.svelte'; import * as Card from '$lib/components/ui/card'; import Badge from '$lib/components/ui/badge/badge.svelte'; - import * as Tooltip from '$lib/components/ui/tooltip'; - import { buttonVariants } from '$lib/components/ui/button'; - import IconWithTooltip from './IconWithTooltip.svelte'; + import IconWithTooltip from '$appui/IconWithTooltip.svelte'; const { vehicle, updateCallback, isSelected = false } = $props(); let deleteDialog = $state(false); diff --git a/app/frontend/src/lib/components/custom/forms/VehicleForm.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte similarity index 96% rename from app/frontend/src/lib/components/custom/forms/VehicleForm.svelte rename to app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte index 5aa5fcbc..2e5c05e1 100644 --- a/app/frontend/src/lib/components/custom/forms/VehicleForm.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte @@ -8,13 +8,13 @@ Gauge, Building2 } from '@lucide/svelte'; - import FormField from '../common/FormField.svelte'; + import FormField from '$appui/FormField.svelte'; import type { NewVehicle } from '$models/vehicle'; import { env } from '$env/dynamic/public'; import { vehiclesStore } from '$stores/vehicle'; import { browser } from '$app/environment'; - import Button from '$appui/common/Button.svelte'; - import StatusBlock from '$appui/common/StatusBlock.svelte'; + import Button from '$appui/Button.svelte'; + import StatusBlock from '$appui/StatusBlock.svelte'; import type { Status } from '$models/status'; import { handleApiError, type ApiError } from '$models/Error'; import { cleanup } from '$utils/formatting'; diff --git a/app/frontend/src/lib/components/custom/lists/VehicleList.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleList.svelte similarity index 94% rename from app/frontend/src/lib/components/custom/lists/VehicleList.svelte rename to app/frontend/src/lib/components/features/vehicle/VehicleList.svelte index 028e68fe..3e0fc83c 100644 --- a/app/frontend/src/lib/components/custom/lists/VehicleList.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleList.svelte @@ -1,6 +1,6 @@ - - - - {#each tabs as tab} - {tab.name} - {/each} - - {#each tabs as { id, Component }} - - - - {/each} - diff --git a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogForm.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogForm.svelte similarity index 98% rename from app/frontend/src/lib/components/features/dashboard/fuel/FuelLogForm.svelte rename to app/frontend/src/lib/components/features/fuel/FuelLogForm.svelte index ba8356c6..97287021 100644 --- a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogForm.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogForm.svelte @@ -5,7 +5,7 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$models/Error'; import type { Status } from '$models/status'; - import { cleanup, getCurrencySymbol, getDistanceUnit, getVolumeUnit } from '$utils/formatting'; + import { cleanup, getCurrencySymbol, getDistanceUnit, getVolumeUnit } from '$helper/formatting'; import FormField from '$appui/FormField.svelte'; import { Calendar1, @@ -15,7 +15,7 @@ BadgeDollarSign, CircleSlash, ArrowBigLeftDash - } from '@lucide/svelte'; + } from '@lucide/svelte/icons'; let { vehicleId, diff --git a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogList.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte similarity index 98% rename from app/frontend/src/lib/components/features/dashboard/fuel/FuelLogList.svelte rename to app/frontend/src/lib/components/features/fuel/FuelLogList.svelte index 5b081f01..52b02a9c 100644 --- a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogList.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte @@ -3,7 +3,7 @@ import { env } from '$env/dynamic/public'; import { Jumper } from 'svelte-loading-spinners'; import DeleteConfirmation from '$appui/DeleteConfirmation.svelte'; - import { getApiUrl } from '$utils/api'; + import { getApiUrl } from '$helper/api'; import { columns, type FuelLog } from '$models/fuel-log'; import FuelLogTable from './FuelLogTable.svelte'; @@ -22,7 +22,6 @@ } else { fetchFuelLogs(); } - $inspect(fuelLogs); }); async function fetchFuelLogs() { diff --git a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogModal.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogModal.svelte similarity index 91% rename from app/frontend/src/lib/components/features/dashboard/fuel/FuelLogModal.svelte rename to app/frontend/src/lib/components/features/fuel/FuelLogModal.svelte index 931f42c3..f123a399 100644 --- a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogModal.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogModal.svelte @@ -1,5 +1,5 @@ diff --git a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogTable.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte similarity index 97% rename from app/frontend/src/lib/components/features/dashboard/fuel/FuelLogTable.svelte rename to app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte index 930c217d..6fc7b7c0 100644 --- a/app/frontend/src/lib/components/features/dashboard/fuel/FuelLogTable.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte @@ -2,7 +2,6 @@ import { getCoreRowModel, type ColumnDef } from '@tanstack/table-core'; import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table/index.js'; import * as Table from '$lib/components/ui/table/index.js'; - import { Calendar1 } from '@lucide/svelte'; type DataTableProps = { columns: ColumnDef[]; diff --git a/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceForm.svelte b/app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte similarity index 97% rename from app/frontend/src/lib/components/features/dashboard/insurance/InsuranceForm.svelte rename to app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte index 13a332c5..9fbdfeb4 100644 --- a/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceForm.svelte +++ b/app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte @@ -5,8 +5,8 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$models/Error'; import type { Status } from '$models/status'; - import { cleanup, getCurrencySymbol } from '$utils/formatting'; - import { BadgeDollarSign, Building2, Calendar1, IdCard, Notebook } from '@lucide/svelte'; + import { cleanup, getCurrencySymbol } from '$helper/formatting'; + import { BadgeDollarSign, Building2, Calendar1, IdCard, Notebook } from '@lucide/svelte/icons'; let { vehicleId, diff --git a/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceList.svelte b/app/frontend/src/lib/components/features/insurance/InsuranceList.svelte similarity index 97% rename from app/frontend/src/lib/components/features/dashboard/insurance/InsuranceList.svelte rename to app/frontend/src/lib/components/features/insurance/InsuranceList.svelte index fadd122d..7a0b863b 100644 --- a/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceList.svelte +++ b/app/frontend/src/lib/components/features/insurance/InsuranceList.svelte @@ -2,12 +2,12 @@ import { onMount } from 'svelte'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import { Shield, Calendar, Hash, DollarSign, Trash2, Notebook } from '@lucide/svelte'; - import { formatCurrency, formatDate } from '$utils/formatting'; + import { Shield, Calendar, Hash, DollarSign, Trash2, Notebook } from '@lucide/svelte/icons'; + import { formatCurrency, formatDate } from '$helper/formatting'; import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$appui/IconButton.svelte'; import DeleteConfirmation from '$appui/DeleteConfirmation.svelte'; - import { getApiUrl } from '$utils/api'; + import { getApiUrl } from '$helper/api'; let { vehicleId } = $props(); diff --git a/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceModal.svelte b/app/frontend/src/lib/components/features/insurance/InsuranceModal.svelte similarity index 79% rename from app/frontend/src/lib/components/features/dashboard/insurance/InsuranceModal.svelte rename to app/frontend/src/lib/components/features/insurance/InsuranceModal.svelte index f922e99b..62b6225e 100644 --- a/app/frontend/src/lib/components/features/dashboard/insurance/InsuranceModal.svelte +++ b/app/frontend/src/lib/components/features/insurance/InsuranceModal.svelte @@ -1,7 +1,7 @@ {#if showModal} - + import TabContainer from '$appui/TabContainer.svelte'; - import InsuranceDetailsList from '$lib/components/features/dashboard/insurance/InsuranceList.svelte'; + import InsuranceDetailsList from './InsuranceList.svelte'; let { vehicleId } = $props(); diff --git a/app/frontend/src/lib/components/features/login/LoginForm.svelte b/app/frontend/src/lib/components/features/login/LoginForm.svelte index 6939344d..ed640bd4 100644 --- a/app/frontend/src/lib/components/features/login/LoginForm.svelte +++ b/app/frontend/src/lib/components/features/login/LoginForm.svelte @@ -1,24 +1,22 @@ -

- -
- -
- Tracktor -
+

Welcome to Tracktor

-
-

- Enter your 6-digit PIN to access the app -

+ + diff --git a/app/frontend/src/lib/components/features/login/PinInput.svelte b/app/frontend/src/lib/components/features/login/PinInput.svelte index 81da53cb..6437e341 100644 --- a/app/frontend/src/lib/components/features/login/PinInput.svelte +++ b/app/frontend/src/lib/components/features/login/PinInput.svelte @@ -25,11 +25,11 @@ {/each} diff --git a/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogForm.svelte b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogForm.svelte similarity index 96% rename from app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogForm.svelte rename to app/frontend/src/lib/components/features/maintenance/MaintenanceLogForm.svelte index cb275808..10a9efb3 100644 --- a/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogForm.svelte +++ b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogForm.svelte @@ -5,8 +5,8 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$models/Error'; import type { Status } from '$models/status'; - import { cleanup, getCurrencySymbol, getDistanceUnit } from '$utils/formatting'; - import { BadgeDollarSign, Calendar1, Gauge, Hammer, Notebook } from '@lucide/svelte'; + import { cleanup, getCurrencySymbol, getDistanceUnit } from '$helper/formatting'; + import { BadgeDollarSign, Calendar1, Gauge, Hammer, Notebook } from '@lucide/svelte/icons'; let { vehicleId, diff --git a/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogList.svelte b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte similarity index 96% rename from app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogList.svelte rename to app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte index d7829dac..c2c56ab4 100644 --- a/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogList.svelte +++ b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte @@ -2,12 +2,12 @@ import { onMount } from 'svelte'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import { formatCurrency, formatDate, formatDistance } from '$utils/formatting'; - import { Trash2 } from '@lucide/svelte'; + import { formatCurrency, formatDate, formatDistance } from '$helper/formatting'; + import { Trash2 } from '@lucide/svelte/icons'; import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$appui/IconButton.svelte'; import DeleteConfirmation from '$appui/DeleteConfirmation.svelte'; - import { getApiUrl } from '$utils/api'; + import { getApiUrl } from '$helper/api'; let { vehicleId } = $props(); diff --git a/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogModal.svelte b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogModal.svelte similarity index 88% rename from app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogModal.svelte rename to app/frontend/src/lib/components/features/maintenance/MaintenanceLogModal.svelte index bff0ecf5..01dec5be 100644 --- a/app/frontend/src/lib/components/features/dashboard/maintenance/MaintenanceLogModal.svelte +++ b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogModal.svelte @@ -1,5 +1,5 @@ diff --git a/app/frontend/src/lib/components/features/dashboard/overview/ChartCard.svelte b/app/frontend/src/lib/components/features/overview/ChartCard.svelte similarity index 100% rename from app/frontend/src/lib/components/features/dashboard/overview/ChartCard.svelte rename to app/frontend/src/lib/components/features/overview/ChartCard.svelte diff --git a/app/frontend/src/lib/components/features/dashboard/overview/DashboardCharts.svelte b/app/frontend/src/lib/components/features/overview/DashboardCharts.svelte similarity index 99% rename from app/frontend/src/lib/components/features/dashboard/overview/DashboardCharts.svelte rename to app/frontend/src/lib/components/features/overview/DashboardCharts.svelte index fae37003..cb95add3 100644 --- a/app/frontend/src/lib/components/features/dashboard/overview/DashboardCharts.svelte +++ b/app/frontend/src/lib/components/features/overview/DashboardCharts.svelte @@ -2,7 +2,7 @@ import { env } from '$env/dynamic/public'; import { darkModeStore } from '$stores/dark-mode'; import { Chart, registerables, type ChartOptions } from 'chart.js'; - import { formatDate, getCurrencySymbol, getMileageUnit } from '$utils/formatting'; + import { formatDate, getCurrencySymbol, getMileageUnit } from '$helper/formatting'; import ChartCard from './ChartCard.svelte'; import { Line } from 'svelte5-chartjs'; diff --git a/app/frontend/src/lib/components/features/dashboard/overview/MileageChart.svelte b/app/frontend/src/lib/components/features/overview/MileageChart.svelte similarity index 97% rename from app/frontend/src/lib/components/features/dashboard/overview/MileageChart.svelte rename to app/frontend/src/lib/components/features/overview/MileageChart.svelte index 8da5c77d..3435c9a2 100644 --- a/app/frontend/src/lib/components/features/dashboard/overview/MileageChart.svelte +++ b/app/frontend/src/lib/components/features/overview/MileageChart.svelte @@ -1,7 +1,7 @@ - diff --git a/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateForm.svelte b/app/frontend/src/lib/components/features/pullution/PollutionCertificateForm.svelte similarity index 98% rename from app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateForm.svelte rename to app/frontend/src/lib/components/features/pullution/PollutionCertificateForm.svelte index a1eda5b3..9988e087 100644 --- a/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateForm.svelte +++ b/app/frontend/src/lib/components/features/pullution/PollutionCertificateForm.svelte @@ -5,8 +5,8 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$models/Error'; import type { Status } from '$models/status'; - import { cleanup } from '$utils/formatting'; - import { Calendar1, IdCard, Notebook, TestTube2 } from '@lucide/svelte'; + import { cleanup } from '$helper/formatting'; + import { Calendar1, IdCard, Notebook, TestTube2 } from '@lucide/svelte/icons'; let { vehicleId, diff --git a/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateList.svelte b/app/frontend/src/lib/components/features/pullution/PollutionCertificateList.svelte similarity index 97% rename from app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateList.svelte rename to app/frontend/src/lib/components/features/pullution/PollutionCertificateList.svelte index 25eff101..0973f239 100644 --- a/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateList.svelte +++ b/app/frontend/src/lib/components/features/pullution/PollutionCertificateList.svelte @@ -2,12 +2,12 @@ import { onMount } from 'svelte'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import { FileText, Calendar, MapPin, Trash2, BadgeCheck } from '@lucide/svelte'; - import { formatDate } from '$utils/formatting'; + import { FileText, Calendar, MapPin, Trash2, BadgeCheck } from '@lucide/svelte/icons'; + import { formatDate } from '$helper/formatting'; import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$appui/IconButton.svelte'; import DeleteConfirmation from '$appui/DeleteConfirmation.svelte'; - import { getApiUrl } from '$utils/api'; + import { getApiUrl } from '$helper/api'; let { vehicleId } = $props(); diff --git a/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateModal.svelte b/app/frontend/src/lib/components/features/pullution/PollutionCertificateModal.svelte similarity index 87% rename from app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateModal.svelte rename to app/frontend/src/lib/components/features/pullution/PollutionCertificateModal.svelte index 64fe8457..3ad0d4fe 100644 --- a/app/frontend/src/lib/components/features/dashboard/pullution/PollutionCertificateModal.svelte +++ b/app/frontend/src/lib/components/features/pullution/PollutionCertificateModal.svelte @@ -1,7 +1,7 @@ diff --git a/app/frontend/src/lib/components/features/vehicle/LicensePlate.svelte b/app/frontend/src/lib/components/features/vehicle/LicensePlate.svelte new file mode 100644 index 00000000..4d2fbda4 --- /dev/null +++ b/app/frontend/src/lib/components/features/vehicle/LicensePlate.svelte @@ -0,0 +1,14 @@ + + +
+
+ +
+

{registrationNumber}

+
diff --git a/app/frontend/src/lib/components/features/vehicle/VehicleCard copy.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleCard copy.svelte deleted file mode 100644 index c0f77254..00000000 --- a/app/frontend/src/lib/components/features/vehicle/VehicleCard copy.svelte +++ /dev/null @@ -1,192 +0,0 @@ - - - -
- {vehicle.make} {vehicle.model} - -
- -
-
- - {vehicle.make} {vehicle.model} -
- {vehicle.year} -
-
- - -
-

- - License Plate: - {vehicle.licensePlate} -

-

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

- -

- - Color: - {#if vehicle.color} - - {:else} - - - {/if} -

-

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

- {#if vehicle.insuranceStatus} -

- - Insurance: - - {vehicle.insuranceStatus} - -

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

- - PUCC: - - {vehicle.puccStatus} - -

- {/if} -
-
- - -
-
- fuelLogModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Log fuel refill" - /> - maintenanceModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Maintenence" - /> - insuranceModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Insurance" - /> - puccModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Pollution Certificate" - /> -
-
- { - vehicleModelStore.show(vehicle, true); - }} - ariaLabel="Edit" - /> - (deleteDialog = true)} - ariaLabel="Delete" - /> -
-
-
-
-
-
- deleteVehicle(vehicle.id)} bind:open={deleteDialog} /> diff --git a/app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte index 5417654e..a65b2764 100644 --- a/app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleCard.svelte @@ -1,4 +1,6 @@ - - -
- car -
-
-
-
-
- {vehicle.make} {vehicle.model} -
-

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

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

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

-
-
- -
- - + + +
+ car
-
- - -
-
-
-
- +
+
+
+
+ {vehicle.make} {vehicle.model} +
+

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

+
+
+ +
-

{vehicle.licensePlate}

+
+ +
+ +
- {vehicle.year} -
- - -
-
- fuelLogModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Log fuel refill" - /> - maintenanceModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Maintenence" - /> - insuranceModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Insurance" - /> - puccModelStore.show(vehicle.id, null, false, updateCallback)} - ariaLabel="Pollution Certificate" - /> + + +
+ + {vehicle.year}
-
- { - vehicleModelStore.show(vehicle, true); - }} - ariaLabel="Edit" - /> - (deleteDialog = true)} - ariaLabel="Delete" - /> + + +
+
+ fuelLogModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Log fuel refill" + /> + maintenanceModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Maintenence" + /> + insuranceModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Insurance" + /> + puccModelStore.show(vehicle.id, null, false, updateCallback)} + ariaLabel="Pollution Certificate" + /> +
+
+ { + vehicleModelStore.show(vehicle, true); + }} + ariaLabel="Edit" + /> + (deleteDialog = true)} + ariaLabel="Delete" + /> +
-
- - + + +
deleteVehicle(vehicle.id)} bind:open={deleteDialog} /> diff --git a/app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte index 2e5c05e1..dff8218b 100644 --- a/app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleForm.svelte @@ -7,7 +7,7 @@ Paintbrush, Gauge, Building2 - } from '@lucide/svelte'; + } from '@lucide/svelte/icons'; import FormField from '$appui/FormField.svelte'; import type { NewVehicle } from '$models/vehicle'; import { env } from '$env/dynamic/public'; @@ -17,7 +17,7 @@ import StatusBlock from '$appui/StatusBlock.svelte'; import type { Status } from '$models/status'; import { handleApiError, type ApiError } from '$models/Error'; - import { cleanup } from '$utils/formatting'; + import { cleanup } from '$helper/formatting'; let { vehicleToEdit = null, editMode = false, modalVisibility = $bindable(), loading } = $props(); diff --git a/app/frontend/src/lib/components/features/vehicle/VehicleList.svelte b/app/frontend/src/lib/components/features/vehicle/VehicleList.svelte index 3e0fc83c..6801299e 100644 --- a/app/frontend/src/lib/components/features/vehicle/VehicleList.svelte +++ b/app/frontend/src/lib/components/features/vehicle/VehicleList.svelte @@ -1,38 +1,68 @@ +{#if loading} +
+ +
+{:else if error} + +{/if} + {#if vehicles.length > 0}
{#each vehicles as vehicle (vehicle.id)} -
selectVehicle(vehicle.id)} - onkeydown={(e) => { + onkeydown={(e: { key: string }) => { if (e.key === 'Enter' || e.key === ' ') selectVehicle(vehicle.id); }} - > - -
+ /> {/each}
{:else} - -
-

- No vehicles found. Add your first vehicle to begin. -

-
- + {/if} diff --git a/app/frontend/src/lib/components/layout/DashboardTabs.svelte b/app/frontend/src/lib/components/layout/DashboardTabs.svelte new file mode 100644 index 00000000..eb5136a0 --- /dev/null +++ b/app/frontend/src/lib/components/layout/DashboardTabs.svelte @@ -0,0 +1,64 @@ + + + + + {#each tabs as tab} + + + + {/each} + + {#each tabs as { id, Component } (id)} + + + + {/each} + diff --git a/app/frontend/src/lib/components/layout/Header.svelte b/app/frontend/src/lib/components/layout/Header.svelte new file mode 100644 index 00000000..1f4f2cb6 --- /dev/null +++ b/app/frontend/src/lib/components/layout/Header.svelte @@ -0,0 +1,62 @@ + + +
+
+ +
+
+ + + +
+
+
+
diff --git a/app/frontend/src/lib/components/ui/app/DeleteConfirmation.svelte b/app/frontend/src/lib/components/ui/app/DeleteConfirmation.svelte index 430d7cc9..ba5a4662 100644 --- a/app/frontend/src/lib/components/ui/app/DeleteConfirmation.svelte +++ b/app/frontend/src/lib/components/ui/app/DeleteConfirmation.svelte @@ -1,6 +1,6 @@
- {label} + {label} + {#if children} + {@render children()} + {/if}
diff --git a/app/frontend/src/lib/components/ui/app/ModalContainer.svelte b/app/frontend/src/lib/components/ui/app/ModalContainer.svelte index edc99e49..11dcd8ab 100644 --- a/app/frontend/src/lib/components/ui/app/ModalContainer.svelte +++ b/app/frontend/src/lib/components/ui/app/ModalContainer.svelte @@ -1,5 +1,5 @@ -
-

+
+

{title}

{@render children()} diff --git a/app/frontend/src/lib/components/ui/app/ThemeToggle.svelte b/app/frontend/src/lib/components/ui/app/ThemeToggle.svelte index 668b0fb3..d19e8ac8 100644 --- a/app/frontend/src/lib/components/ui/app/ThemeToggle.svelte +++ b/app/frontend/src/lib/components/ui/app/ThemeToggle.svelte @@ -3,10 +3,10 @@ import MoonIcon from '@lucide/svelte/icons/moon'; import { toggleMode } from 'mode-watcher'; - import { Button } from '$lib/components/ui/button/index.js'; + import { Button } from '$lib/components/ui/button'; - - -
-

-
- - +
{@render children()}
diff --git a/app/frontend/src/routes/dashboard/+page.svelte b/app/frontend/src/routes/dashboard/+page.svelte index 80190729..cca79f06 100644 --- a/app/frontend/src/routes/dashboard/+page.svelte +++ b/app/frontend/src/routes/dashboard/+page.svelte @@ -1,82 +1,52 @@ -
-
-

Your Vehicles

-
- {#if loading} -

- - Loading Vehicles... -

- {:else if error} -

Error: {error}

- {:else} - - {/if} + + {#if selectedVehicleId} - {:else if vehicles.length > 0 && !loading} + {:else}

- Select a vehicle to view fuel and mileage data. + Please select a vehicle from the list for more details

{/if} diff --git a/app/frontend/src/routes/login/+page.svelte b/app/frontend/src/routes/login/+page.svelte index ef64231c..2992f61f 100644 --- a/app/frontend/src/routes/login/+page.svelte +++ b/app/frontend/src/routes/login/+page.svelte @@ -1,10 +1,10 @@ + +
+ + + {#snippet child({ props })} + + {/snippet} + + + Edit + (showDeleteDialog = true)}> + Delete + + + +
+ + deleteFuelLog()} bind:open={showDeleteDialog} /> diff --git a/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte index 52b02a9c..ba40e3e7 100644 --- a/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte @@ -46,32 +46,6 @@ loading = false; } - async function deleteFuelLog(logId: string | undefined) { - if (!logId) { - 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(); }); @@ -152,6 +126,4 @@ --> - - deleteFuelLog(selectedFuelLog)} bind:open={deleteDialog} /> {/if} diff --git a/app/frontend/src/lib/components/features/fuel/FuelLogTab.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogTab.svelte index 63c2df9c..0c1793ba 100644 --- a/app/frontend/src/lib/components/features/fuel/FuelLogTab.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogTab.svelte @@ -5,6 +5,6 @@ let { vehicleId } = $props(); - + diff --git a/app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte index 6fc7b7c0..4d59b420 100644 --- a/app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte @@ -2,6 +2,8 @@ import { getCoreRowModel, type ColumnDef } from '@tanstack/table-core'; import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table/index.js'; import * as Table from '$lib/components/ui/table/index.js'; + import LabelWithIcon from '$lib/components/ui/app/LabelWithIcon.svelte'; + import { CircleSlash2 } from '@lucide/svelte'; type DataTableProps = { columns: ColumnDef[]; @@ -18,38 +20,46 @@ }); -
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - +
+ {#if data.length === 0} +
+ +
+ {:else} + + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + - {/each} - - {:else} - - No results. - - {/each} - - + + {/each} + + + {/if}
diff --git a/app/frontend/src/lib/components/features/overview/AreaChart.svelte b/app/frontend/src/lib/components/features/overview/AreaChart.svelte new file mode 100644 index 00000000..2bd670f4 --- /dev/null +++ b/app/frontend/src/lib/components/features/overview/AreaChart.svelte @@ -0,0 +1,101 @@ + + +
+
{title}
+ {#if chartData.length != 0} + + + {#snippet tooltip()} + + {/snippet} + {#snippet marks({ series, getAreaProps })} + {#each series as s, i (s.key)} + + {#snippet children({ gradient })} + + {/snippet} + + {/each} + {/snippet} + + + {:else} +
+ +
+ {/if} +
diff --git a/app/frontend/src/lib/components/features/overview/ChartCard.svelte b/app/frontend/src/lib/components/features/overview/ChartCard.svelte deleted file mode 100644 index ec75ab19..00000000 --- a/app/frontend/src/lib/components/features/overview/ChartCard.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -
-

{title}

- {#if chartData && chartData.labels && chartData.labels.length > 0} - - {:else} -

No data available for this vehicle.

- {/if} -
diff --git a/app/frontend/src/lib/components/features/overview/CostChart.svelte b/app/frontend/src/lib/components/features/overview/CostChart.svelte new file mode 100644 index 00000000..7a6bff17 --- /dev/null +++ b/app/frontend/src/lib/components/features/overview/CostChart.svelte @@ -0,0 +1,24 @@ + + + v.toLocaleDateString('en-IN', { day: '2-digit', month: 'short' })} + yFormatter={(v: number) => formatCurrency(v)} +/> diff --git a/app/frontend/src/lib/components/features/overview/DashboardCharts.svelte b/app/frontend/src/lib/components/features/overview/DashboardCharts.svelte deleted file mode 100644 index cb95add3..00000000 --- a/app/frontend/src/lib/components/features/overview/DashboardCharts.svelte +++ /dev/null @@ -1,150 +0,0 @@ - - -{#if loading} -

- -

-{:else if error} -

Error: {error}

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

No fuel or mileage data available for this vehicle.

-{:else} -
-
- - -
-
-{/if} diff --git a/app/frontend/src/lib/components/features/overview/MileageChart.svelte b/app/frontend/src/lib/components/features/overview/MileageChart.svelte index 3435c9a2..09d38637 100644 --- a/app/frontend/src/lib/components/features/overview/MileageChart.svelte +++ b/app/frontend/src/lib/components/features/overview/MileageChart.svelte @@ -1,96 +1,25 @@ - - v.toLocaleDateString('en-US', { month: 'short' }) - // } - }} - xScale={scaleBand()} - y="mileage" - > - {#snippet belowMarks({ series })} - {#each series as s} - d.mileage !== null)} - y={s.value} - class="[stroke-dasharray:3,3]" - stroke={s.color} - /> - {/each} - {/snippet} - {#snippet tooltip()} - - {/snippet} - - + v.toLocaleDateString('en-IN', { day: '2-digit', month: 'short' })} + yFormatter={(v: number) => formatMileage(v)} +/> diff --git a/app/frontend/src/lib/components/features/overview/OverviewTab.svelte b/app/frontend/src/lib/components/features/overview/OverviewTab.svelte index 582f9310..b32bea11 100644 --- a/app/frontend/src/lib/components/features/overview/OverviewTab.svelte +++ b/app/frontend/src/lib/components/features/overview/OverviewTab.svelte @@ -1,10 +1,16 @@ - +
+
+ + +
+
diff --git a/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte b/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte index efef55c7..29738eca 100644 --- a/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte +++ b/app/frontend/src/lib/components/ui/chart/chart-tooltip.svelte @@ -1,9 +1,9 @@ {#snippet TooltipLabel()} {#if formattedLabel} -
- {#if typeof formattedLabel === "function"} +
+ {#if typeof formattedLabel === 'function'} {@render formattedLabel()} {:else} {formattedLabel} @@ -86,7 +89,7 @@
{#each tooltipCtx.payload as item, i (item.key + i)} - {@const key = `${nameKey || item.key || item.name || "value"}`} + {@const key = `${nameKey || item.key || item.name || 'value'}`} {@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)} {@const indicatorColor = color || item.payload?.color || item.color}
svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5", - indicator === "dot" && "items-center" + '[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5', + indicator === 'dot' && 'items-center' )} > {#if formatter && item.value !== undefined && item.name} @@ -111,7 +114,7 @@ name: item.name, item, index: i, - payload: tooltipCtx.payload, + payload: tooltipCtx.payload })} {:else} {#if itemConfig?.icon} @@ -119,22 +122,18 @@ {:else if !hideIndicator}
{/if}
@@ -147,7 +146,7 @@
{#if item.value !== undefined} - {item.value.toLocaleString()} + {valueFormatter(item.value)} {/if}
diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 00000000..867d032d --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,40 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 00000000..907ef737 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte new file mode 100644 index 00000000..48d14a91 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -0,0 +1,22 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte new file mode 100644 index 00000000..aca1f7bd --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 00000000..64bb2831 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 00000000..f72e477e --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 00000000..189aef40 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 00000000..ddcad1ca --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,31 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 00000000..90f1b6f1 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 00000000..69749477 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 00000000..10e14ca6 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 00000000..2d4a92b4 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte new file mode 100644 index 00000000..cb053444 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/app/frontend/src/lib/components/ui/dropdown-menu/index.ts b/app/frontend/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 00000000..1cf9f701 --- /dev/null +++ b/app/frontend/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,49 @@ +import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; +import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; +import Content from "./dropdown-menu-content.svelte"; +import Group from "./dropdown-menu-group.svelte"; +import Item from "./dropdown-menu-item.svelte"; +import Label from "./dropdown-menu-label.svelte"; +import RadioGroup from "./dropdown-menu-radio-group.svelte"; +import RadioItem from "./dropdown-menu-radio-item.svelte"; +import Separator from "./dropdown-menu-separator.svelte"; +import Shortcut from "./dropdown-menu-shortcut.svelte"; +import Trigger from "./dropdown-menu-trigger.svelte"; +import SubContent from "./dropdown-menu-sub-content.svelte"; +import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; +import GroupHeading from "./dropdown-menu-group-heading.svelte"; +const Sub = DropdownMenuPrimitive.Sub; +const Root = DropdownMenuPrimitive.Root; + +export { + CheckboxItem, + Content, + Root as DropdownMenu, + CheckboxItem as DropdownMenuCheckboxItem, + Content as DropdownMenuContent, + Group as DropdownMenuGroup, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + RadioGroup as DropdownMenuRadioGroup, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + Shortcut as DropdownMenuShortcut, + Sub as DropdownMenuSub, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + Trigger as DropdownMenuTrigger, + GroupHeading as DropdownMenuGroupHeading, + Group, + GroupHeading, + Item, + Label, + RadioGroup, + RadioItem, + Root, + Separator, + Shortcut, + Sub, + SubContent, + SubTrigger, + Trigger, +}; diff --git a/app/frontend/src/lib/services/vehicle.service.ts b/app/frontend/src/lib/services/vehicle.service.ts new file mode 100644 index 00000000..a25a72d4 --- /dev/null +++ b/app/frontend/src/lib/services/vehicle.service.ts @@ -0,0 +1,64 @@ +import { getApiUrl } from '$lib/helper/api'; +import type { DataPoint } from '$lib/types'; + +export const fetchMileageData = async (vehicleId: string): Promise => { + let mileageData: DataPoint[] = []; + try { + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}/fuel-logs`), { + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + }); + if (response.ok) { + const data: { + date: string; + mileage: number | null; + }[] = await response.json(); + mileageData = data + .filter((log) => log.mileage != null) + .map((log) => { + return { + x: new Date(log.date), + y: log.mileage + }; + }) + .sort((a, b) => a.x.getTime() - b.x.getTime()); + } else { + console.error('Failed to fetch chart data'); + } + } catch (e) { + console.error('Failed to connect to the server.', e); + } + return mileageData; +}; + +export const fetchCostData = async (vehicleId: string): Promise => { + let costData: DataPoint[] = []; + try { + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}/fuel-logs`), { + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + }); + if (response.ok) { + const data: { + date: string; + cost: number | null; + }[] = await response.json(); + costData = data + .filter((log) => log.cost != null) + .map((log) => { + return { + x: new Date(log.date), + y: log.cost + }; + }) + .sort((a, b) => a.x.getTime() - b.x.getTime()); + } else { + console.error('Failed to fetch chart data'); + } + } catch (e) { + console.error('Failed to connect to the server.', e); + } + return costData; +}; diff --git a/app/frontend/src/lib/types/fuel-log.ts b/app/frontend/src/lib/types/fuel-log.ts index 261af509..2f14b9e8 100644 --- a/app/frontend/src/lib/types/fuel-log.ts +++ b/app/frontend/src/lib/types/fuel-log.ts @@ -5,14 +5,15 @@ import { formatCurrency, formatDate, formatDistance, formatVolume } from '$helpe import { Banknote, Calendar1, + CircleGauge, Fuel, - GaugeCircle, Notebook, PaintBucket, SkipBack } from '@lucide/svelte/icons'; import type { ColumnDef } from '@tanstack/table-core'; import { createRawSnippet } from 'svelte'; +import FuelLogContextMenu from '$lib/components/features/fuel/FuelLogContextMenu.svelte'; export interface NewFuelLog { date: string; @@ -32,6 +33,7 @@ export interface FuelLog { mileage?: number; filled?: boolean; missedLast?: boolean; + vehicleId: string; } export const columns: ColumnDef[] = [ @@ -40,17 +42,16 @@ export const columns: ColumnDef[] = [ header: () => renderComponent(LabelWithIcon, { icon: Calendar1, - iconClass: 'h-4 w-4 ', + iconClass: 'h-4 w-4', label: 'Date', - style: 'justify-center' + style: 'justify-start' }), cell: ({ row }) => { const dateCellSnippet = createRawSnippet<[string]>((date) => { return { - render: () => `
${formatDate(date())}
` + render: () => `
${formatDate(date())}
` }; }); - return renderSnippet(dateCellSnippet, row.getValue('date')); } }, @@ -58,72 +59,30 @@ export const columns: ColumnDef[] = [ accessorKey: 'odometer', header: () => renderComponent(LabelWithIcon, { - icon: GaugeCircle, + icon: CircleGauge, iconClass: 'h-4 w-4 ', label: 'Odometer', - style: 'justify-center' + style: 'justify-start' }), cell: ({ row }) => { const odometerCellSnippet = createRawSnippet<[number]>((getOdometer) => { const odometer = getOdometer(); return { - render: () => - `
${formatDistance(odometer)}
` + render: () => `
${formatDistance(odometer)}
` }; }); return renderSnippet(odometerCellSnippet, row.getValue('odometer')); } }, - { - accessorKey: 'fuelAmount', - header: () => - renderComponent(LabelWithIcon, { - icon: PaintBucket, - iconClass: 'h-4 w-4 ', - label: 'Fuel Amount', - style: 'justify-center' - }), - cell: ({ row }) => { - const fuelAmountCellSnippet = createRawSnippet<[number]>((getFuelAmount) => { - const fuelAmount = getFuelAmount(); - return { - render: () => - `
${formatVolume(fuelAmount)}
` - }; - }); - - return renderSnippet(fuelAmountCellSnippet, row.getValue('fuelAmount')); - } - }, - { - accessorKey: 'cost', - header: () => - renderComponent(LabelWithIcon, { - icon: Banknote, - iconClass: 'h-4 w-4 ', - label: 'Cost', - style: 'justify-center' - }), - cell: ({ row }) => { - const costCellSnippet = createRawSnippet<[number]>((getCost) => { - const cost = getCost(); - return { - render: () => `
${formatCurrency(cost)}
` - }; - }); - - return renderSnippet(costCellSnippet, row.getValue('cost')); - } - }, { accessorKey: 'filled', header: () => renderComponent(LabelWithIcon, { icon: Fuel, - iconClass: 'h-4 w-4 ', + iconClass: 'h-4 w-4', label: 'Full Tank', - style: 'justify-start' + style: 'max-w-24 justify-start' }), cell: ({ row }) => renderComponent(Badge, { @@ -140,9 +99,9 @@ export const columns: ColumnDef[] = [ header: () => renderComponent(LabelWithIcon, { icon: SkipBack, - iconClass: 'h-4 w-4 ', + iconClass: 'h-4 w-4', label: 'Missed Last', - style: 'justify-start' + style: 'max-w-24 justify-start' }), cell: ({ row }) => renderComponent(Badge, { @@ -154,14 +113,67 @@ export const columns: ColumnDef[] = [ }) }) }, + { + accessorKey: 'fuelAmount', + header: () => + renderComponent(LabelWithIcon, { + icon: PaintBucket, + iconClass: 'h-4 w-4 ', + label: 'Volume', + style: 'justify-start' + }), + cell: ({ row }) => { + const fuelAmountCellSnippet = createRawSnippet<[number]>((fuelAmount) => { + return { + render: () => + `
${formatVolume(fuelAmount())}
` + }; + }); + return renderSnippet(fuelAmountCellSnippet, row.getValue('fuelAmount')); + } + }, + { + accessorKey: 'cost', + header: () => + renderComponent(LabelWithIcon, { + icon: Banknote, + iconClass: 'h-4 w-4 ', + label: 'Cost', + style: 'justify-start' + }), + cell: ({ row }) => { + const costCellSnippet = createRawSnippet<[number]>((cost) => { + return { + render: () => `
${formatCurrency(cost())}
` + }; + }); + + return renderSnippet(costCellSnippet, row.getValue('cost')); + } + }, { accessorKey: 'notes', header: () => renderComponent(LabelWithIcon, { icon: Notebook, - iconClass: 'h-4 w-4 ', + iconClass: 'h-4 w-4', label: 'Notes', - style: 'justify-center' + style: 'justify-start' + }), + cell: ({ row }) => { + const noteSnippet = createRawSnippet<[string]>((note) => { + return { + render: () => `
${note() || '-'}
` + }; + }); + return renderSnippet(noteSnippet, row.getValue('notes')); + } + }, + { + id: 'actions', + cell: ({ row }) => + renderComponent(FuelLogContextMenu, { + fuelLog: row.original }) } ]; diff --git a/app/frontend/src/lib/types/index.ts b/app/frontend/src/lib/types/index.ts new file mode 100644 index 00000000..58c843e3 --- /dev/null +++ b/app/frontend/src/lib/types/index.ts @@ -0,0 +1,4 @@ +export type DataPoint = { + x: Date | string; + y: number | null; +}; diff --git a/package-lock.json b/package-lock.json index 8727c22b..f4e1ba7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -412,7 +412,6 @@ "app/frontend": { "version": "0.0.1", "dependencies": { - "@lucide/svelte": "^0.544.0", "@types/d3-array": "^3.2.1", "@types/d3-scale": "^4.0.9", "@types/d3-shape": "^3.1.7", @@ -427,6 +426,7 @@ "devDependencies": { "@eslint/js": "^9.34.0", "@internationalized/date": "^3.9.0", + "@lucide/svelte": "^0.515.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -461,6 +461,16 @@ "vite": "^7.0.4" } }, + "app/frontend/node_modules/@lucide/svelte": { + "version": "0.515.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz", + "integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "app/frontend/node_modules/tailwind-variants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", @@ -1980,15 +1990,6 @@ "win32" ] }, - "node_modules/@lucide/svelte": { - "version": "0.544.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.544.0.tgz", - "integrity": "sha512-9f9O6uxng2pLB01sxNySHduJN3HTl5p0HDu4H26VR51vhZfiMzyOMe9Mhof3XAk4l813eTtl+/DYRvGyoRR+yw==", - "license": "ISC", - "peerDependencies": { - "svelte": "^5" - } - }, "node_modules/@neon-rs/load": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", From b0b0f110d197f4117c903891232f8777fc3e53ae Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sun, 14 Sep 2025 11:22:24 +0530 Subject: [PATCH 13/27] Making UI consistent and changed chart library --- app/frontend/package.json | 6 +- .../features/fuel/FuelLogContextMenu.svelte | 19 +- .../features/fuel/FuelLogList.svelte | 227 ++++++++++++------ .../features/fuel/FuelLogTable.svelte | 65 ----- .../features/insurance/InsuranceList.svelte | 32 ++- .../maintenance/MaintenanceContextMenu.svelte | 69 ++++++ .../maintenance/MaintenanceLogList.svelte | 177 +++++++++----- .../pullution/PollutionCertificateList.svelte | 12 +- .../features/vehicle/VehicleCard.svelte | 2 +- .../src/lib/components/layout/AppTable.svelte | 227 ++++++++++++++++++ .../src/lib/components/ui/select/index.ts | 37 +++ .../ui/select/select-content.svelte | 40 +++ .../ui/select/select-group-heading.svelte | 21 ++ .../components/ui/select/select-group.svelte | 7 + .../components/ui/select/select-item.svelte | 38 +++ .../components/ui/select/select-label.svelte | 20 ++ .../select/select-scroll-down-button.svelte | 20 ++ .../ui/select/select-scroll-up-button.svelte | 20 ++ .../ui/select/select-separator.svelte | 18 ++ .../ui/select/select-trigger.svelte | 29 +++ .../src/lib/components/ui/separator/index.ts | 7 + .../components/ui/separator/separator.svelte | 20 ++ app/frontend/src/lib/types/fuel-log.ts | 142 ----------- app/frontend/src/lib/types/index.ts | 10 + package-lock.json | 54 +---- 25 files changed, 911 insertions(+), 408 deletions(-) delete mode 100644 app/frontend/src/lib/components/features/fuel/FuelLogTable.svelte create mode 100644 app/frontend/src/lib/components/features/maintenance/MaintenanceContextMenu.svelte create mode 100644 app/frontend/src/lib/components/layout/AppTable.svelte create mode 100644 app/frontend/src/lib/components/ui/select/index.ts create mode 100644 app/frontend/src/lib/components/ui/select/select-content.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-group-heading.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-group.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-item.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-label.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-separator.svelte create mode 100644 app/frontend/src/lib/components/ui/select/select-trigger.svelte create mode 100644 app/frontend/src/lib/components/ui/separator/index.ts create mode 100644 app/frontend/src/lib/components/ui/separator/separator.svelte diff --git a/app/frontend/package.json b/app/frontend/package.json index ad13daeb..ce1df258 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@eslint/js": "^9.34.0", "@internationalized/date": "^3.9.0", - "@lucide/svelte": "^0.515.0", + "@lucide/svelte": "^0.544.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -28,7 +28,6 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "bits-ui": "^2.9.6", - "chart.js": "^4.5.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", "eslint": "^9.34.0", @@ -60,7 +59,6 @@ "d3-shape": "^3.2.0", "dot-env": "^0.0.1", "mode-watcher": "^1.1.0", - "svelte-loading-spinners": "^0.3.6", - "svelte5-chartjs": "^1.0.0" + "svelte-loading-spinners": "^0.3.6" } } diff --git a/app/frontend/src/lib/components/features/fuel/FuelLogContextMenu.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogContextMenu.svelte index 21a8f137..d283704d 100644 --- a/app/frontend/src/lib/components/features/fuel/FuelLogContextMenu.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogContextMenu.svelte @@ -3,10 +3,12 @@ import Button from '$lib/components/ui/button/button.svelte'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu'; import { getApiUrl } from '$lib/helper/api'; + import { fuelLogModelStore } from '$lib/stores/fuel-log'; import type { FuelLog } from '$lib/types/fuel-log'; import { EllipsisVertical } from '@lucide/svelte'; + import { toast } from 'svelte-sonner'; - let { fuelLog }: { fuelLog: FuelLog } = $props(); + let { fuelLog, onaction }: { fuelLog: FuelLog; onaction: () => void } = $props(); let showDeleteDialog = $state(false); async function deleteFuelLog() { @@ -23,6 +25,13 @@ } } ); + if (response.ok) { + showDeleteDialog = false; + toast.success('Deleted Fuel Log'); + onaction(); + } else { + toast.error('Failed to delete the Fuel Log'); + } } catch (e) { console.error('Failed to connect to the server.', e); } @@ -40,7 +49,13 @@ {/snippet} - Edit + { + fuelLogModelStore.show(fuelLog.vehicleId, fuelLog, true, onaction); + }} + > + Edit + (showDeleteDialog = true)}> Delete diff --git a/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte b/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte index ba40e3e7..663bac41 100644 --- a/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte +++ b/app/frontend/src/lib/components/features/fuel/FuelLogList.svelte @@ -1,19 +1,161 @@ - -
- {#if data.length === 0} -
- -
- {:else} - - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - - - - - {/each} - - - {/if} -
diff --git a/app/frontend/src/lib/components/features/insurance/InsuranceList.svelte b/app/frontend/src/lib/components/features/insurance/InsuranceList.svelte index 7a0b863b..19b2be9c 100644 --- a/app/frontend/src/lib/components/features/insurance/InsuranceList.svelte +++ b/app/frontend/src/lib/components/features/insurance/InsuranceList.svelte @@ -2,7 +2,15 @@ import { onMount } from 'svelte'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; - import { Shield, Calendar, Hash, DollarSign, Trash2, Notebook } from '@lucide/svelte/icons'; + import { + Shield, + Calendar, + Hash, + DollarSign, + Trash2, + Notebook, + Banknote + } from '@lucide/svelte/icons'; import { formatCurrency, formatDate } from '$helper/formatting'; import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$appui/IconButton.svelte'; @@ -104,13 +112,11 @@
No Insurance found for this vehicle.
{:else} {#each insurances as ins (ins.id)} -
+
-
- - {ins.provider} +
+ + {ins.provider}
-
+
Policy Number: {ins.policyNumber}
-
- +
+ Cost: {formatCurrency(ins.cost)}
-
+
Start Date: {formatDate(ins.startDate)}
-
+
End Date: {formatDate(ins.endDate)}
{#if ins.notes} -
+
Notes: {ins.notes} diff --git a/app/frontend/src/lib/components/features/maintenance/MaintenanceContextMenu.svelte b/app/frontend/src/lib/components/features/maintenance/MaintenanceContextMenu.svelte new file mode 100644 index 00000000..f3315a7f --- /dev/null +++ b/app/frontend/src/lib/components/features/maintenance/MaintenanceContextMenu.svelte @@ -0,0 +1,69 @@ + + +
+ + + {#snippet child({ props })} + + {/snippet} + + + { + maintenanceModelStore.show(maintenanceLog.vehicleId, maintenanceLog, true, onaction); + }} + > + Edit + + (showDeleteDialog = true)}> + Delete + + + +
+ + deleteFuelLog()} bind:open={showDeleteDialog} /> diff --git a/app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte index c2c56ab4..dca23dc9 100644 --- a/app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte +++ b/app/frontend/src/lib/components/features/maintenance/MaintenanceLogList.svelte @@ -1,31 +1,137 @@ + +
+ {#if data.length === 0} +
+ +
+ {:else} +
+ table.getColumn('notes')?.setFilterValue(e.currentTarget.value)} + onchange={(e) => { + table.getColumn('notes')?.setFilterValue(e.currentTarget.value); + }} + class="bg-background/60 h-full max-w-sm" + /> + + + {#snippet child({ props })} + + {/snippet} + + + {#each table + .getAllColumns() + .filter((col: any) => typeof col.accessorFn !== 'undefined' && col.getCanHide()) as column (column.id)} + column.toggleVisibility(!!value)} + > + {column.id} + + {/each} + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + + + + {/each} + + +
+
+
+ + {#each table.getPageOptions() as pageNum} + table.setPageIndex(pageNum)} + class="hover:bg-primary hover:text-background cursor-pointer" + > + {pageNum + 1} + + {/each} + +
+
+ Rows per page + + {pageSize} + + {#each [5, 10, 15, 25, 50, 100] as rowsPerPage, index} + + {rowsPerPage} + + {/each} + + +
+
+ {/if} +
diff --git a/app/frontend/src/lib/components/ui/select/index.ts b/app/frontend/src/lib/components/ui/select/index.ts new file mode 100644 index 00000000..9e8d3e90 --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/index.ts @@ -0,0 +1,37 @@ +import { Select as SelectPrimitive } from "bits-ui"; + +import Group from "./select-group.svelte"; +import Label from "./select-label.svelte"; +import Item from "./select-item.svelte"; +import Content from "./select-content.svelte"; +import Trigger from "./select-trigger.svelte"; +import Separator from "./select-separator.svelte"; +import ScrollDownButton from "./select-scroll-down-button.svelte"; +import ScrollUpButton from "./select-scroll-up-button.svelte"; +import GroupHeading from "./select-group-heading.svelte"; + +const Root = SelectPrimitive.Root; + +export { + Root, + Group, + Label, + Item, + Content, + Trigger, + Separator, + ScrollDownButton, + ScrollUpButton, + GroupHeading, + // + Root as Select, + Group as SelectGroup, + Label as SelectLabel, + Item as SelectItem, + Content as SelectContent, + Trigger as SelectTrigger, + Separator as SelectSeparator, + ScrollDownButton as SelectScrollDownButton, + ScrollUpButton as SelectScrollUpButton, + GroupHeading as SelectGroupHeading, +}; diff --git a/app/frontend/src/lib/components/ui/select/select-content.svelte b/app/frontend/src/lib/components/ui/select/select-content.svelte new file mode 100644 index 00000000..dc16d65d --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-content.svelte @@ -0,0 +1,40 @@ + + + + + + + {@render children?.()} + + + + diff --git a/app/frontend/src/lib/components/ui/select/select-group-heading.svelte b/app/frontend/src/lib/components/ui/select/select-group-heading.svelte new file mode 100644 index 00000000..1fab5f00 --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-group-heading.svelte @@ -0,0 +1,21 @@ + + + + {@render children?.()} + diff --git a/app/frontend/src/lib/components/ui/select/select-group.svelte b/app/frontend/src/lib/components/ui/select/select-group.svelte new file mode 100644 index 00000000..5454fdb3 --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/app/frontend/src/lib/components/ui/select/select-item.svelte b/app/frontend/src/lib/components/ui/select/select-item.svelte new file mode 100644 index 00000000..49dbbd7f --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-item.svelte @@ -0,0 +1,38 @@ + + + + {#snippet children({ selected, highlighted })} + + {#if selected} + + {/if} + + {#if childrenProp} + {@render childrenProp({ selected, highlighted })} + {:else} + {label || value} + {/if} + {/snippet} + diff --git a/app/frontend/src/lib/components/ui/select/select-label.svelte b/app/frontend/src/lib/components/ui/select/select-label.svelte new file mode 100644 index 00000000..46960259 --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-label.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/app/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte b/app/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte new file mode 100644 index 00000000..36292058 --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/app/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte b/app/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte new file mode 100644 index 00000000..1aa2300c --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/app/frontend/src/lib/components/ui/select/select-separator.svelte b/app/frontend/src/lib/components/ui/select/select-separator.svelte new file mode 100644 index 00000000..0eac3ebc --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/app/frontend/src/lib/components/ui/select/select-trigger.svelte b/app/frontend/src/lib/components/ui/select/select-trigger.svelte new file mode 100644 index 00000000..d405187d --- /dev/null +++ b/app/frontend/src/lib/components/ui/select/select-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/app/frontend/src/lib/components/ui/separator/index.ts b/app/frontend/src/lib/components/ui/separator/index.ts new file mode 100644 index 00000000..82442d2c --- /dev/null +++ b/app/frontend/src/lib/components/ui/separator/index.ts @@ -0,0 +1,7 @@ +import Root from "./separator.svelte"; + +export { + Root, + // + Root as Separator, +}; diff --git a/app/frontend/src/lib/components/ui/separator/separator.svelte b/app/frontend/src/lib/components/ui/separator/separator.svelte new file mode 100644 index 00000000..09d88f4a --- /dev/null +++ b/app/frontend/src/lib/components/ui/separator/separator.svelte @@ -0,0 +1,20 @@ + + + diff --git a/app/frontend/src/lib/types/fuel-log.ts b/app/frontend/src/lib/types/fuel-log.ts index 2f14b9e8..8b30a916 100644 --- a/app/frontend/src/lib/types/fuel-log.ts +++ b/app/frontend/src/lib/types/fuel-log.ts @@ -35,145 +35,3 @@ export interface FuelLog { missedLast?: boolean; vehicleId: string; } - -export const columns: ColumnDef[] = [ - { - accessorKey: 'date', - header: () => - renderComponent(LabelWithIcon, { - icon: Calendar1, - iconClass: 'h-4 w-4', - label: 'Date', - style: 'justify-start' - }), - cell: ({ row }) => { - const dateCellSnippet = createRawSnippet<[string]>((date) => { - return { - render: () => `
${formatDate(date())}
` - }; - }); - return renderSnippet(dateCellSnippet, row.getValue('date')); - } - }, - { - accessorKey: 'odometer', - header: () => - renderComponent(LabelWithIcon, { - icon: CircleGauge, - iconClass: 'h-4 w-4 ', - label: 'Odometer', - style: 'justify-start' - }), - cell: ({ row }) => { - const odometerCellSnippet = createRawSnippet<[number]>((getOdometer) => { - const odometer = getOdometer(); - return { - render: () => `
${formatDistance(odometer)}
` - }; - }); - - return renderSnippet(odometerCellSnippet, row.getValue('odometer')); - } - }, - { - accessorKey: 'filled', - header: () => - renderComponent(LabelWithIcon, { - icon: Fuel, - iconClass: 'h-4 w-4', - label: 'Full Tank', - style: 'max-w-24 justify-start' - }), - cell: ({ row }) => - renderComponent(Badge, { - variant: 'outline', - children: createRawSnippet(() => { - return { - render: () => `${row.getValue('filled') ? 'Yes' : 'No'}` - }; - }) - }) - }, - { - accessorKey: 'missedLast', - header: () => - renderComponent(LabelWithIcon, { - icon: SkipBack, - iconClass: 'h-4 w-4', - label: 'Missed Last', - style: 'max-w-24 justify-start' - }), - cell: ({ row }) => - renderComponent(Badge, { - variant: 'outline', - children: createRawSnippet(() => { - return { - render: () => `${row.getValue('missedLast') ? 'Yes' : 'No'}` - }; - }) - }) - }, - { - accessorKey: 'fuelAmount', - header: () => - renderComponent(LabelWithIcon, { - icon: PaintBucket, - iconClass: 'h-4 w-4 ', - label: 'Volume', - style: 'justify-start' - }), - cell: ({ row }) => { - const fuelAmountCellSnippet = createRawSnippet<[number]>((fuelAmount) => { - return { - render: () => - `
${formatVolume(fuelAmount())}
` - }; - }); - return renderSnippet(fuelAmountCellSnippet, row.getValue('fuelAmount')); - } - }, - { - accessorKey: 'cost', - header: () => - renderComponent(LabelWithIcon, { - icon: Banknote, - iconClass: 'h-4 w-4 ', - label: 'Cost', - style: 'justify-start' - }), - cell: ({ row }) => { - const costCellSnippet = createRawSnippet<[number]>((cost) => { - return { - render: () => `
${formatCurrency(cost())}
` - }; - }); - - return renderSnippet(costCellSnippet, row.getValue('cost')); - } - }, - { - accessorKey: 'notes', - header: () => - renderComponent(LabelWithIcon, { - icon: Notebook, - iconClass: 'h-4 w-4', - label: 'Notes', - style: 'justify-start' - }), - cell: ({ row }) => { - const noteSnippet = createRawSnippet<[string]>((note) => { - return { - render: () => `
${note() || '-'}
` - }; - }); - return renderSnippet(noteSnippet, row.getValue('notes')); - } - }, - { - id: 'actions', - cell: ({ row }) => - renderComponent(FuelLogContextMenu, { - fuelLog: row.original - }) - } -]; diff --git a/app/frontend/src/lib/types/index.ts b/app/frontend/src/lib/types/index.ts index 58c843e3..635eb005 100644 --- a/app/frontend/src/lib/types/index.ts +++ b/app/frontend/src/lib/types/index.ts @@ -2,3 +2,13 @@ export type DataPoint = { x: Date | string; y: number | null; }; + +export type MaintenanceLog = { + id: string; + date: string; + odometer: number; + serviceCenter: string; + cost: number; + notes?: string; + vehicleId: string; +}; diff --git a/package-lock.json b/package-lock.json index f4e1ba7c..608cab8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -420,13 +420,12 @@ "d3-shape": "^3.2.0", "dot-env": "^0.0.1", "mode-watcher": "^1.1.0", - "svelte-loading-spinners": "^0.3.6", - "svelte5-chartjs": "^1.0.0" + "svelte-loading-spinners": "^0.3.6" }, "devDependencies": { "@eslint/js": "^9.34.0", "@internationalized/date": "^3.9.0", - "@lucide/svelte": "^0.515.0", + "@lucide/svelte": "^0.544.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -437,7 +436,6 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "bits-ui": "^2.9.6", - "chart.js": "^4.5.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", "eslint": "^9.34.0", @@ -461,16 +459,6 @@ "vite": "^7.0.4" } }, - "app/frontend/node_modules/@lucide/svelte": { - "version": "0.515.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz", - "integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "svelte": "^5" - } - }, "app/frontend/node_modules/tailwind-variants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", @@ -1765,12 +1753,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, "node_modules/@layerstack/svelte-actions": { "version": "1.0.1-next.12", "resolved": "https://registry.npmjs.org/@layerstack/svelte-actions/-/svelte-actions-1.0.1-next.12.tgz", @@ -1990,6 +1972,16 @@ "win32" ] }, + "node_modules/@lucide/svelte": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.544.0.tgz", + "integrity": "sha512-9f9O6uxng2pLB01sxNySHduJN3HTl5p0HDu4H26VR51vhZfiMzyOMe9Mhof3XAk4l813eTtl+/DYRvGyoRR+yw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "node_modules/@neon-rs/load": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", @@ -3931,18 +3923,6 @@ "node": ">=8" } }, - "node_modules/chart.js": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", - "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -9095,16 +9075,6 @@ "@types/estree": "^1.0.6" } }, - "node_modules/svelte5-chartjs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svelte5-chartjs/-/svelte5-chartjs-1.0.0.tgz", - "integrity": "sha512-SMk+D5ECbsoeFurKE/Nr9sqD4H3WqZkQ4eLxwchDSh8gu7YSGN3ASXYCz9kzFhrH2QGQYpebHwLIMHg7FOI/7A==", - "license": "MIT", - "peerDependencies": { - "chart.js": "^3.5.0 || ^4.0.0", - "svelte": "^5.0.0" - } - }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", From 484c147efe48074052be86ac6d528d01827ae7c4 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Mon, 15 Sep 2025 23:14:49 +0530 Subject: [PATCH 14/27] Using App Sheet instead of modals --- .../controllers/MaintenanceLogController.ts | 34 +- .../src/controllers/VehicleController.ts | 9 +- app/backend/src/routes/vehicleRoutes.ts | 2 +- app/backend/src/services/fuelLogService.ts | 1 + .../src/services/maintenanceLogService.ts | 1 + app/backend/src/services/vehicleService.ts | 2 +- app/frontend/package.json | 5 +- .../components/features/fuel/FuelForm.svelte | 163 ++++ .../features/fuel/FuelLogContextMenu.svelte | 32 +- .../features/fuel/FuelLogForm.svelte | 186 ----- .../features/fuel/FuelLogList.svelte | 2 +- .../features/fuel/FuelLogModal.svelte | 41 - .../features/insurance/InsuranceList.svelte | 11 +- .../maintenance/MaintenanceContextMenu.svelte | 2 +- .../maintenance/MaintenanceForm.svelte | 136 ++++ .../maintenance/MaintenanceLogForm.svelte | 156 ---- .../maintenance/MaintenanceLogList.svelte | 5 +- .../maintenance/MaintenanceLogModal.svelte | 41 - .../features/overview/AreaChart.svelte | 2 +- .../features/overview/CostChart.svelte | 1 - .../features/overview/MileageChart.svelte | 1 - .../features/vehicle/VehicleCard.svelte | 34 +- .../features/vehicle/VehicleForm.svelte | 304 +++----- .../features/vehicle/VehicleList.svelte | 6 +- .../features/vehicle/VehicleModal.svelte | 27 - .../src/lib/components/layout/AppSheet.svelte | 31 + .../src/lib/components/layout/AppTable.svelte | 7 +- .../vehicle => ui/app}/LicensePlate.svelte | 0 .../ui/calendar/calendar-caption.svelte | 76 ++ .../ui/calendar/calendar-cell.svelte | 19 + .../ui/calendar/calendar-day.svelte | 35 + .../ui/calendar/calendar-grid-body.svelte | 12 + .../ui/calendar/calendar-grid-head.svelte | 12 + .../ui/calendar/calendar-grid-row.svelte | 12 + .../ui/calendar/calendar-grid.svelte | 16 + .../ui/calendar/calendar-head-cell.svelte | 19 + .../ui/calendar/calendar-header.svelte | 19 + .../ui/calendar/calendar-heading.svelte | 16 + .../ui/calendar/calendar-month-select.svelte | 44 ++ .../ui/calendar/calendar-month.svelte | 15 + .../ui/calendar/calendar-months.svelte | 19 + .../ui/calendar/calendar-nav.svelte | 19 + .../ui/calendar/calendar-next-button.svelte | 31 + .../ui/calendar/calendar-prev-button.svelte | 31 + .../ui/calendar/calendar-year-select.svelte | 43 ++ .../components/ui/calendar/calendar.svelte | 115 +++ .../src/lib/components/ui/calendar/index.ts | 40 + .../components/ui/checkbox/checkbox.svelte | 35 + .../src/lib/components/ui/checkbox/index.ts | 6 + .../lib/components/ui/form/form-button.svelte | 7 + .../ui/form/form-description.svelte | 17 + .../ui/form/form-element-field.svelte | 24 + .../ui/form/form-field-errors.svelte | 30 + .../lib/components/ui/form/form-field.svelte | 29 + .../components/ui/form/form-fieldset.svelte | 15 + .../lib/components/ui/form/form-label.svelte | 39 + .../lib/components/ui/form/form-legend.svelte | 16 + .../src/lib/components/ui/form/index.ts | 33 + .../src/lib/components/ui/input/input.svelte | 169 ++++- .../src/lib/components/ui/popover/index.ts | 17 + .../ui/popover/popover-content.svelte | 29 + .../ui/popover/popover-trigger.svelte | 17 + .../src/lib/components/ui/sheet/index.ts | 36 + .../components/ui/sheet/sheet-close.svelte | 7 + .../components/ui/sheet/sheet-content.svelte | 58 ++ .../ui/sheet/sheet-description.svelte | 17 + .../components/ui/sheet/sheet-footer.svelte | 20 + .../components/ui/sheet/sheet-header.svelte | 20 + .../components/ui/sheet/sheet-overlay.svelte | 20 + .../components/ui/sheet/sheet-title.svelte | 17 + .../components/ui/sheet/sheet-trigger.svelte | 7 + .../lib/components/ui/tabs/tabs-list.svelte | 12 +- .../src/lib/components/ui/textarea/index.ts | 7 + .../components/ui/textarea/textarea.svelte | 22 + app/frontend/src/lib/helper/formatting.ts | 7 +- app/frontend/src/lib/services/fuel.service.ts | 57 ++ .../src/lib/services/maintenence.service.ts | 35 + .../src/lib/services/vehicle.service.ts | 55 +- app/frontend/src/lib/types/fuel-log.ts | 37 - app/frontend/src/lib/types/fuel.ts | 31 + app/frontend/src/lib/types/index.ts | 12 +- app/frontend/src/lib/types/maintenance.ts | 29 + app/frontend/src/lib/types/vehicle.ts | 57 +- .../src/routes/dashboard/+layout.svelte | 7 + .../src/routes/dashboard/+page.svelte | 18 +- app/frontend/src/styles/app.css | 78 +- package-lock.json | 707 +++++++++++++++++- 87 files changed, 2806 insertions(+), 885 deletions(-) create mode 100644 app/frontend/src/lib/components/features/fuel/FuelForm.svelte delete mode 100644 app/frontend/src/lib/components/features/fuel/FuelLogForm.svelte delete mode 100644 app/frontend/src/lib/components/features/fuel/FuelLogModal.svelte create mode 100644 app/frontend/src/lib/components/features/maintenance/MaintenanceForm.svelte delete mode 100644 app/frontend/src/lib/components/features/maintenance/MaintenanceLogForm.svelte delete mode 100644 app/frontend/src/lib/components/features/maintenance/MaintenanceLogModal.svelte delete mode 100644 app/frontend/src/lib/components/features/vehicle/VehicleModal.svelte create mode 100644 app/frontend/src/lib/components/layout/AppSheet.svelte rename app/frontend/src/lib/components/{features/vehicle => ui/app}/LicensePlate.svelte (100%) create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-caption.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-cell.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-day.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-grid-body.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-grid-head.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-grid-row.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-grid.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-head-cell.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-header.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-heading.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-month-select.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-month.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-months.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-nav.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-next-button.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-prev-button.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar-year-select.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/calendar.svelte create mode 100644 app/frontend/src/lib/components/ui/calendar/index.ts create mode 100644 app/frontend/src/lib/components/ui/checkbox/checkbox.svelte create mode 100644 app/frontend/src/lib/components/ui/checkbox/index.ts create mode 100644 app/frontend/src/lib/components/ui/form/form-button.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-description.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-element-field.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-field-errors.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-field.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-fieldset.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-label.svelte create mode 100644 app/frontend/src/lib/components/ui/form/form-legend.svelte create mode 100644 app/frontend/src/lib/components/ui/form/index.ts create mode 100644 app/frontend/src/lib/components/ui/popover/index.ts create mode 100644 app/frontend/src/lib/components/ui/popover/popover-content.svelte create mode 100644 app/frontend/src/lib/components/ui/popover/popover-trigger.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/index.ts create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-close.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-content.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-description.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-footer.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-header.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-overlay.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-title.svelte create mode 100644 app/frontend/src/lib/components/ui/sheet/sheet-trigger.svelte create mode 100644 app/frontend/src/lib/components/ui/textarea/index.ts create mode 100644 app/frontend/src/lib/components/ui/textarea/textarea.svelte create mode 100644 app/frontend/src/lib/services/fuel.service.ts create mode 100644 app/frontend/src/lib/services/maintenence.service.ts delete mode 100644 app/frontend/src/lib/types/fuel-log.ts create mode 100644 app/frontend/src/lib/types/fuel.ts create mode 100644 app/frontend/src/lib/types/maintenance.ts diff --git a/app/backend/src/controllers/MaintenanceLogController.ts b/app/backend/src/controllers/MaintenanceLogController.ts index 03564d3c..4801c5f2 100644 --- a/app/backend/src/controllers/MaintenanceLogController.ts +++ b/app/backend/src/controllers/MaintenanceLogController.ts @@ -10,18 +10,24 @@ export const addMaintenanceLog = async (req: Request, res: Response) => { if (!vehicleId) { throw new MaintenanceLogError( "Vehicle ID is required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } - if (!date || !odometer || !serviceCenter || cost === undefined || cost === null) { + if ( + !date || + !odometer || + !serviceCenter || + cost === undefined || + cost === null + ) { throw new MaintenanceLogError( "Date, Odometer, ServiceCenter, and Cost are required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } const result = await maintenanceLogService.addMaintenanceLog( vehicleId, - req.body, + req.body ); res.status(201).json(result); }; @@ -31,7 +37,7 @@ export const getMaintenanceLogs = async (req: Request, res: Response) => { if (!vehicleId) { throw new MaintenanceLogError( "Vehicle ID is required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } const maintenanceLogs = @@ -44,7 +50,7 @@ export const getMaintenanceLogById = async (req: Request, res: Response) => { if (!id) { throw new MaintenanceLogError( "Maintenance Log ID is required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } @@ -54,18 +60,24 @@ export const getMaintenanceLogById = async (req: Request, res: Response) => { export const updateMaintenanceLog = async (req: Request, res: Response) => { const { id } = req.params; - const { date, odometer, service, cost } = req.body; + const { date, odometer, serviceCenter, cost } = req.body; if (!id) { throw new MaintenanceLogError( "Maintenance Log ID is required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } - if (!date || !odometer || !service || cost === undefined || cost === null ) { + if ( + !date || + !odometer || + !serviceCenter || + cost === undefined || + cost === null + ) { throw new MaintenanceLogError( "Date, Odometer, Service, and Cost are required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } const result = await maintenanceLogService.updateMaintenanceLog(id, req.body); @@ -77,7 +89,7 @@ export const deleteMaintenanceLog = async (req: Request, res: Response) => { if (!id) { throw new MaintenanceLogError( "Maintenance Log ID is required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } diff --git a/app/backend/src/controllers/VehicleController.ts b/app/backend/src/controllers/VehicleController.ts index 69c0eab8..c3d64b9b 100644 --- a/app/backend/src/controllers/VehicleController.ts +++ b/app/backend/src/controllers/VehicleController.ts @@ -8,7 +8,7 @@ export const addVehicle = async (req: Request, res: Response) => { if (!make || !model || !year || !licensePlate) { new VehicleError( "Make, Model, Year, and License Plate are required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } const result = await vehicleService.addVehicle(req.body); @@ -30,13 +30,12 @@ export const getVehicleById = async (req: Request, res: Response) => { }; export const updateVehicle = async (req: Request, res: Response) => { - const { id } = req.params; - const { make, model, year, licensePlate } = req.body; + const { id, make, model, year, licensePlate } = req.body; - if (!make || !model || !year || !licensePlate) { + if (!id || !make || !model || !year || !licensePlate) { throw new VehicleError( "Make, Model, Year, and License Plate are required.", - Status.BAD_REQUEST, + Status.BAD_REQUEST ); } if (!id) { diff --git a/app/backend/src/routes/vehicleRoutes.ts b/app/backend/src/routes/vehicleRoutes.ts index 047cb098..358df974 100644 --- a/app/backend/src/routes/vehicleRoutes.ts +++ b/app/backend/src/routes/vehicleRoutes.ts @@ -19,7 +19,7 @@ const router = Router(); router.post("/", authenticatePin, asyncHandler(addVehicle)); router.get("/", authenticatePin, asyncHandler(getAllVehicles)); router.get("/:id", authenticatePin, asyncHandler(getVehicleById)); -router.put("/:id", authenticatePin, asyncHandler(updateVehicle)); +router.put("/", authenticatePin, asyncHandler(updateVehicle)); router.delete("/:id", authenticatePin, asyncHandler(deleteVehicle)); router.use("/:vehicleId/fuel-logs", fuelLogRoutes); diff --git a/app/backend/src/services/fuelLogService.ts b/app/backend/src/services/fuelLogService.ts index da04348e..bfc17dce 100644 --- a/app/backend/src/services/fuelLogService.ts +++ b/app/backend/src/services/fuelLogService.ts @@ -19,6 +19,7 @@ export const addFuelLog = async (vehicleId: string, fuelLogData: any) => { .insert(schema.fuelLogTable) .values({ ...fuelLogData, + id: undefined, vehicleId: vehicleId, }) .returning(); diff --git a/app/backend/src/services/maintenanceLogService.ts b/app/backend/src/services/maintenanceLogService.ts index 59794ada..e9dd5d48 100644 --- a/app/backend/src/services/maintenanceLogService.ts +++ b/app/backend/src/services/maintenanceLogService.ts @@ -24,6 +24,7 @@ export const addMaintenanceLog = async ( .values({ ...maintenanceLogData, vehicleId: vehicleId, + id: undefined, }) .returning(); return { diff --git a/app/backend/src/services/vehicleService.ts b/app/backend/src/services/vehicleService.ts index 6c70715d..e3448ccc 100644 --- a/app/backend/src/services/vehicleService.ts +++ b/app/backend/src/services/vehicleService.ts @@ -7,7 +7,7 @@ import { eq } from "drizzle-orm"; export const addVehicle = async (vehicleData: any) => { const vehicle = await db .insert(schema.vehicleTable) - .values(vehicleData) + .values({ ...vehicleData, id: undefined }) .returning(); return { id: vehicle[0]?.id, message: "Vehicle added successfully." }; }; diff --git a/app/frontend/package.json b/app/frontend/package.json index ce1df258..464d6f2a 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@eslint/js": "^9.34.0", "@internationalized/date": "^3.9.0", - "@lucide/svelte": "^0.544.0", + "@lucide/svelte": "^0.515.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -34,15 +34,18 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.11.0", "eslint-plugin-unused-imports": "^4.2.0", + "formsnap": "^2.0.1", "globals": "^14.0.0", "layerchart": "^2.0.0-next.27", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.11", "svelte": "^5.0.0", + "svelte-awesome-color-picker": "^4.0.2", "svelte-check": "^4.0.0", "svelte-eslint-parser": "^1.3.1", "svelte-sonner": "^1.0.5", + "sveltekit-superforms": "^2.27.1", "tailwind-merge": "^3.3.1", "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", diff --git a/app/frontend/src/lib/components/features/fuel/FuelForm.svelte b/app/frontend/src/lib/components/features/fuel/FuelForm.svelte new file mode 100644 index 00000000..f42d16ff --- /dev/null +++ b/app/frontend/src/lib/components/features/fuel/FuelForm.svelte @@ -0,0 +1,163 @@ + + + fuelLogModelStore.hide()} title="Add Fuel Log"> +
e.preventDefault()}> +
+ + + + {#snippet children({ props })} + Date + + {/snippet} + + + + + + {#snippet children({ props })} + Odometer + + {/snippet} + + + + + +
+ + + {#snippet children({ props })} + Volume + + {/snippet} + + + + + + + {#snippet children({ props })} + Cost + + {/snippet} + + + + +
+ + + {#snippet children({ props })} +
+ + Full Tank +
+ {/snippet} +
+ +
+ + + {#snippet children({ props })} +
+ + Missed Last +
+ {/snippet} +
+ +
+ + + + {#snippet children({ props })} + Notes + diff --git a/app/frontend/src/lib/helper/formatting.ts b/app/frontend/src/lib/helper/formatting.ts index 268f08e3..c01f70c4 100644 --- a/app/frontend/src/lib/helper/formatting.ts +++ b/app/frontend/src/lib/helper/formatting.ts @@ -1,5 +1,5 @@ import { config } from '$stores/config'; -import { format } from 'date-fns'; +import { format, parse } from 'date-fns'; export interface ConfigStore { dateFormat: string; @@ -31,6 +31,10 @@ const formatDate = (date: Date | string): string => { return format(date, configs.dateFormat); }; +const parseDate = (date: string) => { + return parse(date, configs.dateFormat, new Date()); +}; + const getCurrencySymbol = (): string => { return ( new Intl.NumberFormat('en-US', { @@ -108,6 +112,7 @@ const formatMileage = (mileage: number): string => { export { formatDate, + parseDate, getCurrencySymbol, formatCurrency, getDistanceUnit, diff --git a/app/frontend/src/lib/services/fuel.service.ts b/app/frontend/src/lib/services/fuel.service.ts new file mode 100644 index 00000000..d748b1fe --- /dev/null +++ b/app/frontend/src/lib/services/fuel.service.ts @@ -0,0 +1,57 @@ +import { getApiUrl } from '$lib/helper/api'; +import type { FuelLog } from '$lib/types/fuel'; +import type { Response } from '$lib/types'; + +export const saveFuelLog = async (fuelLog: FuelLog): Promise> => { + const res: Response = { status: 'OK' }; + try { + const response = await fetch( + getApiUrl(`/api/vehicles/${fuelLog.vehicleId}/fuel-logs/${fuelLog.id || ''}`), + { + method: `${fuelLog.id ? 'PUT' : 'POST'}`, + headers: { + 'Content-Type': 'application/json', + 'X-User-PIN': localStorage.getItem('userPin') || '' + }, + body: JSON.stringify(fuelLog) + } + ); + if (response.ok) { + res.data = await response.json(); + } else { + res.status = 'ERROR'; + const data = await response.json(); + res.error = (data.message as string) || 'Failed to save fuel log.'; + } + } catch (e) { + res.status = 'ERROR'; + res.error = 'Error while saving fuel log : ' + e; + } + return res; +}; + +export const deleteFuelLog = async (fuelLog: FuelLog): Promise> => { + const res: Response = { status: 'OK' }; + try { + const response = await fetch( + getApiUrl(`/api/vehicles/${fuelLog.vehicleId}/fuel-logs/${fuelLog.id}`), + { + method: 'DELETE', + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + } + ); + if (response.ok) { + res.data = await response.json(); + } else { + res.status = 'ERROR'; + const data = await response.json(); + res.error = (data.message as string) || 'Failed to delete vehicle.'; + } + } catch (e) { + res.status = 'ERROR'; + res.error = 'Error while deleting fuel log : ' + e; + } + return res; +}; diff --git a/app/frontend/src/lib/services/maintenence.service.ts b/app/frontend/src/lib/services/maintenence.service.ts new file mode 100644 index 00000000..81750d79 --- /dev/null +++ b/app/frontend/src/lib/services/maintenence.service.ts @@ -0,0 +1,35 @@ +import { getApiUrl } from '$lib/helper/api'; +import type { Response } from '$lib/types'; +import type { MaintenanceLog } from '$lib/types/maintenance'; + +export const saveMaintenanceLog = async ( + maintenanceLog: MaintenanceLog +): Promise> => { + const res: Response = { status: 'OK' }; + try { + const response = await fetch( + getApiUrl( + `/api/vehicles/${maintenanceLog.vehicleId}/maintenance-logs/${maintenanceLog.id || ''}` + ), + { + method: `${maintenanceLog.id ? 'PUT' : 'POST'}`, + headers: { + 'Content-Type': 'application/json', + 'X-User-PIN': localStorage.getItem('userPin') || '' + }, + body: JSON.stringify(maintenanceLog) + } + ); + if (response.ok) { + res.data = await response.json(); + } else { + res.status = 'ERROR'; + const data = await response.json(); + res.error = (data.message as string) || 'Failed to save maintenance log.'; + } + } catch (e) { + res.status = 'ERROR'; + res.error = 'Error while saving maintenance log : ' + e; + } + return res; +}; diff --git a/app/frontend/src/lib/services/vehicle.service.ts b/app/frontend/src/lib/services/vehicle.service.ts index a25a72d4..ec14af92 100644 --- a/app/frontend/src/lib/services/vehicle.service.ts +++ b/app/frontend/src/lib/services/vehicle.service.ts @@ -1,5 +1,6 @@ import { getApiUrl } from '$lib/helper/api'; -import type { DataPoint } from '$lib/types'; +import type { DataPoint, Response } from '$lib/types'; +import type { Vehicle } from '$lib/types/vehicle'; export const fetchMileageData = async (vehicleId: string): Promise => { let mileageData: DataPoint[] = []; @@ -62,3 +63,55 @@ export const fetchCostData = async (vehicleId: string): Promise => } return costData; }; + +export const saveVehicle = async ( + vehicle: Vehicle, + method: 'PUT' | 'POST' +): Promise> => { + const res: Response = { status: 'OK' }; + try { + const response = await fetch(getApiUrl(`/api/vehicles/`), { + method, + headers: { + 'Content-Type': 'application/json', + 'X-User-PIN': localStorage.getItem('userPin') || '' + }, + body: JSON.stringify(vehicle) + }); + + if (response.ok) { + res.data = await response.json(); + } else { + res.status = 'ERROR'; + const data = await response.json(); + res.error = (data.message as string) || 'Failed to delete vehicle.'; + } + } catch (e) { + res.status = 'ERROR'; + res.error = 'Error while saving vehicle : ' + e; + } + return res; +}; + +export const deleteVehicle = async (vehicleId: string): Promise> => { + const res: Response = { status: 'OK' }; + try { + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}`), { + method: 'DELETE', + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' + } + }); + if (response.ok) { + res.data = vehicleId; + } else { + res.status = 'ERROR'; + const data = await response.json(); + res.error = (data.message as string) || 'Failed to delete vehicle.'; + } + } catch (e) { + res.status = 'ERROR'; + res.error = 'Error while saving vehicle : ' + e; + } + return res; +}; diff --git a/app/frontend/src/lib/types/fuel-log.ts b/app/frontend/src/lib/types/fuel-log.ts deleted file mode 100644 index 8b30a916..00000000 --- a/app/frontend/src/lib/types/fuel-log.ts +++ /dev/null @@ -1,37 +0,0 @@ -import LabelWithIcon from '$appui/LabelWithIcon.svelte'; -import { Badge } from '$lib/components/ui/badge'; -import { renderComponent, renderSnippet } from '$lib/components/ui/data-table'; -import { formatCurrency, formatDate, formatDistance, formatVolume } from '$helper/formatting'; -import { - Banknote, - Calendar1, - CircleGauge, - Fuel, - Notebook, - PaintBucket, - SkipBack -} from '@lucide/svelte/icons'; -import type { ColumnDef } from '@tanstack/table-core'; -import { createRawSnippet } from 'svelte'; -import FuelLogContextMenu from '$lib/components/features/fuel/FuelLogContextMenu.svelte'; - -export interface NewFuelLog { - date: string; - odometer: number | null; - fuelAmount: number | null; - cost: number | null; - notes?: string; -} - -export interface FuelLog { - id: string; - date: string; - odometer: number; - fuelAmount: number; - cost: number; - notes?: string; - mileage?: number; - filled?: boolean; - missedLast?: boolean; - vehicleId: string; -} diff --git a/app/frontend/src/lib/types/fuel.ts b/app/frontend/src/lib/types/fuel.ts new file mode 100644 index 00000000..bef5e4ae --- /dev/null +++ b/app/frontend/src/lib/types/fuel.ts @@ -0,0 +1,31 @@ +import { parseDate } from '$lib/helper/formatting'; +import { z } from 'zod/v4'; + +export interface FuelLog { + id: string | null; + vehicleId: string; + date: Date; + odometer: number; + filled: boolean; + missedLast: boolean; + fuelAmount: number; + cost: number; + notes: string | null; + mileage?: number; +} + +export const fuelSchema = z.object({ + id: z.string().nullable(), + vehicleId: z.uuid(), + date: z.string().check((val) => { + parseDate(val.value); + }), + odometer: z.number().positive(), + filled: z.boolean().default(true), + missedLast: z.boolean(), + fuelAmount: z.float32().positive(), + cost: z.float32().positive(), + notes: z.string().nullable() +}); + +export type FuelSchema = typeof fuelSchema; diff --git a/app/frontend/src/lib/types/index.ts b/app/frontend/src/lib/types/index.ts index 635eb005..17e7194e 100644 --- a/app/frontend/src/lib/types/index.ts +++ b/app/frontend/src/lib/types/index.ts @@ -3,12 +3,8 @@ export type DataPoint = { y: number | null; }; -export type MaintenanceLog = { - id: string; - date: string; - odometer: number; - serviceCenter: string; - cost: number; - notes?: string; - vehicleId: string; +export type Response = { + status: 'OK' | 'ERROR'; + data?: DataType; + error?: string; }; diff --git a/app/frontend/src/lib/types/maintenance.ts b/app/frontend/src/lib/types/maintenance.ts new file mode 100644 index 00000000..1db3e393 --- /dev/null +++ b/app/frontend/src/lib/types/maintenance.ts @@ -0,0 +1,29 @@ +import { parseDate } from '$lib/helper/formatting'; +import { z } from 'zod/v4'; + +export interface MaintenanceLog { + id: string | null; + vehicleId: string; + date: Date; + odometer: number; + serviceCenter: string; + cost: number; + notes: string | null; +} + +export const maintenenceSchema = z.object({ + id: z.string().nullable(), + vehicleId: z.uuid(), + date: z.string().check((val) => { + parseDate(val.value); + }), + odometer: z.number().positive(), + serviceCenter: z + .string() + .min(2, 'It must be more than 1 character.') + .max(50, 'It must be less than 50 characters.'), + cost: z.float32().positive(), + notes: z.string().nullable() +}); + +export type MaintenenceSchema = typeof maintenenceSchema; diff --git a/app/frontend/src/lib/types/vehicle.ts b/app/frontend/src/lib/types/vehicle.ts index 59aca8bb..41628b78 100644 --- a/app/frontend/src/lib/types/vehicle.ts +++ b/app/frontend/src/lib/types/vehicle.ts @@ -1,16 +1,47 @@ -export interface NewVehicle { - make: string | null; - model: string | null; - year: number | null; - licensePlate: string | null; - vin: string | null; - color: string | null; +import { z } from 'zod/v4'; + +export interface Vehicle { + id: string | null; + make: string; + model: string; + year: number; + licensePlate: string; + vin: string; + color: string; odometer: number | null; + insuranceStatus?: string; + puccStatus?: string; } -export interface Vehicle extends NewVehicle { - vehicleType: string; - id: string; - insuranceStatus: string | null; - puccStatus?: string | null; -} +export const vehicleSchema = z.object({ + id: z.string().nullable(), + make: z + .string() + .min(2, 'It must be more than 1 character.') + .max(50, 'It must be less than 50 characters.'), + + model: z + .string() + .min(2, 'It must be more than 1 character.') + .max(50, 'It must be less than 50 characters.'), + year: z + .number() + .min(1970, 'It must be after 1970') + .max(2100, 'It must be before current year.') + .default(2025), + licensePlate: z + .string() + .min(2, 'It must be more than 1 character.') + .max(50, 'It must be less than 50 characters.'), + vin: z + .string() + .min(2, 'It must be more than 1 character.') + .max(50, 'It must be less than 50 characters.'), + color: z + .string() + .regex(/^(#[0-9a-fA-F]{3})|(#[0-9a-fA-F]{6})$/, 'Only hex color codes allowed.') + .default('#A1A1A1'), + odometer: z.number().nonnegative().nullable() +}); + +export type VehicleSchema = typeof vehicleSchema; diff --git a/app/frontend/src/routes/dashboard/+layout.svelte b/app/frontend/src/routes/dashboard/+layout.svelte index edc32d15..786558c1 100644 --- a/app/frontend/src/routes/dashboard/+layout.svelte +++ b/app/frontend/src/routes/dashboard/+layout.svelte @@ -1,4 +1,7 @@ -
+

Your Garage

{/if} - - - - - - -
diff --git a/app/frontend/src/styles/app.css b/app/frontend/src/styles/app.css index 33a75a96..525f2bca 100644 --- a/app/frontend/src/styles/app.css +++ b/app/frontend/src/styles/app.css @@ -7,70 +7,76 @@ :root { --radius: 0.65rem; --background: oklch(1 0 0); - --foreground: oklch(0.141 0.005 285.823); + --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0.141 0.005 285.823); + --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.623 0.214 259.815); - --primary-foreground: oklch(0.97 0.014 254.604); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.623 0.214 259.815); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.623 0.214 259.815); - --sidebar-primary-foreground: oklch(0.97 0.014 254.604); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.623 0.214 259.815); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); } .dark { - --background: oklch(0.141 0.005 285.823); + --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); - --card: oklch(0.21 0.006 285.885); + --card: oklch(0.205 0 0); --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.21 0.006 285.885); + --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.546 0.245 262.881); - --primary-foreground: oklch(0.379 0.146 265.522); - --secondary: oklch(0.274 0.006 286.033); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); - --ring: oklch(0.488 0.243 264.376); + --ring: oklch(0.556 0 0); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.006 285.885); + --sidebar: oklch(0.205 0 0); --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.546 0.245 262.881); - --sidebar-primary-foreground: oklch(0.379 0.146 265.522); - --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.488 0.243 264.376); + --sidebar-ring: oklch(0.556 0 0); + --cp-bg-color: var(--color-accent); + --cp-border-color: var(--color-ring); + --cp-text-color: var(--color-foreground); + --cp-input-color: var(--color-background); + --cp-button-hover-color: var(--color-ring); } diff --git a/package-lock.json b/package-lock.json index 608cab8a..71b34b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -425,7 +425,7 @@ "devDependencies": { "@eslint/js": "^9.34.0", "@internationalized/date": "^3.9.0", - "@lucide/svelte": "^0.544.0", + "@lucide/svelte": "^0.515.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -442,15 +442,18 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.11.0", "eslint-plugin-unused-imports": "^4.2.0", + "formsnap": "^2.0.1", "globals": "^14.0.0", "layerchart": "^2.0.0-next.27", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.11", "svelte": "^5.0.0", + "svelte-awesome-color-picker": "^4.0.2", "svelte-check": "^4.0.0", "svelte-eslint-parser": "^1.3.1", "svelte-sonner": "^1.0.5", + "sveltekit-superforms": "^2.27.1", "tailwind-merge": "^3.3.1", "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", @@ -459,6 +462,16 @@ "vite": "^7.0.4" } }, + "app/frontend/node_modules/@lucide/svelte": { + "version": "0.515.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz", + "integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "app/frontend/node_modules/tailwind-variants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", @@ -487,6 +500,36 @@ "url": "https://github.com/sponsors/dcastil" } }, + "node_modules/@ark/schema": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.49.0.tgz", + "integrity": "sha512-GphZBLpW72iS0v4YkeUtV3YIno35Gimd7+ezbPO9GwEi9kzdUrPVjvf6aXSBAfHikaFc/9pqZOpv3pOXnC71tw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/util": "0.49.0" + } + }, + "node_modules/@ark/util": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.49.0.tgz", + "integrity": "sha512-/BtnX7oCjNkxi2vi6y1399b+9xd1jnCrDYhZ61f0a+3X8x8DxlK52VgEEzyuC2UQMPACIfYrmHkhD3lGt2GaMA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@dagrejs/dagre": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-1.1.5.tgz", @@ -1557,6 +1600,14 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@faker-js/faker": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.0.0.tgz", @@ -1609,6 +1660,58 @@ "license": "MIT", "optional": true }, + "node_modules/@gcornut/valibot-json-schema": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@gcornut/valibot-json-schema/-/valibot-json-schema-0.42.0.tgz", + "integrity": "sha512-4Et4AN6wmqeA0PfU5Clkv/IS27wiefsWf6TemAZrb75uzkClYEFavim7SboeKwbll9Nbsn2Iv0LT/HS5H7orZg==", + "dev": true, + "optional": true, + "dependencies": { + "valibot": "~0.42.0" + }, + "bin": { + "valibot-json-schema": "bin/index.js" + }, + "optionalDependencies": { + "@types/json-schema": ">= 7.0.14", + "esbuild-runner": ">= 2.2.2" + } + }, + "node_modules/@gcornut/valibot-json-schema/node_modules/valibot": { + "version": "0.42.1", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz", + "integrity": "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==", + "dev": true, + "license": "MIT", + "optional": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1972,16 +2075,6 @@ "win32" ] }, - "node_modules/@lucide/svelte": { - "version": "0.544.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.544.0.tgz", - "integrity": "sha512-9f9O6uxng2pLB01sxNySHduJN3HTl5p0HDu4H26VR51vhZfiMzyOMe9Mhof3XAk4l813eTtl+/DYRvGyoRR+yw==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "svelte": "^5" - } - }, "node_modules/@neon-rs/load": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", @@ -2059,6 +2152,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@poppinss/macroable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.1.0.tgz", + "integrity": "sha512-y/YKzZDuG8XrpXpM7Z1RdQpiIc0MAKyva24Ux1PB4aI7RiSI/79K8JVDcdyubriTm7vJ1LhFs8CrZpmPnx/8Pw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.6", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", @@ -2435,6 +2536,41 @@ "win32" ] }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -3090,6 +3226,41 @@ "@types/node": "*" } }, + "node_modules/@typeschema/class-validator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@typeschema/class-validator/-/class-validator-0.3.0.tgz", + "integrity": "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@typeschema/core": "0.14.0" + }, + "peerDependencies": { + "class-validator": "^0.14.1" + }, + "peerDependenciesMeta": { + "class-validator": { + "optional": true + } + } + }, + "node_modules/@typeschema/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@typeschema/core/-/core-0.14.0.tgz", + "integrity": "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==", + "dev": true, + "license": "MIT", + "optional": true, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + }, + "peerDependenciesMeta": { + "@types/json-schema": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.40.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", @@ -3361,6 +3532,38 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@vinejs/compiler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz", + "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@vinejs/vine": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.1.tgz", + "integrity": "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@poppinss/macroable": "^1.0.4", + "@types/validator": "^13.12.2", + "@vinejs/compiler": "^3.0.0", + "camelcase": "^8.0.0", + "dayjs": "^1.11.13", + "dlv": "^1.1.3", + "normalize-url": "^8.0.1", + "validator": "^13.12.0" + }, + "engines": { + "node": ">=18.16.0" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3535,6 +3738,18 @@ "node": ">= 0.4" } }, + "node_modules/arktype": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.1.22.tgz", + "integrity": "sha512-xdzl6WcAhrdahvRRnXaNwsipCgHuNoLobRqhiP8RjnfL9Gp947abGlo68GAIyLtxbD+MLzNyH2YR4kEqioMmYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/schema": "0.49.0", + "@ark/util": "0.49.0" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -3883,6 +4098,20 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3957,6 +4186,19 @@ "node": ">=10" } }, + "node_modules/class-validator": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", + "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.11.1", + "validator": "^13.9.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4021,6 +4263,13 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4530,6 +4779,14 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -4653,6 +4910,14 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/dot-env": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/dot-env/-/dot-env-0.0.1.tgz", @@ -4838,6 +5103,18 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.17.13", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.17.13.tgz", + "integrity": "sha512-JMz5oBxs/6mu4FP9Csjub4jYMUwMLrp+IzUmSDVIzn2NoeoyOXMl7x1lghfr3dLKWffWrdnv/d8nFFdgrHXPqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4989,6 +5266,32 @@ "esbuild": ">=0.12 <1" } }, + "node_modules/esbuild-runner": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz", + "integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==", + "dev": true, + "license": "Apache License 2.0", + "optional": true, + "dependencies": { + "source-map-support": "0.5.21", + "tslib": "2.4.0" + }, + "bin": { + "esr": "bin/esr.js" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/esbuild-runner/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5426,6 +5729,30 @@ "node": ">= 0.6" } }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5615,6 +5942,47 @@ "node": ">=12.20.0" } }, + "node_modules/formsnap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.1.tgz", + "integrity": "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "svelte-toolbelt": "^0.5.0" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "sveltekit-superforms": "^2.19.0" + } + }, + "node_modules/formsnap/node_modules/svelte-toolbelt": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.5.0.tgz", + "integrity": "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.126" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6243,6 +6611,21 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-base64": { "version": "3.7.8", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", @@ -6269,6 +6652,21 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -6373,6 +6771,14 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.17", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.17.tgz", + "integrity": "sha512-bsxi8FoceAYR/bjHcLYc2ShJ/aVAzo5jaxAYiMHF0BD+NTp47405CGuPNKYpw+lHadN9k/ClFGc9X5vaZswIrA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/libsql": { "version": "0.5.22", "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.22.tgz", @@ -6814,6 +7220,13 @@ "url": "https://github.com/sindresorhus/memoize?sponsor=1" } }, + "node_modules/memoize-weak": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz", + "integrity": "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==", + "dev": true, + "license": "ISC" + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -7343,6 +7756,20 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -7900,6 +8327,14 @@ "node": ">=10" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -7933,6 +8368,24 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -8847,6 +9300,17 @@ "inline-style-parser": "0.2.4" } }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -8885,6 +9349,30 @@ "node": ">=18" } }, + "node_modules/svelte-awesome-color-picker": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/svelte-awesome-color-picker/-/svelte-awesome-color-picker-4.0.2.tgz", + "integrity": "sha512-Ez72goMMNmw6sZhB1/BXEA8984lEkudPrdlNS+y3nHm2Lnk1w4nwy5NFyWPxTP7nFnLxhIqyV3VuJVG4PokKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "svelte-awesome-slider": "2.0.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-awesome-slider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svelte-awesome-slider/-/svelte-awesome-slider-2.0.0.tgz", + "integrity": "sha512-YBkOdYm1Feaqsn2JkJBRs+Kc/X3Qy/3GuVmI7GmoYDjBaHkjx9uH4khTuED22z57Hg3gGWeDhp/clIjWDdLNaw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": "^5.0.0" + } + }, "node_modules/svelte-check": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.1.tgz", @@ -9075,6 +9563,103 @@ "@types/estree": "^1.0.6" } }, + "node_modules/sveltekit-superforms": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.27.1.tgz", + "integrity": "sha512-cvq2AevkZ0Zrk0w0gNM3kjcnJMtJ0jzu+2zqDoM9a+lZa+8bGpNl4YqxVkemiJNkGnFgNC8xr5xF5BlMzjookQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ciscoheat" + }, + { + "type": "ko-fi", + "url": "https://ko-fi.com/ciscoheat" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=NY7F5ALHHSVQS" + } + ], + "license": "MIT", + "dependencies": { + "devalue": "^5.1.1", + "memoize-weak": "^1.0.2", + "ts-deepmerge": "^7.0.3" + }, + "optionalDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@gcornut/valibot-json-schema": "^0.42.0", + "@sinclair/typebox": "^0.34.35", + "@typeschema/class-validator": "^0.3.0", + "@vinejs/vine": "^3.0.1", + "arktype": "^2.1.20", + "class-validator": "^0.14.2", + "effect": "^3.16.7", + "joi": "^17.13.3", + "json-schema-to-ts": "^3.1.1", + "superstruct": "^2.0.2", + "valibot": "^1.1.0", + "yup": "^1.6.1", + "zod": "^3.25.64", + "zod-to-json-schema": "^3.24.5" + }, + "peerDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@sinclair/typebox": "^0.34.28", + "@sveltejs/kit": "1.x || 2.x", + "@typeschema/class-validator": "^0.3.0", + "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0", + "arktype": ">=2.0.0-rc.23", + "class-validator": "^0.14.1", + "effect": "^3.13.7", + "joi": "^17.13.1", + "superstruct": "^2.0.2", + "svelte": "3.x || 4.x || >=5.0.0-next.51", + "valibot": "^1.0.0", + "yup": "^1.4.0", + "zod": "^3.25.0" + }, + "peerDependenciesMeta": { + "@exodus/schemasafe": { + "optional": true + }, + "@sinclair/typebox": { + "optional": true + }, + "@typeschema/class-validator": { + "optional": true + }, + "@vinejs/vine": { + "optional": true + }, + "arktype": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "effect": { + "optional": true + }, + "joi": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "valibot": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -9221,6 +9806,14 @@ "node": ">=18" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -9260,6 +9853,14 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -9286,6 +9887,14 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -9299,6 +9908,16 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-deepmerge": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", + "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, "node_modules/tsc-alias": { "version": "1.8.16", "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", @@ -9393,6 +10012,20 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -9491,6 +10124,22 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "dev": true, + "license": "MIT", + "optional": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/validator": { "version": "13.15.15", "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", @@ -9761,11 +10410,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.0.tgz", + "integrity": "sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, "node_modules/zimmerframe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "license": "MIT" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, + "license": "ISC", + "optional": true, + "peerDependencies": { + "zod": "^3.24.1" + } } } } From a7e090116fbfae7f6c84c7c76d5938b532f3de07 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Mon, 15 Sep 2025 23:46:32 +0530 Subject: [PATCH 15/27] Added sheets for insurance and pucc as well --- app/backend/src/services/insuranceService.ts | 1 + .../services/pollutionCertificateService.ts | 1 + .../features/insurance/InsuranceForm.svelte | 285 ++++++++---------- .../features/insurance/InsuranceList.svelte | 14 +- .../features/insurance/InsuranceModal.svelte | 38 --- .../pullution/PollutionCertificateForm.svelte | 271 ++++++++--------- .../pullution/PollutionCertificateList.svelte | 11 + .../PollutionCertificateModal.svelte | 41 --- .../src/lib/services/insurance.service.ts | 35 +++ app/frontend/src/lib/services/pucc.service.ts | 35 +++ app/frontend/src/lib/types/fuel.ts | 11 +- app/frontend/src/lib/types/index.ts | 5 + app/frontend/src/lib/types/insurance.ts | 46 +++ app/frontend/src/lib/types/maintenance.ts | 11 +- app/frontend/src/lib/types/pucc.ts | 44 +++ .../src/routes/dashboard/+layout.svelte | 4 + 16 files changed, 465 insertions(+), 388 deletions(-) delete mode 100644 app/frontend/src/lib/components/features/insurance/InsuranceModal.svelte delete mode 100644 app/frontend/src/lib/components/features/pullution/PollutionCertificateModal.svelte create mode 100644 app/frontend/src/lib/services/insurance.service.ts create mode 100644 app/frontend/src/lib/services/pucc.service.ts create mode 100644 app/frontend/src/lib/types/insurance.ts create mode 100644 app/frontend/src/lib/types/pucc.ts diff --git a/app/backend/src/services/insuranceService.ts b/app/backend/src/services/insuranceService.ts index d2c0000e..0ed91be4 100644 --- a/app/backend/src/services/insuranceService.ts +++ b/app/backend/src/services/insuranceService.ts @@ -20,6 +20,7 @@ export const addInsurance = async (vehicleId: string, insuranceData: any) => { .values({ ...insuranceData, vehicleId: vehicleId, + id: undefined, }) .returning(); return { diff --git a/app/backend/src/services/pollutionCertificateService.ts b/app/backend/src/services/pollutionCertificateService.ts index 343d7387..6205064c 100644 --- a/app/backend/src/services/pollutionCertificateService.ts +++ b/app/backend/src/services/pollutionCertificateService.ts @@ -23,6 +23,7 @@ export const addPollutionCertificate = async ( .values({ ...pollutionCertificateData, vehicleId: vehicleId, + id: undefined, }) .returning(); return { diff --git a/app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte b/app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte index 9fbdfeb4..607f07c0 100644 --- a/app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte +++ b/app/frontend/src/lib/components/features/insurance/InsuranceForm.svelte @@ -1,172 +1,143 @@ - async function persistInsurance() { - if ( - !insurance.provider || - !insurance.policyNumber || - !insurance.startDate || - !insurance.endDate || - !insurance.cost - ) { - status = { - message: 'Provider, Policy Number, Start Date, End Date, Cost are required.', - type: 'ERROR' - }; - return; - } + insuranceModelStore.hide()} title="Add Insurance"> + e.preventDefault()}> +
+ + + {#snippet children({ props })} + Provider + + {/snippet} + + + - try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}/insurance/${editMode ? entryToEdit.id : ''}`, - { - method: `${editMode ? 'PUT' : 'POST'}`, - headers: { - 'Content-Type': 'application/json', - 'X-User-PIN': localStorage.getItem('userPin') || '' - }, - body: JSON.stringify(cleanup(insurance)) - } - ); + + + {#snippet children({ props })} + Policy Number + + {/snippet} + + + - if (response.ok) { - status = { - message: `Insurance details ${editMode ? 'updated' : 'added'} successfully!`, - type: 'SUCCESS' - }; - Object.assign(insurance, { - provider: '', - policyNumber: '', - startDate: '', - endDate: '', - cost: null - }); - modalVisibility = false; - } else { - const data = await response.json(); - status = handleApiError(data, editMode); - } - } catch (e) { - console.error(e); - status = { - message: 'Failed to connect to the server.', - type: 'ERROR' - }; - } - loading = false; - if (status.type === 'SUCCESS') { - entryToEdit = null; - callback(true); - } - } - +
+ + + {#snippet children({ props })} + Start Date + + {/snippet} + + + + + + + {#snippet children({ props })} + End Date + + {/snippet} + + + +
+ + + + {#snippet children({ props })} + Cost + + {/snippet} + + + - { - persistInsurance(); - e.preventDefault(); - }} -> - - -
- + + + {#snippet children({ props })} + Notes +