Initial commit from monorepo
This commit is contained in:
244
app/components/OrderCalendar.vue
Normal file
244
app/components/OrderCalendar.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 border border-base-300 shadow">
|
||||
<div class="card-body">
|
||||
<div class="mb-4 flex items-center justify-between flex-wrap gap-3">
|
||||
<h3 class="text-lg font-semibold text-base-content">{{ t('orderCalendar.header.title') }}</h3>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Filters -->
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
id="show-loading"
|
||||
v-model="showLoading"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-primary checkbox-sm"
|
||||
>
|
||||
<span class="text-sm text-base-content/80">{{ t('orderCalendar.filters.loading') }}</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
id="show-unloading"
|
||||
v-model="showUnloading"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-primary checkbox-sm"
|
||||
>
|
||||
<span class="text-sm text-base-content/80">{{ t('orderCalendar.filters.unloading') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simplified calendar view -->
|
||||
<div class="bg-base-200 rounded-box p-4">
|
||||
<div class="grid grid-cols-7 gap-2 mb-4">
|
||||
<div v-for="month in months" :key="month.name" class="text-center">
|
||||
<h4 class="font-medium text-sm text-base-content/80">{{ month.name }}</h4>
|
||||
<div class="mt-2 space-y-1">
|
||||
<div
|
||||
v-for="week in month.weeks"
|
||||
:key="week.start"
|
||||
class="text-xs"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-base-content/60">{{ formatWeek(week.start) }}</span>
|
||||
<div class="flex space-x-1">
|
||||
<div
|
||||
v-for="event in week.events"
|
||||
:key="event.id"
|
||||
class="w-2 h-2 rounded-full"
|
||||
:style="{ backgroundColor: event.color }"
|
||||
:title="event.title"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event summary -->
|
||||
<div class="border-t border-base-300 pt-4">
|
||||
<h4 class="font-medium text-sm text-base-content/80 mb-3">{{ t('orderCalendar.stats.title') }}</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div v-for="period in periodStats" :key="period.name" class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body p-3 gap-2">
|
||||
<h5 class="font-medium text-sm text-base-content">{{ period.name }}</h5>
|
||||
<p class="text-xs text-base-content/70">{{ period.description }}</p>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('orderCalendar.stats.trips') }}</span>
|
||||
<span class="font-medium">{{ period.tripCount }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('orderCalendar.stats.companies') }}</span>
|
||||
<span class="font-medium">{{ period.companyCount }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('orderCalendar.stats.weight') }}</span>
|
||||
<span class="font-medium">{{ period.totalWeight }}{{ t('orderCalendar.labels.weight_unit') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
stages: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const showLoading = ref(true)
|
||||
const showUnloading = ref(true)
|
||||
const { t } = useI18n()
|
||||
|
||||
const allTrips = computed(() => {
|
||||
const trips = []
|
||||
props.stages.forEach(stage => {
|
||||
if (stage.trips?.length) {
|
||||
stage.trips.forEach(trip => {
|
||||
trips.push({
|
||||
...trip,
|
||||
stageName: stage.name,
|
||||
stageType: stage.stageType
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
return trips
|
||||
})
|
||||
|
||||
const calendarEvents = computed(() => {
|
||||
const events = []
|
||||
|
||||
allTrips.value.forEach(trip => {
|
||||
const companyColor = getCompanyColor(trip.company?.name)
|
||||
|
||||
if (showLoading.value && trip.plannedLoadingDate) {
|
||||
events.push({
|
||||
id: `loading-${trip.uuid}`,
|
||||
title: `${trip.name} - ${t('orderCalendar.labels.loading')}`,
|
||||
date: new Date(trip.plannedLoadingDate),
|
||||
color: companyColor,
|
||||
type: 'loading',
|
||||
trip: trip
|
||||
})
|
||||
}
|
||||
|
||||
if (showUnloading.value && trip.plannedUnloadingDate) {
|
||||
events.push({
|
||||
id: `unloading-${trip.uuid}`,
|
||||
title: `${trip.name} - ${t('orderCalendar.labels.unloading')}`,
|
||||
date: new Date(trip.plannedUnloadingDate),
|
||||
color: companyColor,
|
||||
type: 'unloading',
|
||||
trip: trip
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return events
|
||||
})
|
||||
|
||||
const months = computed(() => {
|
||||
const monthsData = []
|
||||
const startDate = new Date('2024-08-01')
|
||||
const endDate = new Date('2024-10-31')
|
||||
|
||||
const monthNames = t('orderCalendar.months').split('|')
|
||||
|
||||
monthNames.forEach((name, index) => {
|
||||
const monthStart = new Date(2024, 7 + index, 1) // August = 7
|
||||
const monthEnd = new Date(2024, 8 + index, 0)
|
||||
|
||||
const weeks = []
|
||||
let weekStart = new Date(monthStart)
|
||||
|
||||
while (weekStart <= monthEnd) {
|
||||
const weekEnd = new Date(weekStart)
|
||||
weekEnd.setDate(weekEnd.getDate() + 6)
|
||||
|
||||
const weekEvents = calendarEvents.value.filter(event => {
|
||||
const eventDate = event.date
|
||||
return eventDate >= weekStart && eventDate <= weekEnd
|
||||
})
|
||||
|
||||
weeks.push({
|
||||
start: new Date(weekStart),
|
||||
events: weekEvents.slice(0, 5) // Show max 5 events
|
||||
})
|
||||
|
||||
weekStart.setDate(weekStart.getDate() + 7)
|
||||
}
|
||||
|
||||
monthsData.push({
|
||||
name,
|
||||
weeks
|
||||
})
|
||||
})
|
||||
|
||||
return monthsData
|
||||
})
|
||||
|
||||
const periodStats = computed(() => {
|
||||
const stats = []
|
||||
|
||||
// Stats per stage
|
||||
props.stages.forEach(stage => {
|
||||
if (stage.trips?.length) {
|
||||
const companies = new Set(stage.trips.map(trip => trip.company?.name).filter(Boolean))
|
||||
const totalWeight = stage.trips.reduce((sum, trip) => sum + (trip.plannedWeight || 0), 0)
|
||||
|
||||
stats.push({
|
||||
name: stage.name,
|
||||
description: stage.stageType === 'transport' ? getTransportTypeText(stage.transportType) : t('orderCalendar.labels.service'),
|
||||
tripCount: stage.trips.length,
|
||||
companyCount: companies.size,
|
||||
totalWeight: totalWeight
|
||||
})
|
||||
} else {
|
||||
stats.push({
|
||||
name: stage.name,
|
||||
description: t('orderCalendar.labels.service_stage'),
|
||||
tripCount: 0,
|
||||
companyCount: stage.selectedCompany ? 1 : 0,
|
||||
totalWeight: 0
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return stats
|
||||
})
|
||||
|
||||
const getCompanyColor = (companyName) => {
|
||||
const colors = {
|
||||
'Kenya Coffee Logistics Ltd': '#ef4444',
|
||||
'Kenya Highland Transport': '#f97316',
|
||||
'Mombasa Express Cargo': '#eab308',
|
||||
'East Africa Shipping Lines': '#3b82f6',
|
||||
'TruckCo': '#10b981',
|
||||
'AutoBase #1': '#8b5cf6',
|
||||
'RailTrans': '#ec4899'
|
||||
}
|
||||
return colors[companyName] || '#6b7280'
|
||||
}
|
||||
|
||||
const getTransportTypeText = (transportType) => {
|
||||
const texts = {
|
||||
auto: t('orderCalendar.transport.auto'),
|
||||
rail: t('orderCalendar.transport.rail'),
|
||||
sea: t('orderCalendar.transport.sea'),
|
||||
air: t('orderCalendar.transport.air')
|
||||
}
|
||||
return texts[transportType] || transportType
|
||||
}
|
||||
|
||||
const formatWeek = (date) => {
|
||||
return `${date.getDate()}-${date.getDate() + 6}`
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user