admin felület különválasztva
This commit is contained in:
234
frontend_app/src/components/dashboard/SimpleFuelModal.vue
Normal file
234
frontend_app/src/components/dashboard/SimpleFuelModal.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user