2026.03.30 front és garázs logika
This commit is contained in:
@@ -13,7 +13,12 @@ const props = defineProps({
|
||||
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'
|
||||
'Warning': 'bg-rose-50 text-rose-700 border border-rose-200',
|
||||
'draft': 'bg-yellow-50 text-yellow-800 border border-yellow-300',
|
||||
'verified': 'bg-green-50 text-green-800 border border-green-300',
|
||||
'active': 'bg-blue-50 text-blue-800 border border-blue-300',
|
||||
'pending': 'bg-gray-50 text-gray-800 border border-gray-300',
|
||||
'incomplete': 'bg-orange-50 text-orange-800 border border-orange-300'
|
||||
}
|
||||
|
||||
const sortedVehicles = computed(() => {
|
||||
@@ -103,7 +108,7 @@ const getCountryFlag = (make) => {
|
||||
</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
|
||||
<span class="mr-2">🔧</span> Data Status
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-8 py-4 text-left text-xs font-semibold text-slate-700 uppercase tracking-wider">
|
||||
@@ -178,15 +183,42 @@ const getCountryFlag = (make) => {
|
||||
</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 class="space-y-2">
|
||||
<!-- Data Status Badge -->
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
:class="['px-3 py-1.5 rounded-full text-xs font-semibold flex items-center', statusColors[vehicle.data_status || vehicle.status] || 'bg-slate-100 text-slate-800 border border-slate-300']"
|
||||
>
|
||||
<span
|
||||
class="w-2 h-2 rounded-full mr-2"
|
||||
:class="{
|
||||
'bg-emerald-500': (vehicle.data_status || vehicle.status) === 'OK' || (vehicle.data_status || vehicle.status) === 'verified',
|
||||
'bg-blue-500': (vehicle.data_status || vehicle.status) === 'Service Due' || (vehicle.data_status || vehicle.status) === 'active',
|
||||
'bg-rose-500': (vehicle.data_status || vehicle.status) === 'Warning',
|
||||
'bg-yellow-500': (vehicle.data_status || vehicle.status) === 'draft',
|
||||
'bg-gray-500': (vehicle.data_status || vehicle.status) === 'pending',
|
||||
'bg-orange-500': (vehicle.data_status || vehicle.status) === 'incomplete'
|
||||
}"
|
||||
></span>
|
||||
{{ vehicle.data_status || vehicle.status }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Profile Completion Mini Progress Bar -->
|
||||
<div v-if="vehicle.profile_completion_percentage !== undefined" class="flex items-center space-x-2">
|
||||
<div class="w-16 bg-gray-200 rounded-full h-1.5">
|
||||
<div
|
||||
class="h-1.5 rounded-full transition-all duration-300"
|
||||
:class="{
|
||||
'bg-yellow-500': (vehicle.profile_completion_percentage || 0) < 50,
|
||||
'bg-blue-500': (vehicle.profile_completion_percentage || 0) >= 50 && (vehicle.profile_completion_percentage || 0) < 80,
|
||||
'bg-green-500': (vehicle.profile_completion_percentage || 0) >= 80
|
||||
}"
|
||||
:style="{ width: (vehicle.profile_completion_percentage || 0) + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-600 font-medium">{{ vehicle.profile_completion_percentage || 0 }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-8 py-5 whitespace-nowrap">
|
||||
|
||||
@@ -15,7 +15,11 @@ const statusColors = {
|
||||
'OK': 'bg-green-100 text-green-800',
|
||||
'Service Due': 'bg-blue-100 text-blue-900',
|
||||
'Warning': 'bg-orange-100 text-orange-800',
|
||||
'draft': 'bg-yellow-100 text-yellow-800'
|
||||
'draft': 'bg-yellow-100 text-yellow-800',
|
||||
'verified': 'bg-green-100 text-green-800',
|
||||
'active': 'bg-blue-100 text-blue-800',
|
||||
'pending': 'bg-gray-100 text-gray-800',
|
||||
'incomplete': 'bg-orange-100 text-orange-800'
|
||||
}
|
||||
|
||||
const brandLogoUrl = (make) => {
|
||||
@@ -67,9 +71,9 @@ const getCountryFlag = (make) => {
|
||||
</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']"
|
||||
:class="['px-3 py-1 rounded-full text-xs font-semibold', statusColors[vehicle.data_status || vehicle.status] || 'bg-gray-100 text-gray-800']"
|
||||
>
|
||||
{{ vehicle.status }}
|
||||
{{ vehicle.data_status || vehicle.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,18 +103,32 @@ const getCountryFlag = (make) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Draft Status & Profile Completion -->
|
||||
<div v-if="vehicle.status === 'draft'" class="mb-4">
|
||||
<!-- Profile Completion Progress Bar (shown for all vehicles with <100% completion) -->
|
||||
<div v-if="(vehicle.profile_completion_percentage || 0) < 100" class="mb-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800">DRAFT</span>
|
||||
<span
|
||||
v-if="vehicle.data_status === 'draft'"
|
||||
class="px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800"
|
||||
>
|
||||
DRAFT
|
||||
</span>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs text-gray-600 mb-1">Profile: {{ vehicle.profile_completion_percentage || 0 }}% Complete</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div class="bg-yellow-500 h-2 rounded-full" :style="{ width: (vehicle.profile_completion_percentage || 0) + '%' }"></div>
|
||||
<div
|
||||
class="h-2 rounded-full transition-all duration-500"
|
||||
:class="{
|
||||
'bg-yellow-500': (vehicle.profile_completion_percentage || 0) < 50,
|
||||
'bg-blue-500': (vehicle.profile_completion_percentage || 0) >= 50 && (vehicle.profile_completion_percentage || 0) < 80,
|
||||
'bg-green-500': (vehicle.profile_completion_percentage || 0) >= 80
|
||||
}"
|
||||
:style="{ width: (vehicle.profile_completion_percentage || 0) + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1">Edit vehicle to provide VIN or Catalog ID</p>
|
||||
<p v-if="vehicle.data_status === 'draft'" class="text-xs text-gray-500 mt-1">Edit vehicle to provide VIN or Catalog ID</p>
|
||||
<p v-else class="text-xs text-gray-500 mt-1">Complete your vehicle profile for better service recommendations</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { computed, ref, onMounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import { useGarageStore } from '@/stores/garageStore'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
import VehicleCard from './VehicleCard.vue'
|
||||
import FleetTable from './FleetTable.vue'
|
||||
import api from '@/services/api'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const appModeStore = useAppModeStore()
|
||||
const garageStore = useGarageStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// Animation state
|
||||
const isMounted = ref(false)
|
||||
const organizations = ref([])
|
||||
const activeOrganizationName = ref('')
|
||||
const loadingOrganizations = ref(false)
|
||||
|
||||
// Fetch vehicles on component mount (simulated)
|
||||
onMounted(() => {
|
||||
@@ -21,8 +27,53 @@ onMounted(() => {
|
||||
setTimeout(() => {
|
||||
isMounted.value = true
|
||||
}, 100)
|
||||
|
||||
// Fetch organizations if in fleet mode
|
||||
if (appModeStore.isCorporateFleet) {
|
||||
fetchOrganizations()
|
||||
}
|
||||
})
|
||||
|
||||
// Watch for mode changes
|
||||
watch(() => appModeStore.mode, (newMode) => {
|
||||
if (newMode === 'fleet') {
|
||||
fetchOrganizations()
|
||||
} else {
|
||||
activeOrganizationName.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
// Fetch user's organizations
|
||||
const fetchOrganizations = async () => {
|
||||
if (!authStore.token) return
|
||||
|
||||
try {
|
||||
loadingOrganizations.value = true
|
||||
const response = await api.get('/organizations/my')
|
||||
organizations.value = response.data || []
|
||||
|
||||
// Find active organization name
|
||||
const activeOrgId = authStore.activeOrgId || authStore.userProfile?.active_organization_id
|
||||
if (activeOrgId && organizations.value.length > 0) {
|
||||
// Try to find organization by ID
|
||||
// Note: The current endpoint returns limited data, we may need to enhance it
|
||||
const org = organizations.value.find(o => o.organization_id === parseInt(activeOrgId))
|
||||
if (org) {
|
||||
activeOrganizationName.value = org.display_name || org.name || org.full_name || `Organization #${activeOrgId}`
|
||||
} else {
|
||||
activeOrganizationName.value = 'Corporate Fleet'
|
||||
}
|
||||
} else {
|
||||
activeOrganizationName.value = 'Corporate Fleet'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch organizations:', error)
|
||||
activeOrganizationName.value = 'Corporate Fleet'
|
||||
} finally {
|
||||
loadingOrganizations.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const stats = computed(() => ({
|
||||
totalVehicles: garageStore.totalVehicles,
|
||||
totalMonthlyExpense: garageStore.totalMonthlyExpense,
|
||||
@@ -36,6 +87,30 @@ const formatCurrency = (amount) => {
|
||||
minimumFractionDigits: 0
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
// Computed for header title
|
||||
const headerTitle = computed(() => {
|
||||
if (appModeStore.isPrivateGarage) {
|
||||
return 'Saját Garázs'
|
||||
} else {
|
||||
if (activeOrganizationName.value && activeOrganizationName.value !== 'Corporate Fleet') {
|
||||
return `Céges Flotta: ${activeOrganizationName.value}`
|
||||
}
|
||||
return 'Céges Flotta'
|
||||
}
|
||||
})
|
||||
|
||||
// Computed for header description
|
||||
const headerDescription = computed(() => {
|
||||
if (appModeStore.isPrivateGarage) {
|
||||
return 'Your personal vehicle collection and maintenance tracker'
|
||||
} else {
|
||||
if (activeOrganizationName.value && activeOrganizationName.value !== 'Corporate Fleet') {
|
||||
return `Company-wide vehicle management and cost analytics for ${activeOrganizationName.value}`
|
||||
}
|
||||
return 'Company-wide vehicle management and cost analytics'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,18 +119,29 @@ const formatCurrency = (amount) => {
|
||||
<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>
|
||||
<div class="flex items-center gap-3">
|
||||
<h1 class="text-3xl font-bold text-gray-900">
|
||||
{{ headerTitle }}
|
||||
</h1>
|
||||
<span
|
||||
v-if="appModeStore.isPrivateGarage"
|
||||
class="px-3 py-1 bg-gradient-to-r from-green-500 to-emerald-600 text-white text-xs font-semibold rounded-full shadow-sm"
|
||||
>
|
||||
B2C
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="px-3 py-1 bg-gradient-to-r from-blue-500 to-indigo-600 text-white text-xs font-semibold rounded-full shadow-sm"
|
||||
>
|
||||
B2B
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mt-2">
|
||||
{{ appModeStore.isPrivateGarage
|
||||
? 'Your personal vehicle collection and maintenance tracker'
|
||||
: 'Company-wide vehicle management and cost analytics'
|
||||
}}
|
||||
{{ headerDescription }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button
|
||||
<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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user