179 lines
7.2 KiB
Vue
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> |