|
|
|
|
@@ -0,0 +1,964 @@
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
|
|
|
import { useGarageStore } from '../../stores/garageStore'
|
|
|
|
|
import { useAuthStore } from '../../stores/authStore'
|
|
|
|
|
import { useAppModeStore } from '../../stores/appModeStore'
|
|
|
|
|
import { catalogApi, organizationApi } from '../../services/api'
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['close', 'success'])
|
|
|
|
|
|
|
|
|
|
// Store instances
|
|
|
|
|
const garageStore = useGarageStore()
|
|
|
|
|
const authStore = useAuthStore()
|
|
|
|
|
const appModeStore = useAppModeStore()
|
|
|
|
|
|
|
|
|
|
// Organization context
|
|
|
|
|
const selectedOrganizationId = ref(null)
|
|
|
|
|
const userOrganizations = ref([])
|
|
|
|
|
const loadingOrganizations = ref(false)
|
|
|
|
|
|
|
|
|
|
// Owner vs Operator toggle
|
|
|
|
|
const ownershipType = ref('owner') // 'owner' or 'operator'
|
|
|
|
|
|
|
|
|
|
// Wizard steps - now 4 steps total (0: Organization, 1: Classification, 2: Catalog, 3: Details)
|
|
|
|
|
const currentStep = ref(0)
|
|
|
|
|
const totalSteps = 4
|
|
|
|
|
|
|
|
|
|
// Check if organization step is needed
|
|
|
|
|
const needsOrganizationSelection = computed(() => {
|
|
|
|
|
// Always show in fleet mode, or if user has multiple organizations
|
|
|
|
|
return isFleetMode.value || (userOrganizations.value && userOrganizations.value.length > 1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Step 0: Organization Selection - Categorized
|
|
|
|
|
const categorizedOrganizations = computed(() => {
|
|
|
|
|
const privateOrgs = []
|
|
|
|
|
const businessOrgs = []
|
|
|
|
|
|
|
|
|
|
if (userOrganizations.value && userOrganizations.value.length > 0) {
|
|
|
|
|
userOrganizations.value.forEach(org => {
|
|
|
|
|
// Check if organization has tax_number or org_type is not 'individual'
|
|
|
|
|
// Note: org_type values: 'individual', 'company', 'non_profit', 'government'
|
|
|
|
|
const isBusiness = org.tax_number || (org.org_type && org.org_type !== 'individual')
|
|
|
|
|
|
|
|
|
|
if (isBusiness) {
|
|
|
|
|
businessOrgs.push({
|
|
|
|
|
id: org.organization_id,
|
|
|
|
|
name: org.display_name || org.name,
|
|
|
|
|
description: org.full_name || 'Céges flotta',
|
|
|
|
|
type: 'business',
|
|
|
|
|
originalOrg: org
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
privateOrgs.push({
|
|
|
|
|
id: org.organization_id,
|
|
|
|
|
name: 'Személyes Garázsom',
|
|
|
|
|
description: 'Személyes járművek',
|
|
|
|
|
type: 'private',
|
|
|
|
|
originalOrg: org
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { privateOrgs, businessOrgs }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Backward compatibility - flat list of all organizations
|
|
|
|
|
const organizationOptions = computed(() => {
|
|
|
|
|
const options = []
|
|
|
|
|
const { privateOrgs, businessOrgs } = categorizedOrganizations.value
|
|
|
|
|
|
|
|
|
|
// Add private organizations first
|
|
|
|
|
privateOrgs.forEach(org => {
|
|
|
|
|
options.push(org)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Add business organizations
|
|
|
|
|
businessOrgs.forEach(org => {
|
|
|
|
|
options.push(org)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return options
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Step 1: Classification
|
|
|
|
|
const vehicleClass = ref('')
|
|
|
|
|
const vehicleClasses = ref([
|
|
|
|
|
{ value: 'passenger_car', label: 'Személygépkocsi', icon: '🚗' },
|
|
|
|
|
{ value: 'motorcycle', label: 'Motorkerékpár', icon: '🏍️' },
|
|
|
|
|
{ value: 'commercial_vehicle', label: 'Tehergépkocsi', icon: '🚚' },
|
|
|
|
|
{ value: 'bus', label: 'Busz', icon: '🚌' },
|
|
|
|
|
{ value: 'special_vehicle', label: 'Különleges jármű', icon: '🚜' }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Step 2: Catalog Auto-Complete
|
|
|
|
|
const make = ref('')
|
|
|
|
|
const model = ref('')
|
|
|
|
|
const generation = ref('')
|
|
|
|
|
const engine = ref('')
|
|
|
|
|
const catalogId = ref(null)
|
|
|
|
|
|
|
|
|
|
// Catalog data
|
|
|
|
|
const makes = ref([])
|
|
|
|
|
const models = ref([])
|
|
|
|
|
const generations = ref([])
|
|
|
|
|
const engines = ref([])
|
|
|
|
|
|
|
|
|
|
// Loading states
|
|
|
|
|
const loadingMakes = ref(false)
|
|
|
|
|
const loadingModels = ref(false)
|
|
|
|
|
const loadingGenerations = ref(false)
|
|
|
|
|
const loadingEngines = ref(false)
|
|
|
|
|
|
|
|
|
|
// Step 3: Unique Details
|
|
|
|
|
const licensePlate = ref('')
|
|
|
|
|
const vin = ref('')
|
|
|
|
|
const currentMileage = ref('')
|
|
|
|
|
const color = ref('')
|
|
|
|
|
|
|
|
|
|
// Form state
|
|
|
|
|
const isLoading = ref(false)
|
|
|
|
|
const error = ref(null)
|
|
|
|
|
const successMessage = ref('')
|
|
|
|
|
|
|
|
|
|
// Progress indicator
|
|
|
|
|
const progressPercentage = ref(0)
|
|
|
|
|
|
|
|
|
|
// UI Mode styling
|
|
|
|
|
const uiMode = computed(() => appModeStore.mode)
|
|
|
|
|
const isFleetMode = computed(() => uiMode.value === 'fleet')
|
|
|
|
|
const isPersonalMode = computed(() => uiMode.value === 'personal')
|
|
|
|
|
|
|
|
|
|
// Computed properties for empty states
|
|
|
|
|
const hasGenerations = computed(() => generations.value && generations.value.length > 0)
|
|
|
|
|
const hasEngines = computed(() => engines.value && engines.value.length > 0)
|
|
|
|
|
const generationsLoaded = computed(() => !loadingGenerations.value)
|
|
|
|
|
const enginesLoaded = computed(() => !loadingEngines.value)
|
|
|
|
|
|
|
|
|
|
// Step validation - handle empty generations/engines gracefully
|
|
|
|
|
const canProceedToNextStep = computed(() => {
|
|
|
|
|
if (currentStep.value === 0) {
|
|
|
|
|
// Organization step: only required if selection is needed
|
|
|
|
|
if (needsOrganizationSelection.value) {
|
|
|
|
|
// Must have a valid organization ID (not null, not undefined)
|
|
|
|
|
return selectedOrganizationId.value != null
|
|
|
|
|
}
|
|
|
|
|
return true // Skip if not needed
|
|
|
|
|
} else if (currentStep.value === 1) {
|
|
|
|
|
return !!vehicleClass.value
|
|
|
|
|
} else if (currentStep.value === 2) {
|
|
|
|
|
// Basic requirements
|
|
|
|
|
if (!make.value || !model.value) return false
|
|
|
|
|
|
|
|
|
|
// If generations are still loading, wait
|
|
|
|
|
if (loadingGenerations.value) return false
|
|
|
|
|
|
|
|
|
|
// Check if generations are available
|
|
|
|
|
if (hasGenerations.value) {
|
|
|
|
|
// If generations exist, require generation selection
|
|
|
|
|
if (!generation.value) return false
|
|
|
|
|
|
|
|
|
|
// If engines are still loading, wait
|
|
|
|
|
if (loadingEngines.value) return false
|
|
|
|
|
|
|
|
|
|
// Check if engines are available for the selected generation
|
|
|
|
|
if (hasEngines.value) {
|
|
|
|
|
// If engines exist, require engine selection
|
|
|
|
|
if (!engine.value) return false
|
|
|
|
|
}
|
|
|
|
|
// If no engines available, generation selection is enough
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// If no generations available, make and model are enough
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Calculate progress based on filled fields
|
|
|
|
|
const calculateProgress = () => {
|
|
|
|
|
let filledFields = 0
|
|
|
|
|
let totalFields = 0
|
|
|
|
|
|
|
|
|
|
// Step 0: Organization (if needed)
|
|
|
|
|
if (needsOrganizationSelection.value) {
|
|
|
|
|
if (selectedOrganizationId.value !== undefined) filledFields++
|
|
|
|
|
totalFields++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 1 fields
|
|
|
|
|
if (vehicleClass.value) filledFields++
|
|
|
|
|
totalFields++
|
|
|
|
|
|
|
|
|
|
// Step 2 fields (weighted more)
|
|
|
|
|
if (make.value) filledFields++
|
|
|
|
|
if (model.value) filledFields++
|
|
|
|
|
if (generation.value) filledFields++
|
|
|
|
|
if (engine.value) filledFields++
|
|
|
|
|
totalFields += 4
|
|
|
|
|
|
|
|
|
|
// Step 3 fields
|
|
|
|
|
if (licensePlate.value) filledFields++
|
|
|
|
|
if (vin.value) filledFields++
|
|
|
|
|
if (currentMileage.value) filledFields++
|
|
|
|
|
totalFields += 3
|
|
|
|
|
|
|
|
|
|
progressPercentage.value = Math.round((filledFields / totalFields) * 100)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Watch all form fields for progress updates
|
|
|
|
|
watch([selectedOrganizationId, vehicleClass, make, model, generation, engine, licensePlate, vin, currentMileage], () => {
|
|
|
|
|
calculateProgress()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Fetch organizations and makes on component mount
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await fetchUserOrganizations()
|
|
|
|
|
await fetchMakes()
|
|
|
|
|
calculateProgress()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Fetch user's organizations
|
|
|
|
|
async function fetchUserOrganizations() {
|
|
|
|
|
if (!authStore.isLoggedIn) return
|
|
|
|
|
|
|
|
|
|
loadingOrganizations.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await organizationApi.getMyOrganizations()
|
|
|
|
|
userOrganizations.value = data
|
|
|
|
|
|
|
|
|
|
// If user has only one organization and is in fleet mode, auto-select it
|
|
|
|
|
if (isFleetMode.value && data.length === 1) {
|
|
|
|
|
selectedOrganizationId.value = data[0].organization_id
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error fetching organizations:', err)
|
|
|
|
|
// Non-critical error, continue without organizations
|
|
|
|
|
} finally {
|
|
|
|
|
loadingOrganizations.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch makes from catalog API
|
|
|
|
|
async function fetchMakes() {
|
|
|
|
|
loadingMakes.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await catalogApi.getMakes()
|
|
|
|
|
makes.value = data
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error fetching makes:', err)
|
|
|
|
|
error.value = 'Could not load vehicle makes. Please try again.'
|
|
|
|
|
} finally {
|
|
|
|
|
loadingMakes.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch models when make is selected
|
|
|
|
|
watch(make, async (newMake) => {
|
|
|
|
|
if (newMake) {
|
|
|
|
|
loadingModels.value = true
|
|
|
|
|
try {
|
|
|
|
|
// Pass vehicle_class to filter models by selected vehicle class
|
|
|
|
|
const data = await catalogApi.getModels(newMake, vehicleClass.value)
|
|
|
|
|
models.value = data
|
|
|
|
|
model.value = ''
|
|
|
|
|
generation.value = ''
|
|
|
|
|
engine.value = ''
|
|
|
|
|
catalogId.value = null
|
|
|
|
|
generations.value = []
|
|
|
|
|
engines.value = []
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error fetching models:', err)
|
|
|
|
|
error.value = 'Could not load models for this make.'
|
|
|
|
|
} finally {
|
|
|
|
|
loadingModels.value = false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
models.value = []
|
|
|
|
|
generations.value = []
|
|
|
|
|
engines.value = []
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Fetch generations when model is selected
|
|
|
|
|
watch(model, async (newModel) => {
|
|
|
|
|
if (newModel && make.value) {
|
|
|
|
|
loadingGenerations.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await catalogApi.getGenerations(make.value, newModel)
|
|
|
|
|
generations.value = data
|
|
|
|
|
generation.value = ''
|
|
|
|
|
engine.value = ''
|
|
|
|
|
catalogId.value = null
|
|
|
|
|
engines.value = []
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error fetching generations:', err)
|
|
|
|
|
error.value = 'Could not load generations for this model.'
|
|
|
|
|
} finally {
|
|
|
|
|
loadingGenerations.value = false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
generations.value = []
|
|
|
|
|
engines.value = []
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Fetch engines when generation is selected
|
|
|
|
|
watch(generation, async (newGen) => {
|
|
|
|
|
if (newGen && make.value && model.value) {
|
|
|
|
|
loadingEngines.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = await catalogApi.getEngines(make.value, model.value, newGen)
|
|
|
|
|
engines.value = data
|
|
|
|
|
engine.value = ''
|
|
|
|
|
catalogId.value = null
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error fetching engines:', err)
|
|
|
|
|
error.value = 'Could not load engines for this generation.'
|
|
|
|
|
} finally {
|
|
|
|
|
loadingEngines.value = false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
engines.value = []
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Set catalog ID when engine is selected
|
|
|
|
|
watch(engine, (newEngine) => {
|
|
|
|
|
if (newEngine) {
|
|
|
|
|
const selectedEngine = engines.value.find(e => e.variant === newEngine || e.id === newEngine)
|
|
|
|
|
if (selectedEngine) {
|
|
|
|
|
catalogId.value = selectedEngine.id
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
catalogId.value = null
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Navigation functions
|
|
|
|
|
function nextStep() {
|
|
|
|
|
if (currentStep.value < totalSteps) {
|
|
|
|
|
currentStep.value++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function prevStep() {
|
|
|
|
|
if (currentStep.value > 1) {
|
|
|
|
|
currentStep.value--
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Form submission
|
|
|
|
|
async function handleSubmit() {
|
|
|
|
|
error.value = null
|
|
|
|
|
successMessage.value = ''
|
|
|
|
|
isLoading.value = true
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Determine organization ID: use selected organization, or auth store's active org, or null for personal
|
|
|
|
|
const organizationId = selectedOrganizationId.value !== null
|
|
|
|
|
? selectedOrganizationId.value
|
|
|
|
|
: (isFleetMode.value ? authStore.activeOrgId : null)
|
|
|
|
|
|
|
|
|
|
// Determine owner and operator org IDs based on ownership type
|
|
|
|
|
let ownerOrgId = null
|
|
|
|
|
let operatorOrgId = null
|
|
|
|
|
|
|
|
|
|
if (organizationId !== null) {
|
|
|
|
|
if (ownershipType.value === 'owner') {
|
|
|
|
|
// Owner: both owner and operator are the selected organization
|
|
|
|
|
ownerOrgId = organizationId
|
|
|
|
|
operatorOrgId = organizationId
|
|
|
|
|
} else {
|
|
|
|
|
// Operator: only operator is set, owner remains null
|
|
|
|
|
operatorOrgId = organizationId
|
|
|
|
|
// ownerOrgId stays null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare thick asset payload
|
|
|
|
|
const vehicleData = {
|
|
|
|
|
// Core identification
|
|
|
|
|
vin: vin.value || null,
|
|
|
|
|
licensePlate: licensePlate.value,
|
|
|
|
|
catalogId: catalogId.value,
|
|
|
|
|
organizationId: organizationId,
|
|
|
|
|
|
|
|
|
|
// Ownership fields
|
|
|
|
|
owner_org_id: ownerOrgId,
|
|
|
|
|
operator_org_id: operatorOrgId,
|
|
|
|
|
|
|
|
|
|
// Thick Asset fields
|
|
|
|
|
brand: make.value,
|
|
|
|
|
model: model.value,
|
|
|
|
|
vehicleClass: vehicleClass.value,
|
|
|
|
|
fuelType: getFuelTypeFromEngine(),
|
|
|
|
|
year: getYearFromGeneration(),
|
|
|
|
|
currentMileage: parseInt(currentMileage.value) || 0,
|
|
|
|
|
color: color.value,
|
|
|
|
|
|
|
|
|
|
// Additional metadata
|
|
|
|
|
status: 'draft',
|
|
|
|
|
generation: generation.value,
|
|
|
|
|
engine: engine.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call the updated garage store addVehicle action
|
|
|
|
|
const result = await garageStore.addVehicle(vehicleData)
|
|
|
|
|
|
|
|
|
|
successMessage.value = 'Vehicle registered successfully! The backend will now sync catalog data and calculate profile completion.'
|
|
|
|
|
|
|
|
|
|
// Emit success event
|
|
|
|
|
emit('success', result)
|
|
|
|
|
|
|
|
|
|
// Reset form after a delay
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
resetForm()
|
|
|
|
|
emit('close')
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error registering vehicle:', err)
|
|
|
|
|
error.value = err.message || 'An unknown error occurred'
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper functions
|
|
|
|
|
function getFuelTypeFromEngine() {
|
|
|
|
|
if (!engine.value || !engines.value.length) return null
|
|
|
|
|
const selected = engines.value.find(e => e.variant === engine.value || e.id === engine.value)
|
|
|
|
|
return selected?.fuel_type || null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getYearFromGeneration() {
|
|
|
|
|
// Extract year from generation string (e.g., "2015-2019" or "2020")
|
|
|
|
|
if (!generation.value) return null
|
|
|
|
|
const match = generation.value.match(/\d{4}/)
|
|
|
|
|
return match ? parseInt(match[0]) : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetForm() {
|
|
|
|
|
currentStep.value = needsOrganizationSelection.value ? 0 : 1
|
|
|
|
|
selectedOrganizationId.value = null
|
|
|
|
|
ownershipType.value = 'owner' // Reset to default
|
|
|
|
|
vehicleClass.value = ''
|
|
|
|
|
make.value = ''
|
|
|
|
|
model.value = ''
|
|
|
|
|
generation.value = ''
|
|
|
|
|
engine.value = ''
|
|
|
|
|
catalogId.value = null
|
|
|
|
|
licensePlate.value = ''
|
|
|
|
|
vin.value = ''
|
|
|
|
|
currentMileage.value = ''
|
|
|
|
|
color.value = ''
|
|
|
|
|
error.value = null
|
|
|
|
|
successMessage.value = ''
|
|
|
|
|
progressPercentage.value = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeModal() {
|
|
|
|
|
emit('close')
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<!-- Modal Backdrop -->
|
|
|
|
|
<div class="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black bg-opacity-50" @click.self="closeModal">
|
|
|
|
|
<!-- Modal Container -->
|
|
|
|
|
<div class="w-full max-w-2xl overflow-hidden bg-white rounded-xl shadow-2xl" :class="isFleetMode ? 'border-l-4 border-blue-600' : 'border-l-4 border-green-600'">
|
|
|
|
|
<!-- Modal Header -->
|
|
|
|
|
<div class="p-6 border-b border-gray-200" :class="isFleetMode ? 'bg-blue-50' : 'bg-green-50'">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="text-xl font-bold text-gray-900">
|
|
|
|
|
Smart Vehicle Registration
|
|
|
|
|
</h2>
|
|
|
|
|
<p class="text-sm mt-1 text-gray-600">
|
|
|
|
|
{{ isFleetMode ? 'Add a new vehicle to your fleet' : 'Register your vehicle in just 3 easy steps!' }}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button @click="closeModal" class="p-2 rounded-full text-gray-500 hover:text-gray-700 hover:bg-gray-100">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Progress Indicator -->
|
|
|
|
|
<div class="mt-6">
|
|
|
|
|
<div class="flex items-center justify-between mb-2">
|
|
|
|
|
<span class="text-sm font-medium text-gray-700">Progress</span>
|
|
|
|
|
<span class="text-sm font-bold" :class="isFleetMode ? 'text-blue-600' : 'text-green-600'">{{ progressPercentage }}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="h-2 rounded-full overflow-hidden bg-gray-200">
|
|
|
|
|
<div
|
|
|
|
|
class="h-full transition-all duration-500"
|
|
|
|
|
:class="isFleetMode ? 'bg-blue-600' : 'bg-green-600'"
|
|
|
|
|
:style="{ width: `${progressPercentage}%` }"
|
|
|
|
|
></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Step Indicators -->
|
|
|
|
|
<div class="flex justify-between mt-4">
|
|
|
|
|
<div
|
|
|
|
|
v-for="step in totalSteps"
|
|
|
|
|
:key="step"
|
|
|
|
|
class="flex flex-col items-center"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300"
|
|
|
|
|
:class="[
|
|
|
|
|
step === currentStep
|
|
|
|
|
? (isFleetMode ? 'bg-blue-600 text-white ring-2 ring-blue-300' : 'bg-green-600 text-white ring-2 ring-green-300')
|
|
|
|
|
: step < currentStep
|
|
|
|
|
? (isFleetMode ? 'bg-blue-100 text-blue-700' : 'bg-green-100 text-green-700')
|
|
|
|
|
: 'bg-gray-100 text-gray-400'
|
|
|
|
|
]"
|
|
|
|
|
>
|
|
|
|
|
{{ step }}
|
|
|
|
|
</div>
|
|
|
|
|
<span class="text-xs mt-1 font-medium" :class="[
|
|
|
|
|
step === currentStep
|
|
|
|
|
? (isFleetMode ? 'text-blue-700' : 'text-green-700')
|
|
|
|
|
: step < currentStep
|
|
|
|
|
? (isFleetMode ? 'text-blue-600' : 'text-green-600')
|
|
|
|
|
: 'text-gray-500'
|
|
|
|
|
]">
|
|
|
|
|
{{ step === 0 ? 'Context' : step === 1 ? 'Classification' : step === 2 ? 'Catalog' : 'Details' }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Modal Body -->
|
|
|
|
|
<div class="p-6 max-h-[60vh] overflow-y-auto">
|
|
|
|
|
<!-- Step 0: Organization Selection (if needed) -->
|
|
|
|
|
<div v-if="currentStep === 0 && needsOrganizationSelection">
|
|
|
|
|
<h3 class="text-lg font-semibold mb-4 text-gray-800">1. lépés: Szervezet kiválasztása</h3>
|
|
|
|
|
<p class="text-sm mb-6 text-gray-600">
|
|
|
|
|
Válaszd ki, hogy melyik szervezethez szeretnéd regisztrálni a járművet. A személyes garázsodhoz vagy egy céges flottához.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-6">
|
|
|
|
|
<!-- Private Organizations (Személyes Garázsom) -->
|
|
|
|
|
<div v-if="categorizedOrganizations.privateOrgs.length > 0">
|
|
|
|
|
<h4 class="text-sm font-semibold text-gray-700 mb-3">Személyes Garázsom</h4>
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
<div
|
|
|
|
|
v-for="org in categorizedOrganizations.privateOrgs"
|
|
|
|
|
:key="org.id"
|
|
|
|
|
@click="selectedOrganizationId = org.id"
|
|
|
|
|
class="p-4 rounded-lg border-2 transition-all duration-200 cursor-pointer"
|
|
|
|
|
:class="selectedOrganizationId === org.id
|
|
|
|
|
? (isFleetMode ? 'border-blue-500 bg-blue-50' : 'border-green-500 bg-green-50')
|
|
|
|
|
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50'"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<div class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center mr-4"
|
|
|
|
|
:class="selectedOrganizationId === org.id
|
|
|
|
|
? (isFleetMode ? 'bg-blue-100 text-blue-600' : 'bg-green-100 text-green-600')
|
|
|
|
|
: 'bg-gray-100 text-gray-500'">
|
|
|
|
|
<span>🏠</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-grow">
|
|
|
|
|
<h4 class="font-medium text-gray-900">{{ org.name }}</h4>
|
|
|
|
|
<p class="text-sm text-gray-600">{{ org.description }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="selectedOrganizationId === org.id" class="text-blue-600">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Business Organizations (Céges Flották) -->
|
|
|
|
|
<div v-if="categorizedOrganizations.businessOrgs.length > 0">
|
|
|
|
|
<h4 class="text-sm font-semibold text-gray-700 mb-3">Céges Flották</h4>
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
<div
|
|
|
|
|
v-for="org in categorizedOrganizations.businessOrgs"
|
|
|
|
|
:key="org.id"
|
|
|
|
|
@click="selectedOrganizationId = org.id"
|
|
|
|
|
class="p-4 rounded-lg border-2 transition-all duration-200 cursor-pointer"
|
|
|
|
|
:class="selectedOrganizationId === org.id
|
|
|
|
|
? (isFleetMode ? 'border-blue-500 bg-blue-50' : 'border-green-500 bg-green-50')
|
|
|
|
|
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50'"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<div class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center mr-4"
|
|
|
|
|
:class="selectedOrganizationId === org.id
|
|
|
|
|
? (isFleetMode ? 'bg-blue-100 text-blue-600' : 'bg-green-100 text-green-600')
|
|
|
|
|
: 'bg-gray-100 text-gray-500'">
|
|
|
|
|
<span>🏢</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-grow">
|
|
|
|
|
<h4 class="font-medium text-gray-900">{{ org.name }}</h4>
|
|
|
|
|
<p class="text-sm text-gray-600">{{ org.description }}</p>
|
|
|
|
|
<p v-if="org.originalOrg.tax_number" class="text-xs text-gray-500 mt-1">
|
|
|
|
|
Adószám: {{ org.originalOrg.tax_number }}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="selectedOrganizationId === org.id" class="text-blue-600">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- No organizations message -->
|
|
|
|
|
<div v-if="!loadingOrganizations && categorizedOrganizations.privateOrgs.length === 0 && categorizedOrganizations.businessOrgs.length === 0" class="text-center p-6 border border-dashed border-gray-300 rounded-lg">
|
|
|
|
|
<p class="text-gray-600">Nincs elérhető szervezet. Először hozz létre egy személyes garázst vagy céges flottát a profilodban.</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Owner vs Operator Toggle (shown when organization is selected) -->
|
|
|
|
|
<div v-if="selectedOrganizationId !== null" class="mt-6 pt-6 border-t border-gray-200">
|
|
|
|
|
<h4 class="text-sm font-semibold text-gray-700 mb-3">Tulajdonosi státusz</h4>
|
|
|
|
|
<p class="text-sm text-gray-600 mb-4">Válaszd ki, hogy milyen minőségben vagy kapcsolatban a járművel:</p>
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
<div
|
|
|
|
|
@click="ownershipType = 'owner'"
|
|
|
|
|
class="p-4 rounded-lg border-2 transition-all duration-200 cursor-pointer"
|
|
|
|
|
:class="ownershipType === 'owner'
|
|
|
|
|
? (isFleetMode ? 'border-blue-500 bg-blue-50' : 'border-green-500 bg-green-50')
|
|
|
|
|
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50'"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<div class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center mr-4"
|
|
|
|
|
:class="ownershipType === 'owner'
|
|
|
|
|
? (isFleetMode ? 'bg-blue-100 text-blue-600' : 'bg-green-100 text-green-600')
|
|
|
|
|
: 'bg-gray-100 text-gray-500'">
|
|
|
|
|
<span>👑</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-grow">
|
|
|
|
|
<h4 class="font-medium text-gray-900">Tulajdonos vagyok</h4>
|
|
|
|
|
<p class="text-sm text-gray-600">A jármű a tulajdonom, én fizetem a költségeket és döntök a szervizelésről.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="ownershipType === 'owner'" class="text-blue-600">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
@click="ownershipType = 'operator'"
|
|
|
|
|
class="p-4 rounded-lg border-2 transition-all duration-200 cursor-pointer"
|
|
|
|
|
:class="ownershipType === 'operator'
|
|
|
|
|
? (isFleetMode ? 'border-blue-500 bg-blue-50' : 'border-green-500 bg-green-50')
|
|
|
|
|
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50'"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<div class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center mr-4"
|
|
|
|
|
:class="ownershipType === 'operator'
|
|
|
|
|
? (isFleetMode ? 'bg-blue-100 text-blue-600' : 'bg-green-100 text-green-600')
|
|
|
|
|
: 'bg-gray-100 text-gray-500'">
|
|
|
|
|
<span>🚗</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-grow">
|
|
|
|
|
<h4 class="font-medium text-gray-900">Csak üzembentartó/használó vagyok</h4>
|
|
|
|
|
<p class="text-sm text-gray-600">A járművet használom, de nem én vagyok a tulajdonos (pl. céges autó, lízing).</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="ownershipType === 'operator'" class="text-blue-600">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mt-4 text-sm text-gray-600">
|
|
|
|
|
<p v-if="ownershipType === 'owner'">
|
|
|
|
|
<strong>Hatás:</strong> A kiválasztott szervezet lesz a tulajdonos (<code>owner_org_id</code>) és az üzembentartó (<code>operator_org_id</code>) is.
|
|
|
|
|
</p>
|
|
|
|
|
<p v-else>
|
|
|
|
|
<strong>Hatás:</strong> A kiválasztott szervezet lesz csak az üzembentartó (<code>operator_org_id</code>). A tulajdonos mező üres marad (<code>owner_org_id = null</code>).
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="loadingOrganizations" class="mt-4 text-center text-gray-500">
|
|
|
|
|
Szervezetek betöltése...
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Step 1: Classification -->
|
|
|
|
|
<div v-else-if="currentStep === 1">
|
|
|
|
|
<h3 class="text-lg font-semibold mb-4 text-gray-800">Step {{ needsOrganizationSelection ? '2' : '1' }}: Vehicle Classification</h3>
|
|
|
|
|
<p class="text-sm mb-6 text-gray-600">
|
|
|
|
|
Select the type of vehicle you want to register. This helps us provide relevant catalog data.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
|
|
|
<button
|
|
|
|
|
v-for="cls in vehicleClasses"
|
|
|
|
|
:key="cls.value"
|
|
|
|
|
@click="vehicleClass = cls.value"
|
|
|
|
|
class="p-4 rounded-lg border-2 transition-all duration-200 flex flex-col items-center justify-center"
|
|
|
|
|
:class="vehicleClass === cls.value
|
|
|
|
|
? (isFleetMode ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-green-500 bg-green-50 text-green-700')
|
|
|
|
|
: 'border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50'"
|
|
|
|
|
>
|
|
|
|
|
<span class="text-2xl mb-2">{{ cls.icon }}</span>
|
|
|
|
|
<span class="font-medium text-center">{{ cls.label }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Step 2: Catalog Auto-Complete -->
|
|
|
|
|
<div v-else-if="currentStep === 2">
|
|
|
|
|
<h3 class="text-lg font-semibold mb-4 text-gray-800">Step {{ needsOrganizationSelection ? '3' : '2' }}: Vehicle Catalog</h3>
|
|
|
|
|
<p class="text-sm mb-6 text-gray-600">
|
|
|
|
|
Select your vehicle from our catalog. Start by choosing the make, then model, generation, and engine.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-6">
|
|
|
|
|
<!-- Make Selection -->
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">Make (Brand)</label>
|
|
|
|
|
<select
|
|
|
|
|
v-model="make"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
|
|
|
>
|
|
|
|
|
<option value="">Select a make</option>
|
|
|
|
|
<option v-for="m in makes" :key="m" :value="m">{{ m }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Model Selection -->
|
|
|
|
|
<div v-if="make">
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">Model</label>
|
|
|
|
|
<select
|
|
|
|
|
v-model="model"
|
|
|
|
|
:disabled="!make || loadingModels"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 disabled:opacity-50"
|
|
|
|
|
>
|
|
|
|
|
<option value="">Select a model</option>
|
|
|
|
|
<option v-for="m in models" :key="m" :value="m">{{ m }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div v-if="loadingModels" class="text-sm text-gray-500 mt-1">Loading models...</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Generation Selection -->
|
|
|
|
|
<div v-if="model">
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">Generation</label>
|
|
|
|
|
<select
|
|
|
|
|
v-model="generation"
|
|
|
|
|
:disabled="!model || loadingGenerations || !hasGenerations"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 disabled:opacity-50"
|
|
|
|
|
>
|
|
|
|
|
<option value="">Select a generation</option>
|
|
|
|
|
<option v-for="g in generations" :key="g" :value="g">{{ g }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div v-if="loadingGenerations" class="text-sm text-gray-500 mt-1">Loading generations...</div>
|
|
|
|
|
<div v-if="generationsLoaded && !hasGenerations" class="text-sm text-amber-600 mt-1 p-2 bg-amber-50 rounded border border-amber-200">
|
|
|
|
|
ⓘ No generation data available for this model. You can proceed without selecting a generation.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Engine Selection -->
|
|
|
|
|
<div v-if="generation || (model && !hasGenerations)">
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">Engine Variant</label>
|
|
|
|
|
<select
|
|
|
|
|
v-model="engine"
|
|
|
|
|
:disabled="(!generation && hasGenerations) || loadingEngines || !hasEngines"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 disabled:opacity-50"
|
|
|
|
|
>
|
|
|
|
|
<option value="">Select an engine</option>
|
|
|
|
|
<option v-for="e in engines" :key="e.id || e.variant" :value="e.variant">{{ e.variant }} ({{ e.fuel_type }})</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div v-if="loadingEngines" class="text-sm text-gray-500 mt-1">Loading engines...</div>
|
|
|
|
|
<div v-if="enginesLoaded && !hasEngines" class="text-sm text-amber-600 mt-1 p-2 bg-amber-50 rounded border border-amber-200">
|
|
|
|
|
ⓘ No engine data available for this generation. You can proceed without selecting an engine.
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="catalogId" class="text-xs text-green-600 mt-1">Catalog ID: {{ catalogId }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Step 3: Unique Details -->
|
|
|
|
|
<div v-else-if="currentStep === 3">
|
|
|
|
|
<h3 class="text-lg font-semibold mb-4 text-gray-800">Step {{ needsOrganizationSelection ? '4' : '3' }}: Vehicle Details</h3>
|
|
|
|
|
<p class="text-sm mb-6 text-gray-600">
|
|
|
|
|
Provide the unique details for your vehicle.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-6">
|
|
|
|
|
<!-- License Plate -->
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">License Plate <span class="text-red-500">*</span></label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="licensePlate"
|
|
|
|
|
type="text"
|
|
|
|
|
required
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
|
|
|
placeholder="ABC-123"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- VIN (Optional) -->
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">VIN (Opcionális)</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="vin"
|
|
|
|
|
type="text"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
|
|
|
placeholder="1HGCM82633A123456"
|
|
|
|
|
/>
|
|
|
|
|
<p class="text-xs text-gray-500 mt-1">Járműazonosító szám (17 karakter)</p>
|
|
|
|
|
|
|
|
|
|
<!-- Grace Period Warning -->
|
|
|
|
|
<div v-if="!vin" class="mt-3 p-3 rounded-lg bg-amber-50 border border-amber-200">
|
|
|
|
|
<div class="flex items-start">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-amber-600 mr-2 flex-shrink-0 mt-0.5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
<div>
|
|
|
|
|
<p class="text-sm font-medium text-amber-800">Figyelem: Átmeneti használat</p>
|
|
|
|
|
<p class="text-xs text-amber-700 mt-1">
|
|
|
|
|
Az alvázszám (VIN) megadása nélkül a jármű <strong>14 napig</strong>, vagy maximum <strong>10 költség rögzítéséig</strong> használható.
|
|
|
|
|
Ezt követően a rendszer zárolja a további műveleteket, amíg meg nem adod az alvázszámot.
|
|
|
|
|
</p>
|
|
|
|
|
<p class="text-xs text-amber-700 mt-1">
|
|
|
|
|
Ajánlott az alvázszám megadása a teljes funkcionalitás érdekében.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Current Mileage -->
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">Current Mileage (km)</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="currentMileage"
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
|
|
|
placeholder="150000"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Color -->
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium mb-2 text-gray-700">Color (Optional)</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="color"
|
|
|
|
|
type="text"
|
|
|
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 text-gray-800 focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
|
|
|
placeholder="Red, Black, Silver, etc."
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Error and Success Messages -->
|
|
|
|
|
<div v-if="error" class="mt-6 p-4 rounded-lg bg-red-50 border border-red-200">
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span class="text-red-700 font-medium">Error: {{ error }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="successMessage" class="mt-6 p-4 rounded-lg bg-green-50 border border-green-200">
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span class="text-green-700 font-medium">{{ successMessage }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Modal Footer -->
|
|
|
|
|
<div class="p-6 border-t border-gray-200 bg-gray-50">
|
|
|
|
|
<div class="flex justify-between">
|
|
|
|
|
<button
|
|
|
|
|
v-if="currentStep > 1"
|
|
|
|
|
@click="prevStep"
|
|
|
|
|
class="px-5 py-2.5 rounded-lg border border-gray-300 text-gray-700 font-medium hover:bg-gray-50 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
Previous
|
|
|
|
|
</button>
|
|
|
|
|
<div v-else></div>
|
|
|
|
|
|
|
|
|
|
<div class="flex space-x-3">
|
|
|
|
|
<button
|
|
|
|
|
@click="closeModal"
|
|
|
|
|
class="px-5 py-2.5 rounded-lg border border-gray-300 text-gray-700 font-medium hover:bg-gray-50 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
v-if="currentStep < totalSteps"
|
|
|
|
|
@click="nextStep"
|
|
|
|
|
:disabled="!canProceedToNextStep"
|
|
|
|
|
class="px-5 py-2.5 rounded-lg font-medium transition-colors"
|
|
|
|
|
:class="isFleetMode
|
|
|
|
|
? (canProceedToNextStep ? 'bg-blue-600 text-white hover:bg-blue-700' : 'bg-blue-300 text-white cursor-not-allowed')
|
|
|
|
|
: (canProceedToNextStep ? 'bg-green-600 text-white hover:bg-green-700' : 'bg-green-300 text-white cursor-not-allowed')"
|
|
|
|
|
>
|
|
|
|
|
Next
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
v-else
|
|
|
|
|
@click="handleSubmit"
|
|
|
|
|
:disabled="isLoading || !licensePlate"
|
|
|
|
|
class="px-5 py-2.5 rounded-lg font-medium transition-colors"
|
|
|
|
|
:class="isFleetMode
|
|
|
|
|
? (!isLoading && licensePlate ? 'bg-blue-600 text-white hover:bg-blue-700' : 'bg-blue-300 text-white cursor-not-allowed')
|
|
|
|
|
: (!isLoading && licensePlate ? 'bg-green-600 text-white hover:bg-green-700' : 'bg-green-300 text-white cursor-not-allowed')"
|
|
|
|
|
>
|
|
|
|
|
<span v-if="isLoading">
|
|
|
|
|
<svg class="animate-spin h-5 w-5 text-white inline-block mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
|
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
|
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
Registering...
|
|
|
|
|
</span>
|
|
|
|
|
<span v-else>Register Vehicle</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* Custom scrollbar for modal body */
|
|
|
|
|
.max-h-\[60vh\]::-webkit-scrollbar {
|
|
|
|
|
width: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.max-h-\[60vh\]::-webkit-scrollbar-track {
|
|
|
|
|
background: #f1f1f1;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.max-h-\[60vh\]::-webkit-scrollbar-thumb {
|
|
|
|
|
background: #c1c1c1;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.max-h-\[60vh\]::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
background: #a1a1a1;
|
|
|
|
|
}
|
|
|
|
|
</style>
|