|
1 | 1 | <script lang="ts"> |
2 | | - import { createEventDispatcher } from 'svelte'; |
3 | | - import FormField from '../Common/FormField.svelte'; |
4 | | - import { Calendar1, Gauge, DollarSign, Fuel, FileText } from '@lucide/svelte'; |
5 | | - import { env } from '$env/dynamic/public'; |
| 2 | + import { createEventDispatcher } from "svelte"; |
| 3 | + import FuelRefillFormComponent from "./FuelRefillFormComponent.svelte"; |
| 4 | + import { env } from "$env/dynamic/public"; |
6 | 5 |
|
7 | | - const dispatch = createEventDispatcher(); |
| 6 | + const dispatch = createEventDispatcher(); |
8 | 7 |
|
9 | | - export let vehicleId: number | null = null; |
10 | | - export let onSuccess: (() => void) | null = null; |
11 | | - export let showModal: boolean = false; |
12 | | - export let closeModal: (() => void) | null = null; |
| 8 | + export let vehicleId: number | null = null; |
| 9 | + export let onSuccess: (() => void) | null = null; |
| 10 | + export let showModal: boolean = false; |
| 11 | + export let closeModal: (() => void) | null = null; |
13 | 12 |
|
14 | | - let refill = { |
15 | | - date: '', |
16 | | - odometer: '', |
17 | | - fuelAmount: '', |
18 | | - cost: '', |
19 | | - notes: '' |
20 | | - }; |
| 13 | + let refill = { |
| 14 | + date: "", |
| 15 | + odometer: "", |
| 16 | + fuelAmount: "", |
| 17 | + cost: "", |
| 18 | + notes: "", |
| 19 | + }; |
21 | 20 |
|
22 | | - let loading = false; |
23 | | - let error = ''; |
| 21 | + let loading = false; |
| 22 | + let error = ""; |
24 | 23 |
|
25 | | - function resetForm() { |
26 | | - refill = { |
27 | | - date: '', |
28 | | - odometer: '', |
29 | | - fuelAmount: '', |
30 | | - cost: '', |
31 | | - notes: '' |
32 | | - }; |
33 | | - } |
| 24 | + function resetForm() { |
| 25 | + refill = { |
| 26 | + date: "", |
| 27 | + odometer: "", |
| 28 | + fuelAmount: "", |
| 29 | + cost: "", |
| 30 | + notes: "", |
| 31 | + }; |
| 32 | + } |
34 | 33 |
|
35 | | - async function handleSubmit(e: Event) { |
36 | | - e.preventDefault(); |
37 | | - error = ''; |
38 | | - if (!vehicleId) { |
39 | | - error = 'No vehicle selected.'; |
40 | | - return; |
41 | | - } |
42 | | - if (!refill.date || !refill.odometer || !refill.fuelAmount || !refill.cost) { |
43 | | - error = 'Please fill in all required fields.'; |
44 | | - return; |
45 | | - } |
46 | | - loading = true; |
47 | | - try { |
48 | | - const response = await fetch(`${env.PUBLIC_API_BASE_URL}/api/vehicles/${vehicleId}/fuel-logs`, { |
49 | | - method: 'POST', |
50 | | - headers: { |
51 | | - 'Content-Type': 'application/json', |
52 | | - 'X-User-PIN': localStorage.getItem('userPin') || '' |
53 | | - }, |
54 | | - body: JSON.stringify({ |
55 | | - date: refill.date, |
56 | | - odometer: parseFloat(refill.odometer), |
57 | | - fuelAmount: parseFloat(refill.fuelAmount), |
58 | | - cost: parseFloat(refill.cost), |
59 | | - notes: refill.notes |
60 | | - }) |
61 | | - }); |
62 | | - if (response.ok) { |
63 | | - resetForm(); |
64 | | - dispatch('success'); |
65 | | - if (onSuccess) onSuccess(); |
66 | | - } else { |
67 | | - const data = await response.json(); |
68 | | - error = data?.message || 'Failed to log fuel refill.'; |
69 | | - } |
70 | | - } catch (err) { |
71 | | - error = 'Network error. Please try again.'; |
72 | | - } finally { |
73 | | - loading = false; |
74 | | - } |
75 | | - } |
| 34 | + async function handleSubmit() { |
| 35 | + error = ""; |
| 36 | + if (!vehicleId) { |
| 37 | + error = "No vehicle selected."; |
| 38 | + return; |
| 39 | + } |
| 40 | + if (!refill.date || !refill.odometer || !refill.fuelAmount || !refill.cost) { |
| 41 | + error = "Please fill in all required fields."; |
| 42 | + return; |
| 43 | + } |
| 44 | + loading = true; |
| 45 | + try { |
| 46 | + const response = await fetch( |
| 47 | + `${env.PUBLIC_API_BASE_URL}/api/vehicles/${vehicleId}/fuel-logs`, |
| 48 | + { |
| 49 | + method: "POST", |
| 50 | + headers: { |
| 51 | + "Content-Type": "application/json", |
| 52 | + "X-User-PIN": localStorage.getItem("userPin") || "", |
| 53 | + }, |
| 54 | + body: JSON.stringify({ |
| 55 | + date: refill.date, |
| 56 | + odometer: parseFloat(refill.odometer), |
| 57 | + fuelAmount: parseFloat(refill.fuelAmount), |
| 58 | + cost: parseFloat(refill.cost), |
| 59 | + notes: refill.notes, |
| 60 | + }), |
| 61 | + } |
| 62 | + ); |
| 63 | + if (response.ok) { |
| 64 | + resetForm(); |
| 65 | + dispatch("success"); |
| 66 | + if (onSuccess) onSuccess(); |
| 67 | + } else { |
| 68 | + const data = await response.json(); |
| 69 | + error = data?.message || "Failed to log fuel refill."; |
| 70 | + } |
| 71 | + } catch (err) { |
| 72 | + error = "Network error. Please try again."; |
| 73 | + } finally { |
| 74 | + loading = false; |
| 75 | + } |
| 76 | + } |
76 | 77 | </script> |
77 | 78 |
|
78 | 79 | {#if showModal} |
79 | | - <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"> |
80 | | - <div |
81 | | - class="animate-scale-up relative max-h-[90vh] w-full max-w-xl overflow-y-auto rounded-2xl bg-white p-10 shadow-2xl dark:bg-gray-800" |
82 | | - > |
83 | | - <!-- Close Icon --> |
84 | | - <button |
85 | | - type="button" |
86 | | - class="absolute top-6 right-6 rounded-full bg-gray-100 p-2 text-gray-400 transition hover:text-gray-700 dark:bg-gray-700 dark:text-gray-300 dark:hover:text-white" |
87 | | - on:click={closeModal} |
88 | | - aria-label="Close" |
89 | | - > |
90 | | - <svg |
91 | | - xmlns="http://www.w3.org/2000/svg" |
92 | | - class="h-6 w-6" |
93 | | - fill="none" |
94 | | - viewBox="0 0 24 24" |
95 | | - stroke="currentColor" |
96 | | - > |
97 | | - <path |
98 | | - stroke-linecap="round" |
99 | | - stroke-linejoin="round" |
100 | | - stroke-width="2" |
101 | | - d="M6 18L18 6M6 6l12 12" |
102 | | - /> |
103 | | - </svg> |
104 | | - </button> |
105 | | - <h2 class="mb-2 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100"> |
106 | | - Log Fuel Refill |
107 | | - </h2> |
108 | | - <div class="mb-8 border-b border-gray-200 dark:border-gray-700"></div> |
109 | | - <form class="space-y-6" on:submit={handleSubmit} aria-labelledby="fuel-refill-form-title"> |
110 | | - <FormField |
111 | | - id="date" |
112 | | - type="date" |
113 | | - placeholder="Date" |
114 | | - bind:value={refill.date} |
115 | | - icon={Calendar1} |
116 | | - required={true} |
117 | | - ariaLabel="Refill Date" |
118 | | - /> |
119 | | - <FormField |
120 | | - id="odometer" |
121 | | - type="number" |
122 | | - placeholder="Odometer Reading" |
123 | | - bind:value={refill.odometer} |
124 | | - icon={Gauge} |
125 | | - required={true} |
126 | | - ariaLabel="Odometer Reading" |
127 | | - inputClass="step-0.01" |
128 | | - /> |
129 | | - <FormField |
130 | | - id="fuelAmount" |
131 | | - type="number" |
132 | | - placeholder="Fuel Amount (liters)" |
133 | | - bind:value={refill.fuelAmount} |
134 | | - icon={Fuel} |
135 | | - required={true} |
136 | | - ariaLabel="Fuel Amount" |
137 | | - inputClass="step-0.01" |
138 | | - /> |
139 | | - <FormField |
140 | | - id="cost" |
141 | | - type="number" |
142 | | - placeholder="Cost" |
143 | | - bind:value={refill.cost} |
144 | | - icon={DollarSign} |
145 | | - required={true} |
146 | | - ariaLabel="Fuel Cost" |
147 | | - inputClass="step-0.01" |
148 | | - /> |
149 | | - <FormField |
150 | | - id="notes" |
151 | | - type="text" |
152 | | - placeholder="Notes (optional)" |
153 | | - bind:value={refill.notes} |
154 | | - icon={FileText} |
155 | | - ariaLabel="Notes" |
156 | | - /> |
157 | | - {#if error} |
158 | | - <div class="text-sm text-red-600 dark:text-red-400" role="alert">{error}</div> |
159 | | - {/if} |
160 | | - <div class="flex justify-center"> |
161 | | - <button |
162 | | - type="submit" |
163 | | - class="flex cursor-pointer gap-2 rounded-lg border-2 bg-blue-600 px-3 py-1 text-lg font-semibold text-blue-600 shadow-md dark:text-blue-200" |
164 | | - disabled={loading} |
165 | | - aria-busy={loading} |
166 | | - > |
167 | | - {loading ? 'Logging...' : 'Log Refill'} |
168 | | - </button> |
169 | | - </div> |
170 | | - </form> |
171 | | - </div> |
172 | | - </div> |
| 80 | + <div |
| 81 | + class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" |
| 82 | + > |
| 83 | + <div |
| 84 | + class="animate-scale-up relative max-h-[90vh] w-full max-w-xl overflow-y-auto rounded-2xl bg-white p-10 shadow-2xl dark:bg-gray-800" |
| 85 | + > |
| 86 | + <button |
| 87 | + type="button" |
| 88 | + class="absolute top-6 right-6 rounded-full bg-gray-100 p-2 text-gray-400 transition hover:text-gray-700 dark:bg-gray-700 dark:text-gray-300 dark:hover:text-white" |
| 89 | + on:click={closeModal} |
| 90 | + aria-label="Close" |
| 91 | + > |
| 92 | + <svg |
| 93 | + xmlns="http://www.w3.org/2000/svg" |
| 94 | + class="h-6 w-6" |
| 95 | + fill="none" |
| 96 | + viewBox="0 0 24 24" |
| 97 | + stroke="currentColor" |
| 98 | + > |
| 99 | + <path |
| 100 | + stroke-linecap="round" |
| 101 | + stroke-linejoin="round" |
| 102 | + stroke-width="2" |
| 103 | + d="M6 18L18 6M6 6l12 12" |
| 104 | + /> |
| 105 | + </svg> |
| 106 | + </button> |
| 107 | + <h2 |
| 108 | + class="mb-2 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100" |
| 109 | + > |
| 110 | + Log Fuel Refill |
| 111 | + </h2> |
| 112 | + <div class="mb-8 border-b border-gray-200 dark:border-gray-700"></div> |
| 113 | + <FuelRefillFormComponent |
| 114 | + bind:refill |
| 115 | + onSubmit={handleSubmit} |
| 116 | + bind:error |
| 117 | + bind:loading |
| 118 | + /> |
| 119 | + </div> |
| 120 | + </div> |
173 | 121 | {/if} |
0 commit comments