|
@@ -1,5 +1,17 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="space-y-6">
|
|
|
|
|
|
|
+ <div class="space-y-6" :class="{ 'opacity-50': tripDetails?.registrationClosed }">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Bannière d'information pour trajet fermé -->
|
|
|
|
|
+ <div v-if="tripDetails?.registrationClosed" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md mb-4">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <i class="fas fa-lock mr-3 text-red-500"></i>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="font-semibold">Enregistrement fermé</p>
|
|
|
|
|
+ <p class="text-sm">Les enregistrements pour ce trajet sont fermés. Aucune nouvelle réservation ne peut être effectuée.</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<!-- En-tête avec navigation -->
|
|
<!-- En-tête avec navigation -->
|
|
|
<div class="bg-white shadow rounded-lg">
|
|
<div class="bg-white shadow rounded-lg">
|
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
@@ -15,14 +27,25 @@
|
|
|
<div class="flex space-x-3">
|
|
<div class="flex space-x-3">
|
|
|
<button
|
|
<button
|
|
|
@click="$emit('scan-tickets', trip)"
|
|
@click="$emit('scan-tickets', trip)"
|
|
|
- class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
|
|
|
|
|
|
+ :disabled="tripDetails?.registrationClosed"
|
|
|
|
|
+ class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
|
>
|
|
>
|
|
|
<i class="fas fa-qrcode mr-2"></i>
|
|
<i class="fas fa-qrcode mr-2"></i>
|
|
|
Scanner les tickets
|
|
Scanner les tickets
|
|
|
</button>
|
|
</button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="!tripDetails?.registrationClosed && tripDetails?.status !== 'completed' && tripDetails?.status !== 'cancelled'"
|
|
|
|
|
+ @click="closeRegistration"
|
|
|
|
|
+ :disabled="isClosingRegistration"
|
|
|
|
|
+ class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
+ >
|
|
|
|
|
+ <i class="fas fa-lock mr-2" :class="{ 'fa-spin': isClosingRegistration }"></i>
|
|
|
|
|
+ {{ isClosingRegistration ? 'Fermeture...' : 'Fin d\'enregistrement' }}
|
|
|
|
|
+ </button>
|
|
|
<button
|
|
<button
|
|
|
@click="refreshTripData"
|
|
@click="refreshTripData"
|
|
|
- class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
|
|
|
|
|
+ :disabled="tripDetails?.registrationClosed"
|
|
|
|
|
+ class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
|
>
|
|
>
|
|
|
<i class="fas fa-sync-alt mr-2"></i>
|
|
<i class="fas fa-sync-alt mr-2"></i>
|
|
|
Actualiser
|
|
Actualiser
|
|
@@ -30,11 +53,26 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <!-- Messages d'erreur et de succès -->
|
|
|
|
|
+ <div v-if="errorMessage" class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-md">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <i class="fas fa-exclamation-triangle mr-2"></i>
|
|
|
|
|
+ {{ errorMessage }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="successMessage" class="mb-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded-md">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <i class="fas fa-check-circle mr-2"></i>
|
|
|
|
|
+ {{ successMessage }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<!-- Informations principales du trajet -->
|
|
<!-- Informations principales du trajet -->
|
|
|
<div v-if="tripDetails" class="border-b border-gray-200 pb-6">
|
|
<div v-if="tripDetails" class="border-b border-gray-200 pb-6">
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
<h1 class="text-2xl font-bold text-gray-900">
|
|
<h1 class="text-2xl font-bold text-gray-900">
|
|
|
- {{ tripDetails.route.departureStation.city }} → {{ tripDetails.route.arrivalStation.city }}
|
|
|
|
|
|
|
+ {{ tripDetails.departure }} → {{ tripDetails.arrival }}
|
|
|
</h1>
|
|
</h1>
|
|
|
<span
|
|
<span
|
|
|
:class="getStatusBadgeClass(tripDetails.status)"
|
|
:class="getStatusBadgeClass(tripDetails.status)"
|
|
@@ -85,7 +123,7 @@
|
|
|
<i class="fas fa-bus text-yellow-500 text-xl mr-3"></i>
|
|
<i class="fas fa-bus text-yellow-500 text-xl mr-3"></i>
|
|
|
<div>
|
|
<div>
|
|
|
<p class="text-sm font-medium text-gray-500">Véhicule</p>
|
|
<p class="text-sm font-medium text-gray-500">Véhicule</p>
|
|
|
- <p class="text-lg font-semibold text-gray-900">{{ tripDetails.vehicle.model }} - {{ tripDetails.vehicle.licensePlate }}</p>
|
|
|
|
|
|
|
+ <p class="text-lg font-semibold text-gray-900">{{ tripDetails.vehicle?.model || 'N/A' }} - {{ tripDetails.vehicle?.licensePlate || 'N/A' }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -109,7 +147,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Statistiques des tickets -->
|
|
<!-- Statistiques des tickets -->
|
|
|
- <div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
|
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6" :class="{ 'pointer-events-none': tripDetails?.registrationClosed }">
|
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
|
<div class="p-5">
|
|
<div class="p-5">
|
|
|
<div class="flex items-center">
|
|
<div class="flex items-center">
|
|
@@ -119,7 +157,7 @@
|
|
|
<div class="ml-5 w-0 flex-1">
|
|
<div class="ml-5 w-0 flex-1">
|
|
|
<dl>
|
|
<dl>
|
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Total tickets</dt>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Total tickets</dt>
|
|
|
- <dd class="text-lg font-medium text-gray-900">{{ ticketStats.total }}</dd>
|
|
|
|
|
|
|
+ <dd class="text-lg font-medium text-gray-900">{{ statistics.total }}</dd>
|
|
|
</dl>
|
|
</dl>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -135,7 +173,7 @@
|
|
|
<div class="ml-5 w-0 flex-1">
|
|
<div class="ml-5 w-0 flex-1">
|
|
|
<dl>
|
|
<dl>
|
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Validés</dt>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Validés</dt>
|
|
|
- <dd class="text-lg font-medium text-gray-900">{{ ticketStats.validated }}</dd>
|
|
|
|
|
|
|
+ <dd class="text-lg font-medium text-gray-900">{{ statistics.validated }}</dd>
|
|
|
</dl>
|
|
</dl>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -151,7 +189,7 @@
|
|
|
<div class="ml-5 w-0 flex-1">
|
|
<div class="ml-5 w-0 flex-1">
|
|
|
<dl>
|
|
<dl>
|
|
|
<dt class="text-sm font-medium text-gray-500 truncate">En attente</dt>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">En attente</dt>
|
|
|
- <dd class="text-lg font-medium text-gray-900">{{ ticketStats.pending }}</dd>
|
|
|
|
|
|
|
+ <dd class="text-lg font-medium text-gray-900">{{ statistics.pending }}</dd>
|
|
|
</dl>
|
|
</dl>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -167,7 +205,7 @@
|
|
|
<div class="ml-5 w-0 flex-1">
|
|
<div class="ml-5 w-0 flex-1">
|
|
|
<dl>
|
|
<dl>
|
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Annulés</dt>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Annulés</dt>
|
|
|
- <dd class="text-lg font-medium text-gray-900">{{ ticketStats.cancelled }}</dd>
|
|
|
|
|
|
|
+ <dd class="text-lg font-medium text-gray-900">{{ statistics.cancelled }}</dd>
|
|
|
</dl>
|
|
</dl>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -176,7 +214,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Liste des passagers -->
|
|
<!-- Liste des passagers -->
|
|
|
- <div class="bg-white shadow rounded-lg">
|
|
|
|
|
|
|
+ <div class="bg-white shadow rounded-lg" :class="{ 'pointer-events-none': tripDetails?.registrationClosed }">
|
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div class="flex items-center justify-between mb-6">
|
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
@@ -255,14 +293,14 @@
|
|
|
</td>
|
|
</td>
|
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
|
<span
|
|
<span
|
|
|
- :class="getTicketStatusClass(passenger.ticketStatus)"
|
|
|
|
|
|
|
+ :class="getTicketStatusClass(passenger.ticketStatus || passenger.status)"
|
|
|
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
|
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
|
|
>
|
|
>
|
|
|
<div
|
|
<div
|
|
|
- :class="getTicketStatusColor(passenger.ticketStatus)"
|
|
|
|
|
|
|
+ :class="getTicketStatusColor(passenger.ticketStatus || passenger.status)"
|
|
|
class="w-1.5 h-1.5 rounded-full mr-1.5"
|
|
class="w-1.5 h-1.5 rounded-full mr-1.5"
|
|
|
></div>
|
|
></div>
|
|
|
- {{ getTicketStatusText(passenger.ticketStatus) }}
|
|
|
|
|
|
|
+ {{ getTicketStatusText(passenger.ticketStatus || passenger.status) }}
|
|
|
</span>
|
|
</span>
|
|
|
</td>
|
|
</td>
|
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
@@ -296,107 +334,125 @@
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
|
-import EmployeeService, { type TripDetails, type TripPassenger } from '@/services/EmployeeService'
|
|
|
|
|
|
|
+import EmployeeService, { type AssignedTrip, type TripPassenger } from '@/services/EmployeeService'
|
|
|
|
|
|
|
|
// Props
|
|
// Props
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
- tripId: number
|
|
|
|
|
|
|
+ trip: AssignedTrip
|
|
|
}>()
|
|
}>()
|
|
|
|
|
|
|
|
-// État réactif
|
|
|
|
|
|
|
+// Reactive data
|
|
|
const isLoading = ref(false)
|
|
const isLoading = ref(false)
|
|
|
|
|
+const isClosingRegistration = ref(false)
|
|
|
|
|
+const errorMessage = ref('')
|
|
|
|
|
+const successMessage = ref('')
|
|
|
const searchQuery = ref('')
|
|
const searchQuery = ref('')
|
|
|
const statusFilter = ref('all')
|
|
const statusFilter = ref('all')
|
|
|
const currentPage = ref(1)
|
|
const currentPage = ref(1)
|
|
|
const itemsPerPage = ref(10)
|
|
const itemsPerPage = ref(10)
|
|
|
-const tripDetails = ref<TripDetails | null>(null)
|
|
|
|
|
|
|
+const tripDetails = ref<AssignedTrip | null>(null)
|
|
|
const passengers = ref<TripPassenger[]>([])
|
|
const passengers = ref<TripPassenger[]>([])
|
|
|
const totalPassengers = ref(0)
|
|
const totalPassengers = ref(0)
|
|
|
const totalPages = ref(1)
|
|
const totalPages = ref(1)
|
|
|
const passengerFilter = ref('')
|
|
const passengerFilter = ref('')
|
|
|
const passengerSearch = ref('')
|
|
const passengerSearch = ref('')
|
|
|
|
|
|
|
|
-// Computed
|
|
|
|
|
-const paginatedPassengers = computed(() => {
|
|
|
|
|
- return passengers.value
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
|
|
+// Computed properties
|
|
|
const statistics = computed(() => {
|
|
const statistics = computed(() => {
|
|
|
- if (!tripDetails.value) return { total: 0, validated: 0, pending: 0, validationRate: 0 }
|
|
|
|
|
|
|
+ if (!tripDetails.value) return {
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ validated: 0,
|
|
|
|
|
+ pending: 0,
|
|
|
|
|
+ cancelled: 0,
|
|
|
|
|
+ validationRate: 0
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
const total = passengers.value.length
|
|
const total = passengers.value.length
|
|
|
- const validated = passengers.value.filter(p => p.status === 'validated').length
|
|
|
|
|
- const pending = passengers.value.filter(p => p.status === 'pending').length
|
|
|
|
|
|
|
+ const validated = passengers.value.filter(p =>
|
|
|
|
|
+ p.ticketStatus === 'validated' || p.status === 'validated'
|
|
|
|
|
+ ).length
|
|
|
|
|
+ const pending = passengers.value.filter(p =>
|
|
|
|
|
+ p.ticketStatus === 'valid' || p.ticketStatus === 'pending' || p.status === 'pending'
|
|
|
|
|
+ ).length
|
|
|
|
|
+ const cancelled = passengers.value.filter(p =>
|
|
|
|
|
+ p.ticketStatus === 'cancelled' || p.status === 'cancelled'
|
|
|
|
|
+ ).length
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
total,
|
|
total,
|
|
|
validated,
|
|
validated,
|
|
|
pending,
|
|
pending,
|
|
|
|
|
+ cancelled,
|
|
|
validationRate: total > 0 ? Math.round((validated / total) * 100) : 0
|
|
validationRate: total > 0 ? Math.round((validated / total) * 100) : 0
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const ticketStats = computed(() => {
|
|
|
|
|
- const total = passengers.value.length
|
|
|
|
|
- const validated = passengers.value.filter(p => p.ticketStatus === 'validated').length
|
|
|
|
|
- const pending = passengers.value.filter(p => p.ticketStatus === 'pending').length
|
|
|
|
|
- const cancelled = passengers.value.filter(p => p.ticketStatus === 'cancelled').length
|
|
|
|
|
-
|
|
|
|
|
- return { total, validated, pending, cancelled }
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
const filteredPassengers = computed(() => {
|
|
const filteredPassengers = computed(() => {
|
|
|
let filtered = passengers.value
|
|
let filtered = passengers.value
|
|
|
|
|
|
|
|
// Filtre par statut
|
|
// Filtre par statut
|
|
|
if (passengerFilter.value) {
|
|
if (passengerFilter.value) {
|
|
|
- filtered = filtered.filter(p => p.ticketStatus === passengerFilter.value)
|
|
|
|
|
|
|
+ filtered = filtered.filter(p => (p.ticketStatus || p.status) === passengerFilter.value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Filtre par recherche
|
|
// Filtre par recherche
|
|
|
if (passengerSearch.value) {
|
|
if (passengerSearch.value) {
|
|
|
const query = passengerSearch.value.toLowerCase()
|
|
const query = passengerSearch.value.toLowerCase()
|
|
|
- filtered = filtered.filter(p =>
|
|
|
|
|
- p.name.toLowerCase().includes(query) ||
|
|
|
|
|
- p.email.toLowerCase().includes(query) ||
|
|
|
|
|
- p.seatNumber.toLowerCase().includes(query)
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ filtered = filtered.filter(p => {
|
|
|
|
|
+ const fullName = `${p.firstName || p.name || ''} ${p.lastName || p.otherName || ''}`.toLowerCase()
|
|
|
|
|
+ const email = (p.email || '').toLowerCase()
|
|
|
|
|
+ const seatNumber = (p.seatNumber || '').toLowerCase()
|
|
|
|
|
+ const ticketNumber = (p.ticketNumber || '').toLowerCase()
|
|
|
|
|
+
|
|
|
|
|
+ return fullName.includes(query) ||
|
|
|
|
|
+ email.includes(query) ||
|
|
|
|
|
+ seatNumber.includes(query) ||
|
|
|
|
|
+ ticketNumber.includes(query)
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return filtered
|
|
return filtered
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// Méthodes
|
|
|
|
|
|
|
+// Methods
|
|
|
|
|
+const createTripDetailsObject = (tripData: any): AssignedTrip => ({
|
|
|
|
|
+ id: tripData.id,
|
|
|
|
|
+ departure: tripData.departure,
|
|
|
|
|
+ arrival: tripData.arrival,
|
|
|
|
|
+ departureTime: tripData.departureTime,
|
|
|
|
|
+ arrivalTime: tripData.arrivalTime,
|
|
|
|
|
+ date: tripData.date,
|
|
|
|
|
+ status: tripData.status,
|
|
|
|
|
+ registrationClosed: tripData.registrationClosed || false,
|
|
|
|
|
+ vehicle: tripData.vehicle,
|
|
|
|
|
+ company: tripData.company,
|
|
|
|
|
+ route: tripData.route,
|
|
|
|
|
+ driver: tripData.driver,
|
|
|
|
|
+ totalSeats: tripData.capacity || tripData.vehicle?.capacity || 0,
|
|
|
|
|
+ occupiedSeats: tripData.passengers || 0,
|
|
|
|
|
+ revenue: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
const loadTripDetails = async () => {
|
|
const loadTripDetails = async () => {
|
|
|
- isLoading.value = true
|
|
|
|
|
|
|
+ if (!props.trip) {
|
|
|
|
|
+ console.warn('props.trip n\'est pas encore disponible')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initialiser immédiatement avec les données du prop
|
|
|
|
|
+ tripDetails.value = createTripDetailsObject(props.trip)
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- const details = await EmployeeService.getTripDetails(props.tripId)
|
|
|
|
|
- tripDetails.value = details
|
|
|
|
|
|
|
+ const tripData = await EmployeeService.getTripDetails(props.trip.id)
|
|
|
|
|
+
|
|
|
|
|
+ if (tripData) {
|
|
|
|
|
+ tripDetails.value = createTripDetailsObject(tripData)
|
|
|
|
|
+ console.log('🔍 DEBUG - tripDetails après chargement API:', tripDetails.value)
|
|
|
|
|
+ await loadPassengers()
|
|
|
|
|
+ }
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Erreur lors du chargement des détails du trajet:', error)
|
|
console.error('Erreur lors du chargement des détails du trajet:', error)
|
|
|
- // Fallback avec des données de démonstration
|
|
|
|
|
- tripDetails.value = {
|
|
|
|
|
- id: props.tripId,
|
|
|
|
|
- departure: 'Abidjan',
|
|
|
|
|
- arrival: 'Bouaké',
|
|
|
|
|
- departureTime: '08:00',
|
|
|
|
|
- arrivalTime: '11:30',
|
|
|
|
|
- date: '2024-01-15',
|
|
|
|
|
- status: 'in_progress',
|
|
|
|
|
- vehicle: { id: 1, licensePlate: 'CI-001-AB', model: 'Mercedes Sprinter', capacity: 50 },
|
|
|
|
|
- company: { id: 1, name: 'Transport Express' },
|
|
|
|
|
- route: {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- departureStation: { id: 1, name: 'Gare d\'Abidjan', city: 'Abidjan' },
|
|
|
|
|
- arrivalStation: { id: 2, name: 'Gare de Bouaké', city: 'Bouaké' }
|
|
|
|
|
- },
|
|
|
|
|
- driver: { id: 1, firstName: 'Jean', lastName: 'Kouassi', phone: '+225 07 12 34 56 78' },
|
|
|
|
|
- totalSeats: 50,
|
|
|
|
|
- occupiedSeats: 45,
|
|
|
|
|
- revenue: 225000
|
|
|
|
|
- }
|
|
|
|
|
- } finally {
|
|
|
|
|
- isLoading.value = false
|
|
|
|
|
|
|
+ // tripDetails.value est déjà initialisé avec les données du prop
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -443,6 +499,7 @@ const getStatusText = (status: string) => {
|
|
|
const getTicketStatusColor = (status: string) => {
|
|
const getTicketStatusColor = (status: string) => {
|
|
|
const colors = {
|
|
const colors = {
|
|
|
'validated': 'bg-green-500',
|
|
'validated': 'bg-green-500',
|
|
|
|
|
+ 'valid': 'bg-blue-500',
|
|
|
'pending': 'bg-yellow-500',
|
|
'pending': 'bg-yellow-500',
|
|
|
'cancelled': 'bg-red-500'
|
|
'cancelled': 'bg-red-500'
|
|
|
}
|
|
}
|
|
@@ -452,6 +509,7 @@ const getTicketStatusColor = (status: string) => {
|
|
|
const getTicketStatusClass = (status: string) => {
|
|
const getTicketStatusClass = (status: string) => {
|
|
|
const classes = {
|
|
const classes = {
|
|
|
'validated': 'bg-green-100 text-green-800',
|
|
'validated': 'bg-green-100 text-green-800',
|
|
|
|
|
+ 'valid': 'bg-blue-100 text-blue-800',
|
|
|
'pending': 'bg-yellow-100 text-yellow-800',
|
|
'pending': 'bg-yellow-100 text-yellow-800',
|
|
|
'cancelled': 'bg-red-100 text-red-800'
|
|
'cancelled': 'bg-red-100 text-red-800'
|
|
|
}
|
|
}
|
|
@@ -461,6 +519,7 @@ const getTicketStatusClass = (status: string) => {
|
|
|
const getTicketStatusText = (status: string) => {
|
|
const getTicketStatusText = (status: string) => {
|
|
|
const texts = {
|
|
const texts = {
|
|
|
'validated': 'Validé',
|
|
'validated': 'Validé',
|
|
|
|
|
+ 'valid': 'Valide',
|
|
|
'pending': 'En attente',
|
|
'pending': 'En attente',
|
|
|
'cancelled': 'Annulé'
|
|
'cancelled': 'Annulé'
|
|
|
}
|
|
}
|
|
@@ -508,43 +567,62 @@ const viewPassengerDetails = (passenger: TripPassenger) => {
|
|
|
|
|
|
|
|
const refreshTripData = async () => {
|
|
const refreshTripData = async () => {
|
|
|
try {
|
|
try {
|
|
|
- // Recharger les données du trajet depuis l'API
|
|
|
|
|
console.log('Actualisation des données du trajet...')
|
|
console.log('Actualisation des données du trajet...')
|
|
|
|
|
+ await loadTripDetails()
|
|
|
|
|
+ await loadPassengers()
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Erreur lors de l\'actualisation:', error)
|
|
console.error('Erreur lors de l\'actualisation:', error)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Lifecycle
|
|
|
|
|
-onMounted(() => {
|
|
|
|
|
- loadTripDetails()
|
|
|
|
|
- loadPassengers()
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// Watchers
|
|
|
|
|
-watch([searchQuery, statusFilter], () => {
|
|
|
|
|
- applyFilters()
|
|
|
|
|
-})
|
|
|
|
|
|
|
+const closeRegistration = async () => {
|
|
|
|
|
+ if (!tripDetails.value?.id) {
|
|
|
|
|
+ console.error('Aucun trajet sélectionné');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-watch(() => props.tripId, () => {
|
|
|
|
|
- loadTripDetails()
|
|
|
|
|
- loadPassengers()
|
|
|
|
|
-})
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await EmployeeService.closeRegistration(tripDetails.value.id);
|
|
|
|
|
+
|
|
|
|
|
+ if (response.success) {
|
|
|
|
|
+ // Mettre à jour les données locales
|
|
|
|
|
+ tripDetails.value.registrationClosed = true;
|
|
|
|
|
+
|
|
|
|
|
+ // Recharger les données pour s'assurer de la cohérence
|
|
|
|
|
+ await loadTripDetails();
|
|
|
|
|
+
|
|
|
|
|
+ // Afficher un message de succès
|
|
|
|
|
+ console.log('Enregistrement fermé avec succès');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.error('Erreur lors de la fermeture de l\'enregistrement:', response.message);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erreur lors de la fermeture de l\'enregistrement:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
const loadPassengers = async () => {
|
|
const loadPassengers = async () => {
|
|
|
|
|
+ if (!props.trip) {
|
|
|
|
|
+ console.warn('props.trip n\'est pas encore disponible pour loadPassengers')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
const status = statusFilter.value === 'all' ? undefined : statusFilter.value
|
|
const status = statusFilter.value === 'all' ? undefined : statusFilter.value
|
|
|
const search = searchQuery.value || undefined
|
|
const search = searchQuery.value || undefined
|
|
|
|
|
|
|
|
const result = await EmployeeService.getTripPassengers(
|
|
const result = await EmployeeService.getTripPassengers(
|
|
|
- props.tripId,
|
|
|
|
|
|
|
+ props.trip.id,
|
|
|
currentPage.value,
|
|
currentPage.value,
|
|
|
itemsPerPage.value,
|
|
itemsPerPage.value,
|
|
|
status,
|
|
status,
|
|
|
search
|
|
search
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- passengers.value = result.passengers
|
|
|
|
|
|
|
+ passengers.value = result.passengers.map(passenger => ({
|
|
|
|
|
+ ...passenger,
|
|
|
|
|
+ status: passenger.status || passenger.ticketStatus || 'pending'
|
|
|
|
|
+ }))
|
|
|
totalPassengers.value = result.pagination.total
|
|
totalPassengers.value = result.pagination.total
|
|
|
totalPages.value = result.pagination.totalPages
|
|
totalPages.value = result.pagination.totalPages
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -559,15 +637,67 @@ const loadPassengers = async () => {
|
|
|
email: 'marie.kone@email.com',
|
|
email: 'marie.kone@email.com',
|
|
|
seatNumber: 'A1',
|
|
seatNumber: 'A1',
|
|
|
ticketNumber: 'TK001234',
|
|
ticketNumber: 'TK001234',
|
|
|
|
|
+ ticketStatus: 'validated',
|
|
|
status: 'validated',
|
|
status: 'validated',
|
|
|
validatedAt: '2024-01-15T07:45:00Z',
|
|
validatedAt: '2024-01-15T07:45:00Z',
|
|
|
qrCode: 'QR001234'
|
|
qrCode: 'QR001234'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 2,
|
|
|
|
|
+ firstName: 'Jean',
|
|
|
|
|
+ lastName: 'Kouassi',
|
|
|
|
|
+ phone: '+225 07 23 45 67 89',
|
|
|
|
|
+ email: 'jean.kouassi@email.com',
|
|
|
|
|
+ seatNumber: 'A2',
|
|
|
|
|
+ ticketNumber: 'TK001235',
|
|
|
|
|
+ ticketStatus: 'valid',
|
|
|
|
|
+ status: 'pending',
|
|
|
|
|
+ validatedAt: null,
|
|
|
|
|
+ qrCode: 'QR001235'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 3,
|
|
|
|
|
+ firstName: 'Fatou',
|
|
|
|
|
+ lastName: 'Traoré',
|
|
|
|
|
+ phone: '+225 07 34 56 78 90',
|
|
|
|
|
+ email: 'fatou.traore@email.com',
|
|
|
|
|
+ seatNumber: 'B1',
|
|
|
|
|
+ ticketNumber: 'TK001236',
|
|
|
|
|
+ ticketStatus: 'valid',
|
|
|
|
|
+ status: 'pending',
|
|
|
|
|
+ validatedAt: null,
|
|
|
|
|
+ qrCode: 'QR001236'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 4,
|
|
|
|
|
+ firstName: 'Amadou',
|
|
|
|
|
+ lastName: 'Diallo',
|
|
|
|
|
+ phone: '+225 07 45 67 89 01',
|
|
|
|
|
+ email: 'amadou.diallo@email.com',
|
|
|
|
|
+ seatNumber: 'B2',
|
|
|
|
|
+ ticketNumber: 'TK001237',
|
|
|
|
|
+ ticketStatus: 'cancelled',
|
|
|
|
|
+ status: 'cancelled',
|
|
|
|
|
+ validatedAt: null,
|
|
|
|
|
+ qrCode: 'QR001237'
|
|
|
}
|
|
}
|
|
|
]
|
|
]
|
|
|
- totalPassengers.value = 1
|
|
|
|
|
|
|
+ totalPassengers.value = 4
|
|
|
totalPages.value = 1
|
|
totalPages.value = 1
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// Watchers
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadTripDetails()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+watch(() => props.trip, (newTrip) => {
|
|
|
|
|
+ if (newTrip) {
|
|
|
|
|
+ loadTripDetails()
|
|
|
|
|
+ loadPassengers()
|
|
|
|
|
+ }
|
|
|
|
|
+}, { immediate: true })
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|