|
|
@@ -84,11 +84,11 @@
|
|
|
<td>{{ formatDateTime(trip.departAt) }}</td>
|
|
|
<td>{{ formatDateTime(trip.arriveAt) }}</td>
|
|
|
<td>{{ trip.pricePerSeat || 0 }} FCFA</td>
|
|
|
- <td>{{ trip.availableSeats || 0 }}/{{ trip.totalSeats || 0 }}</td>
|
|
|
- <td>{{ getDriverName(trip.driverId) }}</td>
|
|
|
+ <td>{{ trip.availableSeats || 0 }}/{{ trip.vehicule?.number_places || 0 }}</td>
|
|
|
+ <td>{{ getDriverName() }}</td>
|
|
|
<td>
|
|
|
- <span class="status-badge" :class="getStatusClass(trip.status)">
|
|
|
- {{ getStatusLabel(trip.status || 'upcoming') }}
|
|
|
+ <span class="status-badge" :class="getStatusClass(trip)">
|
|
|
+ {{ getStatusLabel(trip) }}
|
|
|
</span>
|
|
|
</td>
|
|
|
<td class="actions-cell">
|
|
|
@@ -257,11 +257,11 @@
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<div class="info-label">Places disponibles</div>
|
|
|
- <div class="info-value">{{ viewingTrip.availableSeats || 0 }}/{{ viewingTrip.totalSeats || 0 }}</div>
|
|
|
+ <div class="info-value">{{ viewingTrip.availableSeats || 0 }}/{{ viewingTrip.vehicule?.number_places || 0 }}</div>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<div class="info-label">Chauffeur</div>
|
|
|
- <div class="info-value">{{ getDriverName(viewingTrip.driverId) }}</div>
|
|
|
+ <div class="info-value">{{ getDriverName() }}</div>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<div class="info-label">Véhicule</div>
|
|
|
@@ -270,28 +270,40 @@
|
|
|
<div class="info-item">
|
|
|
<div class="info-label">Statut</div>
|
|
|
<div class="info-value">
|
|
|
- <span class="status-badge" :class="getStatusClass(viewingTrip.status || 'upcoming')">
|
|
|
- {{ getStatusLabel(viewingTrip.status || 'upcoming') }}
|
|
|
+ <span class="status-badge" :class="getStatusClass(viewingTrip)">
|
|
|
+ {{ getStatusLabel(viewingTrip) }}
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="info-item" v-if="viewingTrip.actualArrivalDateTime">
|
|
|
<div class="info-label">Arrivée réelle</div>
|
|
|
<div class="info-value">{{ formatDateTime(viewingTrip.actualArrivalDateTime) }}</div>
|
|
|
+ </div>
|
|
|
+ <!-- Ajout de la somme gagnée selon la formule demandée -->
|
|
|
+ <div class="info-item bg-gradient-to-r from-green-100 to-emerald-100 border-2 border-green-300 shadow-lg rounded-lg p-3 transform hover:scale-105 transition-all duration-200">
|
|
|
+ <div class="info-label text-green-800 font-semibold flex items-center">
|
|
|
+ <svg class="w-5 h-5 mr-2 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
|
|
+ <path fill-rule="evenodd" d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
|
|
|
+ </svg>
|
|
|
+ Somme gagnée 💰
|
|
|
+ </div>
|
|
|
+ <div class="info-value text-green-700 font-bold text-lg">{{ (viewingTrip.pricePerSeat || 0) * ((viewingTrip.vehicule?.number_places || 0) - (viewingTrip.availableSeats || 0)) }} FCFA</div>
|
|
|
</div>
|
|
|
<!-- Ajout des sommes -->
|
|
|
- <div class="info-item">
|
|
|
- <div class="info-label">Totale des places prises</div>
|
|
|
- <div class="info-value">{{ ((viewingTrip.totalSeats || 0) - (viewingTrip.availableSeats || 0)) * (viewingTrip.pricePerSeat || 0) }} FCFA</div>
|
|
|
+ <div class="info-item cursor-pointer hover:bg-blue-50 transition-colors duration-200" @click="openReservationsModal">
|
|
|
+ <div class="info-label flex items-center">
|
|
|
+ <span>Totale des places réservées</span>
|
|
|
+ <i class="fas fa-eye ml-2 text-blue-500"></i>
|
|
|
+ </div>
|
|
|
+ <div class="info-value text-blue-600">{{ ((viewingTrip.vehicule?.number_places || 0) - (viewingTrip.availableSeats || 0)) }} </div>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
- <div class="info-label">Totale des places non occupés</div>
|
|
|
- <div class="info-value">{{ (viewingTrip.availableSeats || 0) * (viewingTrip.pricePerSeat || 0) }} FCFA</div>
|
|
|
+ <div class="info-label">Totale des paiements en ligne</div>
|
|
|
+ <div class="info-value">{{ totalOnlinePayments }} FCFA</div>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
- <div class="info-label">Totale des places en espèces</div>
|
|
|
- <!-- Calcul à faire -->
|
|
|
- <div class="info-value">{{ (viewingTrip.availableSeats || 0) * (viewingTrip.pricePerSeat || 0) }} FCFA</div>
|
|
|
+ <div class="info-label">Totale des paiements en espèces</div>
|
|
|
+ <div class="info-value">{{ totalCashPayments }} FCFA</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -309,6 +321,8 @@
|
|
|
:cities="stationStore.stations"
|
|
|
/>
|
|
|
</div>
|
|
|
+
|
|
|
+
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="py-4 px-5 border-t border-gray-200 flex justify-end gap-2.5">
|
|
|
@@ -336,6 +350,137 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- Modal des réservations -->
|
|
|
+ <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" v-if="showReservationsModal">
|
|
|
+ <div class="bg-white rounded-lg w-11/12 max-w-6xl max-h-screen overflow-y-auto shadow-lg">
|
|
|
+
|
|
|
+ <div class="flex justify-end items-center py-4 px-5 border-b border-gray-200 ">
|
|
|
+ <button class="bg-transparent border-0 m-2 p-2 rounded-full text-xl text-gray-400 cursor-pointer hover:bg-gray-400 hover:text-white " @click="closeReservationsModal">
|
|
|
+ <i class="fas fa-times"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <div class="p-5">
|
|
|
+ <!-- Filtres et actions -->
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <h2 class="text-xl text-gray-800 m-0">
|
|
|
+ <i class="fas fa-list-alt mr-2 text-blue-500"></i>
|
|
|
+ Réservations du voyage
|
|
|
+ </h2>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <select v-model="reservationFilters.status" @change="loadTripReservations" class="px-3 py-2 border border-gray-300 rounded text-sm">
|
|
|
+ <option value="">Tous les statuts</option>
|
|
|
+ <option value="pending">En attente</option>
|
|
|
+ <option value="confirmed">Confirmée</option>
|
|
|
+ <option value="cancelled">Annulée</option>
|
|
|
+ <option value="completed">Terminée</option>
|
|
|
+ </select>
|
|
|
+ <button @click="loadTripReservations" class="px-4 py-2 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 transition-colors">
|
|
|
+ <i class="fas fa-sync-alt mr-1"></i> Actualiser
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Loading state -->
|
|
|
+ <div v-if="loadingReservations" class="text-center py-8">
|
|
|
+ <i class="fas fa-spinner fa-spin text-blue-500 text-2xl"></i>
|
|
|
+ <p class="text-gray-600 mt-3">Chargement des réservations...</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Error state -->
|
|
|
+ <div v-else-if="reservationsError" class="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">
|
|
|
+ <i class="fas fa-exclamation-triangle mr-2"></i>
|
|
|
+ {{ reservationsError }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Reservations list -->
|
|
|
+ <div v-else-if="tripReservations.length > 0" class="space-y-4">
|
|
|
+ <div v-for="reservation in tripReservations" :key="reservation.id"
|
|
|
+ class="bg-white border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow">
|
|
|
+ <div class="flex justify-between items-start">
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="flex items-center gap-3 mb-3">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <i class="fas fa-user text-gray-500"></i>
|
|
|
+ <span class="font-medium text-gray-900 text-lg">
|
|
|
+ {{ reservation.user?.name || 'N/A' }} {{ reservation.user?.otherName || '' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <span class="reservation-status-badge" :class="getReservationStatusClass(reservation.status)">
|
|
|
+ {{ getReservationStatusLabel(reservation.status) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm text-gray-600 mb-3">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <i class="fas fa-chair text-blue-500"></i>
|
|
|
+ <span><strong>Places:</strong> {{ reservation.place.number }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <i class="fas fa-money-bill-wave text-green-500"></i>
|
|
|
+ <span><strong>Total:</strong> {{ reservation.price }} FCFA</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <i class="fas fa-credit-card text-purple-500"></i>
|
|
|
+ <span><strong>Paiement:</strong> {{ getPaymentMethodLabel(reservation.paymentMethod) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <i class="fas fa-calendar text-gray-500"></i>
|
|
|
+ <span><strong>Réservé le:</strong> {{ formatDateTime(reservation.dateAt) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="reservation.place" class="flex items-center gap-2 text-sm text-gray-600 mb-2">
|
|
|
+ <i class="fas fa-map-marker-alt text-red-500"></i>
|
|
|
+ <span><strong>Lieu de prise en charge:</strong> {{ reservation.place.name }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="reservation.notes" class="flex items-start gap-2 text-sm text-gray-600">
|
|
|
+ <i class="fas fa-sticky-note text-yellow-500 mt-1"></i>
|
|
|
+ <span><strong>Notes:</strong> {{ reservation.notes }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Pagination -->
|
|
|
+ <div v-if="reservationsPagination.totalPages > 1" class="flex justify-center items-center gap-3 mt-6 pt-4 border-t border-gray-200">
|
|
|
+ <button @click="changePage(reservationsPagination.currentPage - 1)"
|
|
|
+ :disabled="reservationsPagination.currentPage <= 1"
|
|
|
+ class="px-4 py-2 border border-gray-300 rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 transition-colors">
|
|
|
+ <i class="fas fa-chevron-left mr-1"></i> Précédent
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <span class="text-sm text-gray-600 px-4">
|
|
|
+ Page {{ reservationsPagination.currentPage }} sur {{ reservationsPagination.totalPages }}
|
|
|
+ ({{ reservationsPagination.totalItems }} réservations)
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <button @click="changePage(reservationsPagination.currentPage + 1)"
|
|
|
+ :disabled="reservationsPagination.currentPage >= reservationsPagination.totalPages"
|
|
|
+ class="px-4 py-2 border border-gray-300 rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 transition-colors">
|
|
|
+ Suivant <i class="fas fa-chevron-right ml-1"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Empty state -->
|
|
|
+ <div v-else class="text-center py-12 text-gray-500">
|
|
|
+ <i class="fas fa-calendar-times text-6xl mb-4 text-gray-300"></i>
|
|
|
+ <h3 class="text-lg font-medium text-gray-600 mb-2">Aucune réservation trouvée</h3>
|
|
|
+ <p class="text-gray-500">Ce voyage n'a pas encore de réservations.</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="py-4 px-5 border-t border-gray-200 flex justify-end">
|
|
|
+ <button class="bg-gray-100 text-gray-600 border-0 rounded py-2.5 px-4 cursor-pointer hover:bg-gray-200 transition-colors" @click="closeReservationsModal">
|
|
|
+ Fermer
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -377,11 +522,28 @@ const { success, error } = useToast()
|
|
|
const editingTrip = ref<Trip | null>(null)
|
|
|
const viewingTrip = ref<Trip | null>(null)
|
|
|
const showDeleteConfirm = ref(false)
|
|
|
+const showReservationsModal = ref(false)
|
|
|
const tripToDelete = ref<Trip | null>(null)
|
|
|
const searchQuery = ref('')
|
|
|
const driverFilter = ref('')
|
|
|
const statusFilter = ref('')
|
|
|
|
|
|
+// Variables pour les réservations
|
|
|
+const tripReservations = ref<any[]>([])
|
|
|
+const loadingReservations = ref(false)
|
|
|
+const reservationsError = ref('')
|
|
|
+const reservationFilters = ref({
|
|
|
+ status: '',
|
|
|
+ page: 1,
|
|
|
+ limit: 10
|
|
|
+})
|
|
|
+const reservationsPagination = ref({
|
|
|
+ currentPage: 1,
|
|
|
+ totalPages: 1,
|
|
|
+ totalItems: 0,
|
|
|
+ itemsPerPage: 10
|
|
|
+})
|
|
|
+
|
|
|
const formData = ref<Trip>({
|
|
|
id: 0,
|
|
|
departureCity: 0,
|
|
|
@@ -435,7 +597,7 @@ const filteredTrips = computed(() => {
|
|
|
const searchLower = searchQuery.value.toLowerCase()
|
|
|
const departureMatch = getStationName(trip.departStationId).toLowerCase().includes(searchLower)
|
|
|
const arrivalMatch = getStationName(trip.arriveStationId).toLowerCase().includes(searchLower)
|
|
|
- const driverMatch = getDriverName(trip.driverId).toLowerCase().includes(searchLower)
|
|
|
+ const driverMatch = getDriverName().toLowerCase().includes(searchLower)
|
|
|
|
|
|
if (searchQuery.value && !departureMatch && !arrivalMatch && !driverMatch) {
|
|
|
return false
|
|
|
@@ -455,6 +617,26 @@ const filteredTrips = computed(() => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+// Propriétés calculées pour les totaux des paiements
|
|
|
+const totalOnlinePayments = computed(() => {
|
|
|
+ if (!viewingTrip.value || !tripReservations.value.length) return 0
|
|
|
+
|
|
|
+ return tripReservations.value
|
|
|
+ .filter(reservation =>
|
|
|
+ reservation.paymentMethod === 'CARD' ||
|
|
|
+ reservation.paymentMethod === 'MOBILE'
|
|
|
+ )
|
|
|
+ .reduce((total, reservation) => total + (reservation.price || 0), 0)
|
|
|
+})
|
|
|
+
|
|
|
+const totalCashPayments = computed(() => {
|
|
|
+ if (!viewingTrip.value || !tripReservations.value.length) return 0
|
|
|
+
|
|
|
+ return tripReservations.value
|
|
|
+ .filter(reservation => reservation.paymentMethod === 'CASH')
|
|
|
+ .reduce((total, reservation) => total + (reservation.price || 0), 0)
|
|
|
+})
|
|
|
+
|
|
|
// Fonctions utilitaires
|
|
|
const formatDateTime = (dateTime: string): string => {
|
|
|
if (!dateTime) return ''
|
|
|
@@ -473,18 +655,24 @@ const getStationName = (stationId: number): string => {
|
|
|
return station ? station.name : 'Station inconnue'
|
|
|
}
|
|
|
|
|
|
-const getDriverName = (driverId: number): string => {
|
|
|
+const getDriverName = (): string => {
|
|
|
console.log('drivers:', drivers.value)
|
|
|
- const driver = drivers.value.find(d => d.id === driverId)
|
|
|
- return driver ? `${driver.name} ${driver.otherName}` : 'Inconnu'
|
|
|
+ return drivers.value[0] ? `${drivers.value[0].name} ${drivers.value[0].otherName}` : 'Inconnu'
|
|
|
}
|
|
|
|
|
|
const getVehicleName = (vehicleId: number): string => {
|
|
|
const vehicle = vehicles.value.find(v => v.id === vehicleId)
|
|
|
- return vehicle ? `${vehicle.model} ${vehicle.type}` : 'Inconnu'
|
|
|
+ return vehicle ? `${vehicle.model} | ${vehicle.type}` : 'Inconnu'
|
|
|
}
|
|
|
|
|
|
-const getStatusLabel = (status: string): string => {
|
|
|
+const getStatusLabel = (trip: any): string => {
|
|
|
+ // Vérifier d'abord si les inscriptions sont fermées
|
|
|
+ if (trip.registrationClosed) {
|
|
|
+ return 'En cours'
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sinon, utiliser le statut normal
|
|
|
+ const status = trip.status || 'upcoming'
|
|
|
switch (status) {
|
|
|
case 'upcoming': return 'À venir'
|
|
|
case 'ongoing': return 'En cours'
|
|
|
@@ -494,7 +682,14 @@ const getStatusLabel = (status: string): string => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const getStatusClass = (status: string): string => {
|
|
|
+const getStatusClass = (trip: any): string => {
|
|
|
+ // Vérifier d'abord si les inscriptions sont fermées
|
|
|
+ if (trip.registrationClosed) {
|
|
|
+ return 'status-ongoing'
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sinon, utiliser le statut normal
|
|
|
+ const status = trip.status || 'upcoming'
|
|
|
switch (status) {
|
|
|
case 'upcoming': return 'status-upcoming'
|
|
|
case 'ongoing': return 'status-ongoing'
|
|
|
@@ -792,9 +987,131 @@ const handleMessages = () => {
|
|
|
const handleUserMenu = () => {
|
|
|
// Gérer l'affichage du menu utilisateur
|
|
|
}
|
|
|
+
|
|
|
+// Méthodes pour gérer les réservations
|
|
|
+const loadTripReservations = async () => {
|
|
|
+ if (!viewingTrip.value?.id) return
|
|
|
+
|
|
|
+ loadingReservations.value = true
|
|
|
+ reservationsError.value = ''
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await TripService.getTripReservations(
|
|
|
+ viewingTrip.value.id,
|
|
|
+ reservationFilters.value.page,
|
|
|
+ reservationFilters.value.limit,
|
|
|
+ reservationFilters.value.status
|
|
|
+ )
|
|
|
+ console.log('Résultat getTripReservations:', result)
|
|
|
+ tripReservations.value = result.reservations
|
|
|
+ reservationsPagination.value = {
|
|
|
+ currentPage: result.pagination.currentPage,
|
|
|
+ totalPages: result.pagination.totalPages,
|
|
|
+ totalItems: result.pagination.totalItems,
|
|
|
+ itemsPerPage: result.pagination.itemsPerPage
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ console.error('Erreur lors du chargement des réservations:', err)
|
|
|
+ reservationsError.value = err.message || 'Erreur lors du chargement des réservations'
|
|
|
+ tripReservations.value = []
|
|
|
+ } finally {
|
|
|
+ loadingReservations.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const changePage = (newPage: number) => {
|
|
|
+ if (newPage >= 1 && newPage <= reservationsPagination.value.totalPages) {
|
|
|
+ reservationFilters.value.page = newPage
|
|
|
+ loadTripReservations()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getReservationStatusLabel = (status: string): string => {
|
|
|
+ switch (status) {
|
|
|
+ case 'pending': return 'En attente'
|
|
|
+ case 'confirmed': return 'Confirmée'
|
|
|
+ case 'cancelled': return 'Annulée'
|
|
|
+ case 'completed': return 'Terminée'
|
|
|
+ default: return status
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getReservationStatusClass = (status: string): string => {
|
|
|
+ switch (status) {
|
|
|
+ case 'pending': return 'bg-yellow-100 text-yellow-800 px-2 py-1 rounded-full text-xs'
|
|
|
+ case 'confirmed': return 'bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs'
|
|
|
+ case 'cancelled': return 'bg-red-100 text-red-800 px-2 py-1 rounded-full text-xs'
|
|
|
+ case 'completed': return 'bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs'
|
|
|
+ default: return 'bg-gray-100 text-gray-800 px-2 py-1 rounded-full text-xs'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getPaymentMethodLabel = (method: string): string => {
|
|
|
+ switch (method) {
|
|
|
+ case 'cash': return 'Espèces'
|
|
|
+ case 'card': return 'Carte'
|
|
|
+ case 'mobile': return 'Mobile Money'
|
|
|
+ case 'online': return 'En ligne'
|
|
|
+ default: return method || 'Non spécifié'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const formatDate = (dateString: string): string => {
|
|
|
+ if (!dateString) return 'N/A'
|
|
|
+ const date = new Date(dateString)
|
|
|
+ return date.toLocaleDateString('fr-FR', {
|
|
|
+ day: '2-digit',
|
|
|
+ month: '2-digit',
|
|
|
+ year: 'numeric'
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// Méthodes pour gérer la modal des réservations
|
|
|
+const openReservationsModal = (trip: Trip) => {
|
|
|
+ viewingTrip.value = trip
|
|
|
+ showReservationsModal.value = true
|
|
|
+ // Réinitialiser les filtres et la pagination
|
|
|
+ reservationFilters.value = {
|
|
|
+ status: '',
|
|
|
+ page: 1,
|
|
|
+ limit: 10
|
|
|
+ }
|
|
|
+ reservationsPagination.value = {
|
|
|
+ currentPage: 1,
|
|
|
+ totalPages: 1,
|
|
|
+ totalItems: 0,
|
|
|
+ itemsPerPage: 10
|
|
|
+ }
|
|
|
+ // Charger les réservations
|
|
|
+ loadTripReservations()
|
|
|
+}
|
|
|
+
|
|
|
+const closeReservationsModal = () => {
|
|
|
+ showReservationsModal.value = false
|
|
|
+ viewingTrip.value = null
|
|
|
+ tripReservations.value = []
|
|
|
+ reservationsError.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+// Watcher pour charger les réservations quand un voyage est sélectionné pour visualisation
|
|
|
+watch(() => viewingTrip.value, (newTrip) => {
|
|
|
+ if (newTrip) {
|
|
|
+ // Réinitialiser les filtres et la pagination
|
|
|
+ reservationFilters.value = {
|
|
|
+ status: '',
|
|
|
+ page: 1,
|
|
|
+ limit: 10
|
|
|
+ }
|
|
|
+ loadTripReservations()
|
|
|
+ } else {
|
|
|
+ // Nettoyer les données quand la modal se ferme
|
|
|
+ tripReservations.value = []
|
|
|
+ reservationsError.value = ''
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
// Initialisation des données
|
|
|
onMounted(async () => {
|
|
|
- console.log('viewingTrip:', viewingTrip.value)
|
|
|
await loadStations()
|
|
|
await loadDrivers()
|
|
|
await loadVehicles()
|
|
|
@@ -1286,6 +1603,36 @@ watch(() => formData.value.vehicleId, (newVehicleId) => {
|
|
|
border-radius: 5px;
|
|
|
}
|
|
|
|
|
|
+/* Styles pour l'élément cliquable des réservations */
|
|
|
+.info-item.clickable {
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.info-item.clickable:hover {
|
|
|
+ background-color: #f8f9fc;
|
|
|
+ border-color: #4e73df;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.info-item.clickable .info-value {
|
|
|
+ color: #4e73df;
|
|
|
+}
|
|
|
+
|
|
|
+.info-item.clickable:hover .info-value {
|
|
|
+ color: #2e59d9;
|
|
|
+}
|
|
|
+
|
|
|
+/* Styles pour les badges de statut des réservations */
|
|
|
+.reservation-status-badge {
|
|
|
+ display: inline-block;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
.trip-notes h4 {
|
|
|
margin: 0 0 10px 0;
|
|
|
color: #333;
|