Files
service-finder/frontend/src/components/dashboard/CostsActionsCard.vue
2026-06-23 21:11:21 +00:00

210 lines
8.3 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div
class="bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden flex flex-col h-[350px] relative transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
>
<!-- Top header bar clickable to navigate to full Costs page -->
<div
class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4 gap-2 cursor-pointer transition-colors hover:bg-slate-600"
@click="navigateToCostsPage"
>
<span class="text-white font-bold text-sm tracking-wide">💰 {{ t('dashboard.costsTitle') }}</span>
<svg class="ml-auto h-4 w-4 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
</div>
<!-- Content -->
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
<!-- Vehicle Selector Dropdown -->
<div class="mb-3">
<label class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1 block">{{ t('dashboard.vehicle') }}</label>
<select
v-model="selectedActionVehicleId"
class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-800 focus:border-sf-accent focus:ring-2 focus:ring-sf-accent/20 outline-none transition-all"
>
<option
v-for="v in vehicleStore.sortedVehicles"
:key="v.id"
:value="v.id"
>
{{ v.license_plate || '—' }} ({{ v.brand || '' }} {{ v.model || '' }})
</option>
</select>
</div>
<!-- Action Buttons -->
<div class="flex-1 flex flex-col gap-2.5 justify-center">
<!-- No vehicle warning -->
<div
v-if="!selectedActionVehicle"
class="flex items-center justify-center rounded-xl bg-amber-50 border border-amber-200 px-4 py-3 text-sm text-amber-700"
>
<span> {{ t('dashboard.noVehicleSelected') }}</span>
</div>
<!-- Record Fueling (Big, prominent) -->
<button
v-if="selectedActionVehicle"
@click="openFuelModal"
class="flex items-center gap-3 rounded-xl bg-gradient-to-r from-sf-accent to-emerald-500 px-4 py-3 text-sm font-bold text-white shadow-md transition-all hover:shadow-lg hover:scale-[1.02] active:scale-[0.98]"
>
<span class="flex h-9 w-9 items-center justify-center rounded-lg bg-white/20 text-lg"></span>
<span>{{ t('dashboard.recordFueling') }}</span>
<span class="ml-auto text-white/60 text-xs"></span>
</button>
<!-- 🅿 Parking / Toll (Medium) -->
<button
v-if="selectedActionVehicle"
@click="openFeeModal"
class="flex items-center gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-700 transition-all hover:bg-slate-100 hover:shadow-md active:scale-[0.98]"
>
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-200 text-base">🅿</span>
<span>{{ t('dashboard.parkingToll') }}</span>
<span class="ml-auto text-slate-400 text-xs"></span>
</button>
<!-- Additional Costs (Medium) navigates to full Costs page -->
<button
v-if="selectedActionVehicle"
@click="navigateToCostsPage"
class="flex items-center gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-700 transition-all hover:bg-slate-100 hover:shadow-md active:scale-[0.98]"
>
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-200 text-base"></span>
<span>{{ t('dashboard.additionalCosts') }}</span>
<span class="ml-auto text-slate-400 text-xs"></span>
</button>
<!-- 🧾 Detailed Invoice (Medium) -->
<button
v-if="selectedActionVehicle"
@click="openWizardModal"
class="flex items-center gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-700 transition-all hover:bg-slate-100 hover:shadow-md active:scale-[0.98]"
>
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-200 text-base">🧾</span>
<span>{{ t('dashboard.detailedInvoice') }}</span>
<span class="ml-auto text-slate-400 text-xs"></span>
</button>
</div>
</div>
<!--
Cost Action Modals (Teleported to body)
-->
<SimpleFuelModal
:is-open="isFuelModalOpen"
:vehicle="selectedActionVehicle"
@close="isFuelModalOpen = false"
@saved="onModalSaved"
/>
<ComplexExpenseModal
:is-open="isFeeModalOpen"
:vehicle="selectedActionVehicle"
:is-fee-mode="true"
@close="isFeeModalOpen = false"
@saved="onModalSaved"
/>
<ComplexExpenseModal
:is-open="isComplexModalOpen"
:vehicle="selectedActionVehicle"
:is-fee-mode="false"
@close="isComplexModalOpen = false"
@saved="onModalSaved"
/>
<CostEntryWizard
:is-open="isWizardModalOpen"
:vehicle="selectedActionVehicle"
@close="isWizardModalOpen = false"
@saved="onModalSaved"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useVehicleStore } from '../../stores/vehicle'
import SimpleFuelModal from './SimpleFuelModal.vue'
import ComplexExpenseModal from './ComplexExpenseModal.vue'
import CostEntryWizard from '../cost/CostEntryWizard.vue'
const { t } = useI18n()
const router = useRouter()
const vehicleStore = useVehicleStore()
/** Navigate to the full Costs page */
function navigateToCostsPage() {
router.push('/dashboard/costs')
}
// ── Emits ──
const emit = defineEmits<{
/**
* F5 Bug fix (RBAC Phase 3): Értesíti a szülő komponenst, hogy egy költség sikeresen el lett mentve.
* A szülő (DashboardView) továbbítja ezt a VehicleDetailModal-nak, ami újratölti a költségeket.
*/
'cost-saved': [vehicleId: string]
}>()
// ── Cost Card Action Center State ──
const selectedActionVehicleId = ref<string>('')
const isFuelModalOpen = ref(false)
const isFeeModalOpen = ref(false)
const isComplexModalOpen = ref(false)
const isWizardModalOpen = ref(false)
/** The currently selected vehicle object for the action modals */
const selectedActionVehicle = computed(() => {
if (!selectedActionVehicleId.value) return null
return vehicleStore.vehicles.find(v => v.id === selectedActionVehicleId.value) || null
})
/** Auto-select the first (primary/sorted) vehicle on load */
function autoSelectVehicle() {
if (vehicleStore.sortedVehicles.length > 0 && !selectedActionVehicleId.value) {
selectedActionVehicleId.value = vehicleStore.sortedVehicles[0].id
}
}
function openFuelModal() {
if (!selectedActionVehicle.value) return
isFuelModalOpen.value = true
}
function openFeeModal() {
if (!selectedActionVehicle.value) return
isFeeModalOpen.value = true
}
function openComplexModal() {
if (!selectedActionVehicle.value) return
isComplexModalOpen.value = true
}
function openWizardModal() {
if (!selectedActionVehicle.value) return
isWizardModalOpen.value = true
}
/**
* F5 Bug fix (RBAC Phase 3): Modal mentés után bezárja a modalt,
* és értesíti a szülőt, hogy az új költség megjelent.
*/
function onModalSaved() {
isFuelModalOpen.value = false
isFeeModalOpen.value = false
isComplexModalOpen.value = false
isWizardModalOpen.value = false
// Értesítjük a szülőt, hogy frissítse a VehicleDetailModal költségeit
if (selectedActionVehicleId.value) {
emit('cost-saved', selectedActionVehicleId.value)
}
}
// Watch for vehicles to load, then auto-select
watch(() => vehicleStore.vehicles.length, () => {
autoSelectVehicle()
}, { immediate: true })
</script>