210 lines
8.3 KiB
Vue
210 lines
8.3 KiB
Vue
<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>
|