Initial commit from monorepo
This commit is contained in:
243
app/pages/clientarea/orders/[id].vue
Normal file
243
app/pages/clientarea/orders/[id].vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<Section variant="plain">
|
||||
<Stack gap="8">
|
||||
<template v-if="hasOrderError">
|
||||
<div class="text-sm text-error">
|
||||
{{ orderError }}
|
||||
</div>
|
||||
<Button @click="loadOrder">{{ t('ordersDetail.errors.retry') }}</Button>
|
||||
</template>
|
||||
|
||||
<div v-else-if="isLoadingOrder" class="text-sm text-base-content/60">
|
||||
{{ t('ordersDetail.states.loading') }}
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<Card padding="lg" class="border border-base-300">
|
||||
<RouteSummaryHeader :title="orderTitle" :meta="orderMeta" />
|
||||
</Card>
|
||||
|
||||
<Card v-if="orderRoutesForMap.length" padding="lg" class="border border-base-300">
|
||||
<Stack gap="4">
|
||||
<RouteStagesList
|
||||
:stages="orderStageItems"
|
||||
:empty-text="t('ordersDetail.sections.stages.empty')"
|
||||
/>
|
||||
|
||||
<div class="divider my-0"></div>
|
||||
|
||||
<RequestRoutesMap :routes="orderRoutesForMap" :height="260" />
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<div class="space-y-3">
|
||||
<Heading :level="3" weight="semibold">{{ t('ordersDetail.sections.timeline.title') }}</Heading>
|
||||
<GanttTimeline
|
||||
v-if="order?.stages"
|
||||
:stages="order.stages"
|
||||
:showLoading="showLoading"
|
||||
:showUnloading="showUnloading"
|
||||
/>
|
||||
<Text v-else tone="muted">{{ t('ordersDetail.sections.timeline.empty') }}</Text>
|
||||
</div>
|
||||
</template>
|
||||
</Stack>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GetOrderDocument } from '~/composables/graphql/team/orders-generated'
|
||||
import type { RouteStageItem } from '~/components/RouteStagesList.vue'
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth-oidc']
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const order = ref<any>(null)
|
||||
const isLoadingOrder = ref(true)
|
||||
const hasOrderError = ref(false)
|
||||
const orderError = ref('')
|
||||
const showLoading = ref(true)
|
||||
const showUnloading = ref(true)
|
||||
|
||||
const orderTitle = computed(() => {
|
||||
const source = order.value?.sourceLocationName || t('ordersDetail.labels.source_unknown')
|
||||
const destination = order.value?.destinationLocationName || t('ordersDetail.labels.destination_unknown')
|
||||
return `${source} → ${destination}`
|
||||
})
|
||||
|
||||
const orderMeta = computed(() => {
|
||||
const meta: string[] = []
|
||||
const orderName = order.value?.name || (route.params.id as string)
|
||||
if (orderName) meta.push(`#${orderName}`)
|
||||
|
||||
const line = order.value?.orderLines?.[0]
|
||||
if (line?.quantity) {
|
||||
meta.push(`${line.quantity} ${line.unit || t('ordersDetail.labels.unit_tons')}`)
|
||||
}
|
||||
if (line?.productName) {
|
||||
meta.push(line.productName)
|
||||
}
|
||||
if (order.value?.totalAmount) {
|
||||
meta.push(formatPrice(order.value.totalAmount, order.value?.currency))
|
||||
}
|
||||
|
||||
const durationDays = getOrderDuration()
|
||||
if (durationDays) {
|
||||
meta.push(`${durationDays} ${t('ordersDetail.labels.delivery_days')}`)
|
||||
}
|
||||
|
||||
return meta
|
||||
})
|
||||
|
||||
const orderRoutesForMap = computed(() => {
|
||||
const stages = (order.value?.stages || [])
|
||||
.filter(Boolean)
|
||||
.map((stage: any) => {
|
||||
if (stage.stageType === 'transport') {
|
||||
if (!stage.sourceLatitude || !stage.sourceLongitude || !stage.destinationLatitude || !stage.destinationLongitude) return null
|
||||
return {
|
||||
fromLat: stage.sourceLatitude,
|
||||
fromLon: stage.sourceLongitude,
|
||||
fromName: stage.sourceLocationName,
|
||||
toLat: stage.destinationLatitude,
|
||||
toLon: stage.destinationLongitude,
|
||||
toName: stage.destinationLocationName,
|
||||
transportType: stage.transportType
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
if (!stages.length) return []
|
||||
return [{ stages }]
|
||||
})
|
||||
|
||||
const orderStageItems = computed<RouteStageItem[]>(() => {
|
||||
return (order.value?.stages || []).map((stage: any) => {
|
||||
const isTransport = stage.stageType === 'transport'
|
||||
const from = isTransport ? stage.sourceLocationName : stage.locationName
|
||||
const to = isTransport ? stage.destinationLocationName : stage.locationName
|
||||
|
||||
const meta: string[] = []
|
||||
const dateRange = getStageDateRange(stage)
|
||||
if (dateRange) {
|
||||
meta.push(dateRange)
|
||||
}
|
||||
|
||||
const companies = getCompaniesSummary(stage)
|
||||
companies.forEach((company: any) => {
|
||||
meta.push(
|
||||
`${company.name} · ${company.totalWeight || 0}${t('ordersDetail.labels.weight_unit')} · ${company.tripsCount || 0} ${t('ordersDetail.labels.trips')}`
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
key: stage.uuid,
|
||||
from,
|
||||
to,
|
||||
label: stage.name,
|
||||
meta
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const loadOrder = async () => {
|
||||
try {
|
||||
isLoadingOrder.value = true
|
||||
hasOrderError.value = false
|
||||
const orderUuid = route.params.id as string
|
||||
const { data, error: orderErrorResp } = await useServerQuery('order-detail', GetOrderDocument, { orderUuid }, 'team', 'orders')
|
||||
if (orderErrorResp.value) throw orderErrorResp.value
|
||||
order.value = data.value?.getOrder
|
||||
} catch (err: any) {
|
||||
hasOrderError.value = true
|
||||
orderError.value = err.message || t('ordersDetail.errors.load_failed')
|
||||
} finally {
|
||||
isLoadingOrder.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const formatPrice = (price: number, currency?: string | null) => {
|
||||
if (!price) return t('ordersDetail.labels.price_zero')
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
style: 'currency',
|
||||
currency: currency || 'RUB',
|
||||
minimumFractionDigits: 0
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
const getCompaniesSummary = (stage: any) => {
|
||||
const companies = []
|
||||
if (stage.stageType === 'service' && stage.selectedCompany) {
|
||||
companies.push({
|
||||
name: stage.selectedCompany.name,
|
||||
totalWeight: 0,
|
||||
tripsCount: 0,
|
||||
company: stage.selectedCompany
|
||||
})
|
||||
return companies
|
||||
}
|
||||
|
||||
if (stage.stageType === 'transport' && stage.trips?.length) {
|
||||
const companiesMap = new Map()
|
||||
stage.trips.forEach((trip: any) => {
|
||||
const companyName = trip.company?.name || t('ordersDetail.labels.company_unknown')
|
||||
const weight = trip.plannedWeight || 0
|
||||
if (companiesMap.has(companyName)) {
|
||||
const existing = companiesMap.get(companyName)
|
||||
existing.totalWeight += weight
|
||||
existing.tripsCount += 1
|
||||
} else {
|
||||
companiesMap.set(companyName, {
|
||||
name: companyName,
|
||||
totalWeight: weight,
|
||||
tripsCount: 1,
|
||||
company: trip.company
|
||||
})
|
||||
}
|
||||
})
|
||||
return Array.from(companiesMap.values())
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const getOrderDuration = () => {
|
||||
if (!order.value?.stages?.length) return 0
|
||||
let minDate: Date | null = null
|
||||
let maxDate: Date | null = null
|
||||
order.value.stages.forEach((stage: any) => {
|
||||
stage.trips?.forEach((trip: any) => {
|
||||
const startDate = new Date(trip.plannedLoadingDate || trip.actualLoadingDate)
|
||||
const endDate = new Date(trip.plannedUnloadingDate || trip.actualUnloadingDate)
|
||||
if (!minDate || startDate < minDate) minDate = startDate
|
||||
if (!maxDate || endDate > maxDate) maxDate = endDate
|
||||
})
|
||||
})
|
||||
if (!minDate || !maxDate) return 0
|
||||
const diffTime = Math.abs(maxDate.getTime() - minDate.getTime())
|
||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
const getStageDateRange = (stage: any) => {
|
||||
if (!stage.trips?.length) return t('ordersDetail.labels.dates_undefined')
|
||||
let minDate: Date | null = null
|
||||
let maxDate: Date | null = null
|
||||
stage.trips.forEach((trip: any) => {
|
||||
const startDate = new Date(trip.plannedLoadingDate || trip.actualLoadingDate)
|
||||
const endDate = new Date(trip.plannedUnloadingDate || trip.actualUnloadingDate)
|
||||
if (!minDate || startDate < minDate) minDate = startDate
|
||||
if (!maxDate || endDate > maxDate) maxDate = endDate
|
||||
})
|
||||
if (!minDate || !maxDate) return t('ordersDetail.labels.dates_undefined')
|
||||
const formatDate = (date: Date) => date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
|
||||
if (minDate.toDateString() === maxDate.toDateString()) return formatDate(minDate)
|
||||
return `${formatDate(minDate)} - ${formatDate(maxDate)}`
|
||||
}
|
||||
|
||||
await loadOrder()
|
||||
</script>
|
||||
Reference in New Issue
Block a user