egyedi jármű szerkesztés előtti mentés

This commit is contained in:
Roo
2026-06-12 07:56:15 +00:00
parent 0a3fd8de74
commit ef8df9608c
29 changed files with 3863 additions and 396 deletions

View File

@@ -62,7 +62,7 @@
<!-- Top header bar -->
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
<span class="text-white font-bold text-sm tracking-wide">🚗 {{ t('dashboard.myVehicles') }}</span>
<span class="ml-auto text-xs text-white/60">{{ displayVehicles.length }} {{ t('dashboard.vehicles') }}</span>
<span class="ml-auto text-xs text-white/60">{{ vehicleStore.vehicles.length }} {{ t('dashboard.vehicles') }}</span>
</div>
<!-- Content -->
<div class="p-4 flex-1 flex flex-col text-slate-800 min-h-0">
@@ -72,7 +72,7 @@
:key="vehicle.id"
:vehicle="vehicle"
@click="openCard('vehicles')"
@plate-click="openCard('vehicles', vehicle.license_plate)"
@plate-click="openVehicleDetail(vehicle)"
/>
<!-- Empty state (only when BOTH real and mock are empty) -->
<div
@@ -89,49 +89,67 @@
</div>
<!--
Card 2: 💰 Costs & TCO (Költségek & Analitika)
Card 2: 💰 Költségek & Gyorsműveletek (Action Center)
-->
<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 cursor-pointer transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
@click="openCard('financials')"
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 -->
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
<span class="text-white font-bold text-sm tracking-wide">💰 {{ t('dashboard.fuelCost') }} & TCO</span>
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4 gap-2">
<span class="text-white font-bold text-sm tracking-wide">💰 Költségek & Gyorsműveletek</span>
</div>
<!-- Content -->
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
<div class="flex-1 space-y-3">
<!-- Monthly cost summary -->
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3">
<p class="text-xs text-slate-500 uppercase tracking-wider font-semibold">{{ t('dashboard.monthlyScore') }}</p>
<p class="text-2xl font-extrabold text-slate-800 mt-1"> 2,450</p>
</div>
<!-- Stat rows -->
<div class="space-y-1.5">
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
<span class="text-slate-500">{{ t('dashboard.totalKm') }}</span>
<span class="font-semibold text-slate-800">12,430 km</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
<span class="text-slate-500">{{ t('dashboard.fuelCost') }}</span>
<span class="font-semibold text-slate-800"> 0.42</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
<span class="text-slate-500">{{ t('dashboard.serviceCost') }}</span>
<span class="font-semibold text-slate-800"> 1.28</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
<span class="text-slate-500">{{ t('dashboard.co2Savings') }}</span>
<span class="font-semibold text-emerald-600">-340 kg</span>
</div>
</div>
<!-- Vehicle Selector Dropdown -->
<div class="mb-3">
<label class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1 block">Jármű</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">
<!-- Tankolás (Big, prominent) -->
<button
@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>Tankolás rögzítése</span>
<span class="ml-auto text-white/60 text-xs"></span>
</button>
<!-- 🅿 Parkolás / Útdíj (Medium) -->
<button
@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>Parkolás / Útdíj</span>
<span class="ml-auto text-slate-400 text-xs"></span>
</button>
<!-- További költségek (Medium) -->
<button
@click="openComplexModal"
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>További költségek</span>
<span class="ml-auto text-slate-400 text-xs"></span>
</button>
</div>
</div>
<!-- Bottom CTA button -->
<button class="mt-auto mb-4 mx-auto px-8 py-3 bg-slate-700 hover:bg-slate-800 text-white rounded-2xl font-medium text-sm transition-colors shadow-md">
{{ t('menu.finance') }}
</button>
</div>
<!--
@@ -297,7 +315,12 @@
<!-- Dynamic content area -->
<div class="flex-1 p-8 overflow-y-auto text-slate-800">
<!-- Private Vehicle Manager -->
<PrivateVehicleManager v-if="activeCard === 'vehicles'" :target-vehicle-id="selectedTargetId" />
<PrivateVehicleManager
v-if="activeCard === 'vehicles'"
:target-vehicle-id="selectedTargetId"
:edit-target-vehicle="editTargetVehicle"
@clear-edit-target="editTargetVehicle = null"
/>
<!-- Fallback for other cards -->
<template v-if="activeCard && activeCard !== 'vehicles'">
@@ -312,17 +335,55 @@
</div>
</div>
</Teleport>
<!--
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"
/>
<!--
Vehicle Detail Modal (Digital Twin 3-Tab Deep Link)
-->
<VehicleDetailModal
:is-open="isVehicleDetailOpen"
:vehicle="detailVehicle"
@close="closeVehicleDetail"
@edit="handleEditFromDetail"
@set-primary="handleSetPrimaryFromDetail"
/>
</div><!-- end min-h-screen -->
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '../stores/auth'
import { useVehicleStore } from '../stores/vehicle'
import type { VehicleData } from '../types/vehicle'
import PrivateVehicleManager from '../components/dashboard/PrivateVehicleManager.vue'
import VehicleCardCompact from '../components/vehicle/VehicleCardCompact.vue'
import { mockVehicles } from '../data/mockVehicles'
import VehicleDetailModal from '../components/vehicle/VehicleDetailModal.vue'
import SimpleFuelModal from '../components/dashboard/SimpleFuelModal.vue'
import ComplexExpenseModal from '../components/dashboard/ComplexExpenseModal.vue'
const { t } = useI18n()
@@ -340,24 +401,83 @@ const openCard = (cardId: string, targetId: string | null = null) => {
selectedTargetId.value = targetId
}
// ── Vehicle Detail Modal (Deep Link from plate click) ──
const isVehicleDetailOpen = ref(false)
const detailVehicle = ref<VehicleData | null>(null)
// ── Task 3: Edit target for direct VehicleFormModal open ──
const editTargetVehicle = ref<VehicleData | null>(null)
function openVehicleDetail(vehicle: VehicleData) {
detailVehicle.value = vehicle
isVehicleDetailOpen.value = true
}
function closeVehicleDetail() {
isVehicleDetailOpen.value = false
detailVehicle.value = null
}
function handleEditFromDetail(vehicle: VehicleData) {
closeVehicleDetail()
// Task 3: Open the vehicles card AND pass the vehicle to edit directly
editTargetVehicle.value = vehicle
activeCard.value = 'vehicles'
}
function handleSetPrimaryFromDetail(vehicle: VehicleData) {
vehicleStore.setPrimaryVehicle(vehicle.id)
}
/**
* Smart vehicle display: always show at least 3 vehicles for testing.
* - Real vehicles from the backend come first (sorted by is_primary).
* - If fewer than 3 real vehicles exist, fill the gap with mock data
* (excluding any mock that has the same license plate as a real vehicle).
* Display vehicles from the backend API only.
* Sorted by is_primary flag (favorite first), then alphabetically.
* Shows all real vehicles; no mock/fallback data is injected.
*/
const displayVehicles = computed(() => {
const real = vehicleStore.vehicles
if (real.length >= 3) return real.slice(0, 3)
const needed = 3 - real.length
const realPlates = new Set(real.map(v => v.license_plate || v.licensePlate))
const mocksToUse = mockVehicles
.filter(mv => !real.some(rv => (rv.license_plate || rv.licensePlate) === (mv.license_plate || mv.licensePlate)))
.slice(0, needed)
return [...real, ...mocksToUse]
return vehicleStore.sortedVehicles
})
// ── Cost Card Action Center State ──
const selectedActionVehicleId = ref<string>('')
const isFuelModalOpen = ref(false)
const isFeeModalOpen = ref(false)
const isComplexModalOpen = 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 onModalSaved() {
isFuelModalOpen.value = false
isFeeModalOpen.value = false
isComplexModalOpen.value = false
}
// ── Placeholder notifications ──
const notifications = ref([
{ icon: '🔧', title: 'Service due for BMW X5', time: '2 hours ago' },
@@ -378,6 +498,11 @@ onMounted(() => {
vehicleStore.fetchVehicles()
authStore.fetchMyOrganizations()
})
// Watch for vehicles to load, then auto-select
watch(() => vehicleStore.vehicles.length, () => {
autoSelectVehicle()
}, { immediate: true })
</script>
<style scoped>