235 lines
7.6 KiB
Vue
235 lines
7.6 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="isOpen"
|
|
class="fixed inset-0 z-[9999] flex items-center justify-center bg-slate-900/60 backdrop-blur-sm"
|
|
@click.self="$emit('close')"
|
|
>
|
|
<div class="bg-white w-full max-w-md rounded-2xl shadow-2xl overflow-hidden animate-fade-in-up">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between bg-slate-700 px-6 py-4">
|
|
<h3 class="text-lg font-bold text-white flex items-center gap-2">
|
|
<span>⛽</span> Tankolás rögzítése
|
|
</h3>
|
|
<button
|
|
class="flex h-8 w-8 items-center justify-center rounded-full bg-white/10 text-white/70 hover:bg-white/20 hover:text-white transition-colors"
|
|
@click="$emit('close')"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Form Body -->
|
|
<form @submit.prevent="handleSubmit" class="p-6 space-y-5">
|
|
<!-- Vehicle selector (read-only display of selected vehicle) -->
|
|
<div class="rounded-lg bg-slate-50 border border-slate-200 px-4 py-3 text-sm text-slate-600">
|
|
<span class="font-semibold text-slate-800">{{ vehicleLabel }}</span>
|
|
</div>
|
|
|
|
<!-- Date -->
|
|
<div>
|
|
<label class="block text-sm font-semibold text-slate-700 mb-1.5">Dátum</label>
|
|
<input
|
|
v-model="form.date"
|
|
type="date"
|
|
required
|
|
class="w-full rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm text-slate-800 focus:border-sf-accent focus:ring-2 focus:ring-sf-accent/20 outline-none transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Amount (HUF) -->
|
|
<div>
|
|
<label class="block text-sm font-semibold text-slate-700 mb-1.5">Összeg (HUF)</label>
|
|
<input
|
|
v-model.number="form.amount"
|
|
type="number"
|
|
min="0"
|
|
step="1"
|
|
required
|
|
placeholder="pl. 15000"
|
|
class="w-full rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm text-slate-800 focus:border-sf-accent focus:ring-2 focus:ring-sf-accent/20 outline-none transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Quantity (Liters/kWh) -->
|
|
<div>
|
|
<label class="block text-sm font-semibold text-slate-700 mb-1.5">Mennyiség (Liter / kWh)</label>
|
|
<input
|
|
v-model.number="form.quantity"
|
|
type="number"
|
|
min="0"
|
|
step="0.1"
|
|
required
|
|
placeholder="pl. 45.5"
|
|
class="w-full rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm text-slate-800 focus:border-sf-accent focus:ring-2 focus:ring-sf-accent/20 outline-none transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Odometer -->
|
|
<div>
|
|
<label class="block text-sm font-semibold text-slate-700 mb-1.5">Km óra állás</label>
|
|
<input
|
|
v-model.number="form.odometer"
|
|
type="number"
|
|
min="0"
|
|
step="1"
|
|
placeholder="pl. 123456"
|
|
class="w-full rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm text-slate-800 focus:border-sf-accent focus:ring-2 focus:ring-sf-accent/20 outline-none transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Error message -->
|
|
<div
|
|
v-if="errorMessage"
|
|
class="rounded-lg bg-red-50 border border-red-200 px-4 py-3 text-sm text-red-700"
|
|
>
|
|
{{ errorMessage }}
|
|
</div>
|
|
|
|
<!-- Submit button -->
|
|
<button
|
|
type="submit"
|
|
:disabled="costStore.isSubmitting"
|
|
class="w-full rounded-xl bg-sf-accent py-3 text-sm font-bold text-white shadow-md transition-all hover:bg-sf-blue hover:shadow-lg disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
>
|
|
<svg
|
|
v-if="costStore.isSubmitting"
|
|
class="h-4 w-4 animate-spin"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
<span v-else>✅ Tankolás rögzítése</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
|
import { useCostStore } from '../../stores/cost'
|
|
import api from '../../api/axios'
|
|
import type { Vehicle } from '../../stores/vehicle'
|
|
import { getLocalDateString } from '../../services/dateUtils'
|
|
|
|
const props = defineProps<{
|
|
isOpen: boolean
|
|
vehicle: Vehicle | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
close: []
|
|
saved: []
|
|
}>()
|
|
|
|
const costStore = useCostStore()
|
|
|
|
const form = reactive({
|
|
date: getLocalDateString(),
|
|
amount: 0,
|
|
quantity: 0,
|
|
odometer: 0,
|
|
})
|
|
|
|
const errorMessage = ref<string | null>(null)
|
|
|
|
// ── Category Resolution ──
|
|
const fuelCategoryId = ref<number>(1) // Default fallback
|
|
|
|
async function resolveFuelCategory() {
|
|
try {
|
|
const res = await api.get('/dictionaries/cost-categories')
|
|
const allCats = res.data as Array<{ id: number; code?: string; name: string; parent_id: number | null }>
|
|
const fuelCat = allCats.find((c) => c.code === 'FUEL' && c.parent_id === null)
|
|
if (fuelCat) {
|
|
fuelCategoryId.value = fuelCat.id
|
|
}
|
|
} catch (err) {
|
|
console.warn('[SimpleFuelModal] Could not fetch categories, using fallback ID 1:', err)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the form date to today's local date every time the modal opens.
|
|
* This ensures the date-picker always shows the correct local date,
|
|
* even if the component is kept alive between opens.
|
|
*/
|
|
watch(() => props.isOpen, (open) => {
|
|
if (open) {
|
|
form.date = getLocalDateString()
|
|
errorMessage.value = null
|
|
}
|
|
})
|
|
|
|
onMounted(() => {
|
|
resolveFuelCategory()
|
|
})
|
|
|
|
const vehicleLabel = computed(() => {
|
|
if (!props.vehicle) return 'Nincs kiválasztva jármű'
|
|
const plate = props.vehicle.license_plate || ''
|
|
const brand = props.vehicle.brand || ''
|
|
const model = props.vehicle.model || ''
|
|
return `${plate} (${brand} ${model})`.trim()
|
|
})
|
|
|
|
async function handleSubmit() {
|
|
if (!props.vehicle) {
|
|
errorMessage.value = 'Nincs kiválasztva jármű.'
|
|
return
|
|
}
|
|
|
|
errorMessage.value = null
|
|
|
|
// Build payload: category auto-set to FUEL (resolved dynamically by code)
|
|
// organization_id is omitted — backend auto-resolves from asset
|
|
const payload = {
|
|
asset_id: props.vehicle.id,
|
|
category_id: fuelCategoryId.value, // FUEL category (resolved by code)
|
|
/** GROSS-FIRST (Masterbook 2.0.1): amount_gross is the primary field */
|
|
amount_gross: form.amount,
|
|
currency: 'HUF' as const,
|
|
date: new Date(form.date).toISOString(),
|
|
mileage_at_cost: form.odometer > 0 ? form.odometer : null,
|
|
description: `Tankolás: ${form.quantity} L/kWh`,
|
|
data: {
|
|
quantity: form.quantity,
|
|
fuel_type: props.vehicle.fuel_type || null,
|
|
},
|
|
}
|
|
|
|
const result = await costStore.addExpense(payload)
|
|
|
|
if (result) {
|
|
// Reset form
|
|
form.date = getLocalDateString()
|
|
form.amount = 0
|
|
form.quantity = 0
|
|
form.odometer = 0
|
|
emit('saved')
|
|
} else {
|
|
errorMessage.value = costStore.lastError || 'Ismeretlen hiba történt.'
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px) scale(0.96);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
.animate-fade-in-up {
|
|
animation: fadeInUp 0.25s ease-out forwards;
|
|
}
|
|
</style>
|