Initial commit from monorepo
This commit is contained in:
184
app/components/OrderTimeline.vue
Normal file
184
app/components/OrderTimeline.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="space-y-0">
|
||||
<div
|
||||
v-for="(stage, index) in stages"
|
||||
:key="stage.uuid"
|
||||
class="relative"
|
||||
>
|
||||
<!-- Timeline connector -->
|
||||
<div
|
||||
v-if="index < stages.length - 1"
|
||||
class="absolute left-4 top-8 w-0.5 h-16 bg-base-300"
|
||||
:class="{ 'bg-success': isStageCompleted(stage) }"
|
||||
></div>
|
||||
|
||||
<!-- Stage content -->
|
||||
<div class="flex items-start space-x-4 pb-8">
|
||||
<!-- Stage icon -->
|
||||
<div
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 text-xs font-bold"
|
||||
:class="getStageIconClass(stage)"
|
||||
>
|
||||
{{ getStageIcon(stage) }}
|
||||
</div>
|
||||
|
||||
<!-- Stage details -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-medium text-base-content">{{ stage.name }}</h3>
|
||||
<span
|
||||
:class="getStageStatusClass(stage)"
|
||||
class="badge badge-sm font-semibold"
|
||||
>
|
||||
{{ getStageStatusText(stage) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Stage info -->
|
||||
<div class="mt-2 space-y-2">
|
||||
<!-- Location info -->
|
||||
<div class="text-sm text-base-content/70">
|
||||
<span v-if="stage.stageType === 'transport'">
|
||||
<i class="fas fa-route mr-1"></i>
|
||||
{{ stage.sourceLocationName }} → {{ stage.destinationLocationName }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<i class="fas fa-map-marker-alt mr-1"></i>
|
||||
{{ stage.locationName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Company info -->
|
||||
<div v-if="stage.selectedCompany" class="text-sm text-base-content/70">
|
||||
<i class="fas fa-building mr-1"></i>
|
||||
{{ stage.selectedCompany.name }} ({{ stage.selectedCompany.countryCode }})
|
||||
</div>
|
||||
|
||||
<!-- Transport type -->
|
||||
<div v-if="stage.stageType === 'transport'" class="text-sm text-base-content/70">
|
||||
<i class="fas fa-truck mr-1"></i>
|
||||
{{ getTransportTypeText(stage.transportType) }}
|
||||
</div>
|
||||
|
||||
<!-- Trips progress -->
|
||||
<div v-if="stage.trips?.length > 0" class="mt-3">
|
||||
<div class="flex items-center justify-between text-sm text-base-content/70 mb-1">
|
||||
<span>Trips:</span>
|
||||
<span>{{ getCompletedTrips(stage) }}/{{ stage.trips.length }}</span>
|
||||
</div>
|
||||
<div class="w-full bg-base-200 rounded-full h-2">
|
||||
<div
|
||||
class="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
:style="{ width: `${getTripProgress(stage)}%` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Company breakdown -->
|
||||
<div class="mt-2 text-xs text-base-content/60">
|
||||
<div v-for="companyGroup in getCompanyGroups(stage.trips)" :key="companyGroup.name">
|
||||
{{ companyGroup.name }}: {{ companyGroup.completed }}/{{ companyGroup.total }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const props = defineProps({
|
||||
stages: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const getStageIcon = (stage) => {
|
||||
if (stage.stageType === 'service') return 'C'
|
||||
|
||||
switch (stage.transportType) {
|
||||
case 'auto': return 'A'
|
||||
case 'sea': return 'M'
|
||||
case 'rail': return 'R'
|
||||
case 'air': return 'A'
|
||||
default: return 'A'
|
||||
}
|
||||
}
|
||||
|
||||
const getStageIconClass = (stage) => {
|
||||
const baseClass = 'border-2'
|
||||
|
||||
if (isStageCompleted(stage)) {
|
||||
return `${baseClass} bg-success/10 border-success text-success`
|
||||
} else if (stage.status === 'in_progress') {
|
||||
return `${baseClass} bg-primary/10 border-primary text-primary`
|
||||
} else {
|
||||
return `${baseClass} bg-base-200 border-base-300 text-base-content/60`
|
||||
}
|
||||
}
|
||||
|
||||
const getStageStatusClass = (stage) => {
|
||||
const classes = {
|
||||
pending: 'badge-ghost',
|
||||
in_progress: 'badge-info',
|
||||
completed: 'badge-success',
|
||||
cancelled: 'badge-error'
|
||||
}
|
||||
return classes[stage.status] || 'badge-ghost'
|
||||
}
|
||||
|
||||
const getStageStatusText = (stage) => {
|
||||
const texts = {
|
||||
pending: 'Pending',
|
||||
in_progress: 'In progress',
|
||||
completed: 'Completed',
|
||||
cancelled: 'Cancelled'
|
||||
}
|
||||
return texts[stage.status] || stage.status
|
||||
}
|
||||
|
||||
const getTransportTypeText = (transportType) => {
|
||||
const texts = {
|
||||
auto: 'Auto transport',
|
||||
rail: 'Rail transport',
|
||||
sea: 'Sea transport',
|
||||
air: 'Air transport'
|
||||
}
|
||||
return texts[transportType] || transportType
|
||||
}
|
||||
|
||||
const isStageCompleted = (stage) => {
|
||||
return stage.status === 'completed'
|
||||
}
|
||||
|
||||
const getCompletedTrips = (stage) => {
|
||||
if (!stage.trips?.length) return 0
|
||||
return stage.trips.filter(trip => trip.status === 'completed').length
|
||||
}
|
||||
|
||||
const getTripProgress = (stage) => {
|
||||
if (!stage.trips?.length) return 0
|
||||
const completed = getCompletedTrips(stage)
|
||||
return (completed / stage.trips.length) * 100
|
||||
}
|
||||
|
||||
const getCompanyGroups = (trips) => {
|
||||
const groups = {}
|
||||
|
||||
trips.forEach(trip => {
|
||||
const companyName = trip.company?.name || 'Unknown'
|
||||
if (!groups[companyName]) {
|
||||
groups[companyName] = { name: companyName, total: 0, completed: 0 }
|
||||
}
|
||||
groups[companyName].total++
|
||||
if (trip.status === 'completed') {
|
||||
groups[companyName].completed++
|
||||
}
|
||||
})
|
||||
|
||||
return Object.values(groups)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user