Files
service-finder/frontend/src/components/garage/VehicleShowcase.vue
2026-03-26 07:09:44 +00:00

179 lines
7.2 KiB
Vue

<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>