201 előtti mentés

This commit is contained in:
Roo
2026-03-26 07:09:44 +00:00
parent 89668a9beb
commit 03258db091
124 changed files with 13619 additions and 13347 deletions

View File

@@ -0,0 +1,277 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
vehicles: {
type: Array,
required: true,
default: () => []
}
})
// Enhanced status colors for corporate look
const statusColors = {
'OK': 'bg-emerald-50 text-emerald-700 border border-emerald-200',
'Service Due': 'bg-blue-50 text-blue-900 border border-blue-200 animate-pulse',
'Warning': 'bg-rose-50 text-rose-700 border border-rose-200'
}
const sortedVehicles = computed(() => {
return [...props.vehicles].sort((a, b) => b.monthlyExpense - a.monthlyExpense)
})
const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR',
minimumFractionDigits: 0
}).format(amount)
}
const formatMileage = (mileage) => {
return new Intl.NumberFormat('en-US').format(mileage)
}
// Country flag mapping
const getCountryFlag = (make) => {
const makeLower = make.toLowerCase()
if (makeLower.includes('bmw') || makeLower.includes('mercedes') || makeLower.includes('audi') || makeLower.includes('volkswagen') || makeLower.includes('porsche')) {
return 'https://flagcdn.com/w40/de.png'
} else if (makeLower.includes('tesla') || makeLower.includes('ford') || makeLower.includes('chevrolet') || makeLower.includes('dodge')) {
return 'https://flagcdn.com/w40/us.png'
} else if (makeLower.includes('toyota') || makeLower.includes('honda') || makeLower.includes('nissan') || makeLower.includes('mazda')) {
return 'https://flagcdn.com/w40/jp.png'
} else if (makeLower.includes('ferrari') || makeLower.includes('lamborghini') || makeLower.includes('fiat') || makeLower.includes('alfa romeo')) {
return 'https://flagcdn.com/w40/it.png'
} else if (makeLower.includes('volvo') || makeLower.includes('saab')) {
return 'https://flagcdn.com/w40/se.png'
} else if (makeLower.includes('renault') || makeLower.includes('peugeot') || makeLower.includes('citroen')) {
return 'https://flagcdn.com/w40/fr.png'
} else if (makeLower.includes('skoda') || makeLower.includes('seat')) {
return 'https://flagcdn.com/w40/cz.png'
} else {
return 'https://flagcdn.com/w40/eu.png'
}
}
</script>
<template>
<div class="bg-white/80 backdrop-blur-sm rounded-2xl shadow-2xl border border-gray-300/50 overflow-hidden">
<!-- Corporate Glass Header -->
<div class="px-8 py-5 border-b border-gray-300/30 bg-gradient-to-r from-slate-900/90 to-slate-800/90 backdrop-blur-md">
<div class="flex justify-between items-center">
<div>
<h2 class="text-2xl font-bold text-white tracking-tight">Corporate Fleet Management</h2>
<p class="text-sm text-slate-300 mt-1">Enterprise-grade vehicle oversight with real-time analytics</p>
</div>
<div class="text-right">
<div class="text-3xl font-bold text-white">{{ formatCurrency(vehicles.reduce((sum, v) => sum + v.monthlyExpense, 0)) }}</div>
<div class="text-sm text-slate-300">Total monthly fleet cost {{ vehicles.length }} assets</div>
</div>
</div>
</div>
<!-- Table Container -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-300/30">
<thead class="bg-gradient-to-r from-slate-100 to-slate-200/80">
<tr>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2">🚗</span> Vehicle
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2">🏷</span> License Plate
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2">📅</span> Year
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2">📊</span> Mileage
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2"></span> Fuel Type
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2">🔧</span> Status
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2">💰</span> Monthly Cost
</div>
</th>
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
<div class="flex items-center">
<span class="mr-2"></span> Actions
</div>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-300/20">
<tr
v-for="(vehicle, index) in sortedVehicles"
:key="vehicle.id"
:class="[
'transition-all duration-200 hover:bg-slate-100/80',
index % 2 === 0 ? 'bg-white' : 'bg-slate-50/70'
]"
>
<td class="px-8 py-5 whitespace-nowrap">
<div class="flex items-center">
<div class="h-12 w-12 flex-shrink-0 bg-gradient-to-br from-slate-200 to-slate-300 rounded-xl overflow-hidden mr-4 shadow-sm border border-slate-300/50">
<img
:src="vehicle.imageUrl"
:alt="`${vehicle.make} ${vehicle.model}`"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="font-bold text-slate-900 text-lg">{{ vehicle.make }} {{ vehicle.model }}</div>
<div class="text-sm text-slate-600 mt-1">ID: {{ vehicle.id }} Asset</div>
</div>
</div>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<div class="flex items-center space-x-3">
<img
:src="getCountryFlag(vehicle.make)"
:alt="`${vehicle.make} origin flag`"
class="w-6 h-4 rounded-sm shadow-md border border-slate-300"
/>
<div>
<div class="font-mono font-bold text-slate-900 text-lg tracking-wider">{{ vehicle.licensePlate }}</div>
<div class="text-xs text-slate-500 mt-1">Registered</div>
</div>
</div>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<div class="text-center">
<div class="text-2xl font-bold text-slate-900">{{ vehicle.year }}</div>
<div class="text-xs text-slate-500 mt-1">Model Year</div>
</div>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<div class="text-center">
<div class="text-2xl font-bold text-slate-900">{{ formatMileage(vehicle.mileage) }}</div>
<div class="text-xs text-slate-500 mt-1">km</div>
</div>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<span :class="[
'px-4 py-2 rounded-full text-sm font-semibold shadow-sm',
vehicle.fuelType === 'Electric' ? 'bg-emerald-100 text-emerald-800 border border-emerald-300' :
vehicle.fuelType === 'Diesel' ? 'bg-blue-100 text-blue-800 border border-blue-300' :
'bg-amber-100 text-amber-800 border border-amber-300'
]">
{{ vehicle.fuelType }}
</span>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<div class="flex items-center">
<span
:class="['px-4 py-2 rounded-full text-sm font-semibold flex items-center', statusColors[vehicle.status] || 'bg-slate-100 text-slate-800 border border-slate-300']"
>
<span v-if="vehicle.status === 'OK'" class="w-2 h-2 bg-emerald-500 rounded-full mr-2"></span>
<span v-else-if="vehicle.status === 'Service Due'" class="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"></span>
<span v-else class="w-2 h-2 bg-rose-500 rounded-full mr-2"></span>
{{ vehicle.status }}
</span>
</div>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<div class="text-center">
<div class="text-2xl font-bold text-slate-900">{{ formatCurrency(vehicle.monthlyExpense) }}</div>
<div class="text-xs text-slate-500 mt-1">per month</div>
</div>
</td>
<td class="px-8 py-5 whitespace-nowrap">
<div class="flex space-x-2">
<button class="px-4 py-2.5 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white font-medium rounded-xl text-sm transition-all duration-200 active:scale-95 shadow-md hover:shadow-lg">
View Details
</button>
<button class="px-3 py-2.5 border border-slate-300 hover:bg-slate-100 rounded-xl text-slate-700 transition-all duration-200 active:scale-95 shadow-sm hover:shadow">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Corporate Footer -->
<div class="px-8 py-5 border-t border-gray-300/30 bg-gradient-to-r from-slate-100 to-slate-200/80">
<div class="flex justify-between items-center">
<div class="text-sm text-slate-700">
<span class="font-semibold">Showing {{ vehicles.length }} of {{ vehicles.length }} corporate assets</span>
<span class="mx-2"></span>
<span>Last updated: {{ new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) }}</span>
</div>
<div class="flex space-x-3">
<button class="px-5 py-2.5 border border-slate-300 hover:bg-white text-slate-700 font-medium rounded-xl text-sm transition-all duration-200 active:scale-95 shadow-sm hover:shadow flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Export CSV
</button>
<button class="px-5 py-2.5 bg-gradient-to-r from-emerald-600 to-emerald-700 hover:from-emerald-700 hover:to-emerald-800 text-white font-medium rounded-xl text-sm transition-all duration-200 active:scale-95 shadow-md hover:shadow-lg flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Add Vehicle
</button>
<button class="px-5 py-2.5 bg-gradient-to-r from-slate-700 to-slate-800 hover:from-slate-800 hover:to-slate-900 text-white font-medium rounded-xl text-sm transition-all duration-200 active:scale-95 shadow-md hover:shadow-lg flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Analytics
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* Custom table styles */
table {
border-spacing: 0;
}
/* Smooth row transitions */
tr {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Custom scrollbar for table */
.overflow-x-auto::-webkit-scrollbar {
height: 8px;
}
.overflow-x-auto::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 4px;
}
.overflow-x-auto::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
.overflow-x-auto::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>

View File

@@ -0,0 +1,130 @@
<script setup>
import { useThemeStore } from '@/stores/themeStore'
defineProps({
vehicle: {
type: Object,
required: true
}
})
const themeStore = useThemeStore()
const themeClasses = themeStore.themeClasses
const statusColors = {
'OK': 'bg-green-100 text-green-800',
'Service Due': 'bg-blue-100 text-blue-900',
'Warning': 'bg-orange-100 text-orange-800'
}
const brandLogoUrl = (make) => {
const cleanMake = make.toLowerCase().replace(/\s+/g, '')
// Use simpleicons CDN
return `https://cdn.simpleicons.org/${cleanMake}`
}
// Country flag mapping
const getCountryFlag = (make) => {
const makeLower = make.toLowerCase()
if (makeLower.includes('bmw') || makeLower.includes('mercedes') || makeLower.includes('audi') || makeLower.includes('volkswagen') || makeLower.includes('porsche')) {
return 'https://flagcdn.com/w40/de.png'
} else if (makeLower.includes('tesla') || makeLower.includes('ford') || makeLower.includes('chevrolet') || makeLower.includes('dodge')) {
return 'https://flagcdn.com/w40/us.png'
} else if (makeLower.includes('toyota') || makeLower.includes('honda') || makeLower.includes('nissan') || makeLower.includes('mazda')) {
return 'https://flagcdn.com/w40/jp.png'
} else if (makeLower.includes('ferrari') || makeLower.includes('lamborghini') || makeLower.includes('fiat') || makeLower.includes('alfa romeo')) {
return 'https://flagcdn.com/w40/it.png'
} else if (makeLower.includes('volvo') || makeLower.includes('saab')) {
return 'https://flagcdn.com/w40/se.png'
} else if (makeLower.includes('renault') || makeLower.includes('peugeot') || makeLower.includes('citroen')) {
return 'https://flagcdn.com/w40/fr.png'
} else if (makeLower.includes('skoda') || makeLower.includes('seat')) {
return 'https://flagcdn.com/w40/cz.png'
} else {
return 'https://flagcdn.com/w40/eu.png'
}
}
</script>
<template>
<div :class="['rounded-2xl shadow-lg overflow-hidden hover:shadow-xl transition-all duration-500 border', themeClasses.card]">
<!-- Vehicle Image -->
<div class="h-48 bg-gray-200 relative overflow-hidden">
<img
:src="vehicle.imageUrl"
:alt="`${vehicle.make} ${vehicle.model}`"
class="w-full h-full object-cover"
/>
<!-- Brand Logo -->
<div class="absolute top-3 left-3 bg-white/80 backdrop-blur-sm rounded-lg p-2 shadow-md">
<img
:src="brandLogoUrl(vehicle.make)"
:alt="vehicle.make"
class="w-8 h-8"
@error="(e) => e.target.style.display = 'none'"
/>
</div>
<div class="absolute top-3 right-3">
<span
:class="['px-3 py-1 rounded-full text-xs font-semibold', statusColors[vehicle.status] || 'bg-gray-100 text-gray-800']"
>
{{ vehicle.status }}
</span>
</div>
</div>
<!-- Vehicle Details -->
<div class="p-6">
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="text-xl font-bold text-gray-900">{{ vehicle.make }} {{ vehicle.model }}</h3>
<p class="text-gray-600">{{ vehicle.year }} {{ vehicle.fuelType }}</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-blue-700">{{ vehicle.monthlyExpense }}</div>
<div class="text-sm text-gray-500">/month</div>
</div>
</div>
<!-- License Plate with Country Flag -->
<div class="mb-4">
<div class="inline-flex items-center bg-gray-100 px-4 py-2 rounded-lg space-x-3">
<img
:src="getCountryFlag(vehicle.make)"
:alt="`${vehicle.make} origin flag`"
class="w-6 h-4 rounded-sm shadow-sm"
/>
<span class="text-gray-700 font-mono font-bold text-lg">{{ vehicle.licensePlate }}</span>
</div>
</div>
<!-- Stats -->
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="text-center">
<div class="text-2xl font-bold text-gray-900">{{ (vehicle.mileage / 1000).toFixed(1) }}k</div>
<div class="text-sm text-gray-600">km</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-gray-900">{{ vehicle.fuelType.charAt(0) }}</div>
<div class="text-sm text-gray-600">Fuel</div>
</div>
</div>
<!-- Actions -->
<div class="flex space-x-3">
<button class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg transition-colors duration-200">
View Details
</button>
<button class="px-4 py-3 border border-gray-300 hover:bg-gray-50 rounded-lg transition-colors duration-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
</div>
</div>
</div>
</template>
<style scoped>
/* Custom scrollbar for future use */
</style>

View File

@@ -0,0 +1,179 @@
<script setup>
import { computed, ref, onMounted } from 'vue'
import { useAppModeStore } from '@/stores/appModeStore'
import { useGarageStore } from '@/stores/garageStore'
import VehicleCard from './VehicleCard.vue'
import FleetTable from './FleetTable.vue'
const appModeStore = useAppModeStore()
const garageStore = useGarageStore()
// Animation state
const isMounted = ref(false)
// Fetch vehicles on component mount (simulated)
onMounted(() => {
garageStore.fetchVehicles()
// Trigger animation after mount
setTimeout(() => {
isMounted.value = true
}, 100)
})
const stats = computed(() => ({
totalVehicles: garageStore.totalVehicles,
totalMonthlyExpense: garageStore.totalMonthlyExpense,
vehiclesNeedingService: garageStore.vehiclesNeedingService
}))
const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR',
minimumFractionDigits: 0
}).format(amount)
}
</script>
<template>
<div class="space-y-8">
<!-- Header with Stats -->
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-6 border border-blue-100">
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-3xl font-bold text-gray-900">
{{ appModeStore.isPrivateGarage ? 'My Garage' : 'Corporate Fleet' }}
</h1>
<p class="text-gray-600 mt-2">
{{ appModeStore.isPrivateGarage
? 'Your personal vehicle collection and maintenance tracker'
: 'Company-wide vehicle management and cost analytics'
}}
</p>
</div>
<div class="flex items-center space-x-4">
<button
@click="appModeStore.toggleMode"
class="px-4 py-2 bg-white border border-gray-300 rounded-lg font-medium text-gray-700 hover:bg-gray-50 transition-all duration-300 flex items-center active:scale-95"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
Switch to {{ appModeStore.isPrivateGarage ? 'Corporate' : 'Private' }} View
</button>
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div
v-for="(stat, index) in [
{ label: 'Total Vehicles', value: stats.totalVehicles, icon: 'check', color: 'blue' },
{ label: 'Monthly Cost', value: formatCurrency(stats.totalMonthlyExpense), icon: 'currency', color: 'green' },
{ label: 'Need Service', value: stats.vehiclesNeedingService, icon: 'warning', color: 'orange' }
]"
:key="stat.label"
class="bg-white rounded-xl p-5 shadow-sm border border-gray-200 transition-all duration-500 hover:shadow-md hover:-translate-y-1"
:style="{
opacity: isMounted ? 1 : 0,
transform: isMounted ? 'translateY(0)' : 'translateY(20px)',
transitionDelay: `${index * 100}ms`
}"
>
<div class="flex items-center">
<div :class="[`p-3 bg-${stat.color}-100 rounded-lg mr-4`]">
<svg xmlns="http://www.w3.org/2000/svg" :class="[`h-6 w-6 text-${stat.color}-600`]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path v-if="stat.icon === 'check'" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
<path v-if="stat.icon === 'currency'" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<path v-if="stat.icon === 'warning'" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.998-.833-2.732 0L4.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
<div>
<div class="text-2xl font-bold text-gray-900">{{ stat.value }}</div>
<div class="text-gray-600">{{ stat.label }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- Dual-UI Content -->
<div v-if="appModeStore.isPrivateGarage">
<!-- Private Garage: Card Grid with TransitionGroup -->
<div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-900">My Vehicles</h2>
<div class="text-gray-600">
{{ garageStore.vehicles.length }} vehicles in your garage
</div>
</div>
<TransitionGroup
name="stagger-card"
tag="div"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-6"
>
<VehicleCard
v-for="(vehicle, index) in garageStore.vehicles"
:key="vehicle.id"
:vehicle="vehicle"
:style="{
opacity: isMounted ? 1 : 0,
transform: isMounted ? 'translateY(0) scale(1)' : 'translateY(30px) scale(0.95)',
transitionDelay: `${index * 150}ms`
}"
class="transition-all duration-700 ease-out"
/>
</TransitionGroup>
</div>
</div>
<div v-else>
<!-- Corporate Fleet: Table View -->
<FleetTable :vehicles="garageStore.vehicles" />
</div>
<!-- Empty State (if no vehicles) -->
<div v-if="garageStore.vehicles.length === 0" class="text-center py-12">
<div class="text-6xl text-gray-300 mb-4">🚗</div>
<h3 class="text-xl font-bold text-gray-500 mb-2">No vehicles yet</h3>
<p class="text-gray-600 mb-6">Add your first vehicle to get started</p>
<button class="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-all duration-300 hover:scale-105 active:scale-95">
Add First Vehicle
</button>
</div>
</div>
</template>
<style scoped>
/* Staggered card animations */
.stagger-card-move {
transition: transform 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.stagger-card-enter-active,
.stagger-card-leave-active {
transition: all 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.stagger-card-enter-from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
.stagger-card-leave-to {
opacity: 0;
transform: translateY(-30px) scale(0.95);
}
.stagger-card-leave-active {
position: absolute;
}
/* Smooth hover effects */
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
</style>