|
@@ -40,7 +40,7 @@
|
|
|
<select v-model="driverFilter">
|
|
<select v-model="driverFilter">
|
|
|
<option value="">Tous les chauffeurs</option>
|
|
<option value="">Tous les chauffeurs</option>
|
|
|
<option v-for="driver in drivers" :key="driver.id" :value="driver.id">
|
|
<option v-for="driver in drivers" :key="driver.id" :value="driver.id">
|
|
|
- {{ driver.firstName }} {{ driver.lastName }}
|
|
|
|
|
|
|
+ {{ driver.name }} {{ driver.otherName }}
|
|
|
</option>
|
|
</option>
|
|
|
</select>
|
|
</select>
|
|
|
</div>
|
|
</div>
|
|
@@ -57,7 +57,13 @@
|
|
|
|
|
|
|
|
<!-- Tableau des voyages -->
|
|
<!-- Tableau des voyages -->
|
|
|
<div class="trip-table-container">
|
|
<div class="trip-table-container">
|
|
|
- <table class="trip-table" v-if="filteredTrips.length > 0">
|
|
|
|
|
|
|
+ <!-- Indicateur de chargement -->
|
|
|
|
|
+ <div v-if="loading" class="loading-state">
|
|
|
|
|
+ <div class="loading-spinner"></div>
|
|
|
|
|
+ <p>Chargement des voyages...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <table class="trip-table" v-else-if="filteredTrips.length > 0">
|
|
|
<thead>
|
|
<thead>
|
|
|
<tr>
|
|
<tr>
|
|
|
<th>Départ</th>
|
|
<th>Départ</th>
|
|
@@ -73,16 +79,16 @@
|
|
|
</thead>
|
|
</thead>
|
|
|
<tbody>
|
|
<tbody>
|
|
|
<tr v-for="trip in filteredTrips" :key="trip.id">
|
|
<tr v-for="trip in filteredTrips" :key="trip.id">
|
|
|
- <td>{{ getCityName(trip.departureCity) }}</td>
|
|
|
|
|
- <td>{{ getCityName(trip.arrivalCity) }}</td>
|
|
|
|
|
- <td>{{ formatDateTime(trip.departureDateTime) }}</td>
|
|
|
|
|
- <td>{{ formatDateTime(trip.estimatedArrivalDateTime) }}</td>
|
|
|
|
|
- <td>{{ trip.pricePerSeat }} FCFA</td>
|
|
|
|
|
- <td>{{ trip.availableSeats }}/{{ trip.totalSeats }}</td>
|
|
|
|
|
|
|
+ <td>{{ getStationName(trip.departStationId) }}</td>
|
|
|
|
|
+ <td>{{ getStationName(trip.arriveStationId) }}</td>
|
|
|
|
|
+ <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>{{ getDriverName(trip.driverId) }}</td>
|
|
|
<td>
|
|
<td>
|
|
|
<span class="status-badge" :class="getStatusClass(trip.status)">
|
|
<span class="status-badge" :class="getStatusClass(trip.status)">
|
|
|
- {{ getStatusLabel(trip.status) }}
|
|
|
|
|
|
|
+ {{ getStatusLabel(trip.status || 'upcoming') }}
|
|
|
</span>
|
|
</span>
|
|
|
</td>
|
|
</td>
|
|
|
<td class="actions-cell">
|
|
<td class="actions-cell">
|
|
@@ -99,8 +105,16 @@
|
|
|
</tr>
|
|
</tr>
|
|
|
</tbody>
|
|
</tbody>
|
|
|
</table>
|
|
</table>
|
|
|
|
|
+
|
|
|
<div class="no-data" v-else>
|
|
<div class="no-data" v-else>
|
|
|
- <p>Aucun voyage trouvé</p>
|
|
|
|
|
|
|
+ <div class="no-data-icon">
|
|
|
|
|
+ <i class="fas fa-route"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <h3>{{ trips.length === 0 ? 'Aucun voyage enregistré' : 'Aucun voyage trouvé' }}</h3>
|
|
|
|
|
+ <p>{{ trips.length === 0 ? 'Commencez par ajouter votre premier voyage.' : 'Essayez de modifier vos critères de recherche.' }}</p>
|
|
|
|
|
+ <button v-if="trips.length === 0" class="add-button" @click="showAddForm = true">
|
|
|
|
|
+ <i class="fas fa-plus"></i> Ajouter un voyage
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -117,20 +131,20 @@
|
|
|
<div class="p-5">
|
|
<div class="p-5">
|
|
|
<div class="form-row">
|
|
<div class="form-row">
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Ville de départ <span class="required">*</span></label>
|
|
|
|
|
|
|
+ <label>Station de départ <span class="required">*</span></label>
|
|
|
<select v-model="formData.departureCity" required>
|
|
<select v-model="formData.departureCity" required>
|
|
|
- <option value="">Sélectionner une ville</option>
|
|
|
|
|
- <option v-for="city in cities" :key="city.id" :value="city.id">
|
|
|
|
|
- {{ city.name }} {{ city?.commune }}
|
|
|
|
|
|
|
+ <option value="">Sélectionner une station</option>
|
|
|
|
|
+ <option v-for="station in stationStore.stations" :key="station.id" :value="station.id">
|
|
|
|
|
+ {{ station.name }}
|
|
|
</option>
|
|
</option>
|
|
|
</select>
|
|
</select>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Ville d'arrivée <span class="required">*</span></label>
|
|
|
|
|
|
|
+ <label>Station d'arrivée <span class="required">*</span></label>
|
|
|
<select v-model="formData.arrivalCity" required>
|
|
<select v-model="formData.arrivalCity" required>
|
|
|
- <option value="">Sélectionner une ville</option>
|
|
|
|
|
- <option v-for="city in cities" :key="city.id" :value="city.id">
|
|
|
|
|
- {{ city.name }}
|
|
|
|
|
|
|
+ <option value="">Sélectionner une station</option>
|
|
|
|
|
+ <option v-for="station in stationStore.stations" :key="station.id" :value="station.id">
|
|
|
|
|
+ {{ station.name }}
|
|
|
</option>
|
|
</option>
|
|
|
</select>
|
|
</select>
|
|
|
</div>
|
|
</div>
|
|
@@ -149,65 +163,62 @@
|
|
|
|
|
|
|
|
<div class="form-row">
|
|
<div class="form-row">
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Date et heure d'arrivée réelle</label>
|
|
|
|
|
- <input type="datetime-local" v-model="formData.actualArrivalDateTime">
|
|
|
|
|
|
|
+ <label>Véhicule <span class="required">*</span></label>
|
|
|
|
|
+ <select v-model="formData.vehicleId" required>
|
|
|
|
|
+ <option value="">Sélectionner un véhicule</option>
|
|
|
|
|
+ <option v-for="vehicle in vehicles" :key="vehicle.id" :value="vehicle.id">
|
|
|
|
|
+ {{ vehicle.type }} {{ vehicle.model }} - {{ vehicle.number_places }} places
|
|
|
|
|
+ </option>
|
|
|
|
|
+ </select>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Prix par place (FCFA) <span class="required">*</span></label>
|
|
|
|
|
- <input type="number" v-model="formData.pricePerSeat" min="0" required>
|
|
|
|
|
|
|
+ <label>Chauffeur <span class="required">*</span></label>
|
|
|
|
|
+ <select v-model="formData.driverId" required>
|
|
|
|
|
+ <option value="">Sélectionner un chauffeur</option>
|
|
|
|
|
+ <option v-for="driver in drivers" :key="driver.id" :value="driver.id">
|
|
|
|
|
+ {{ driver.name }} {{ driver.otherName }}
|
|
|
|
|
+ </option>
|
|
|
|
|
+ </select>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-row">
|
|
<div class="form-row">
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Véhicule <span class="required">*</span></label>
|
|
|
|
|
- <select v-model="formData.vehicleId" required @change="updateTotalSeats">
|
|
|
|
|
- <option value="">Sélectionner un véhicule</option>
|
|
|
|
|
- <option v-for="vehicle in vehicles" :key="vehicle.id" :value="vehicle.id">
|
|
|
|
|
- {{ vehicle.make }} {{ vehicle.model }} ({{ vehicle.registrationNumber }})
|
|
|
|
|
- </option>
|
|
|
|
|
- </select>
|
|
|
|
|
|
|
+ <label>Prix par place <span class="required">*</span></label>
|
|
|
|
|
+ <input type="number" v-model="formData.pricePerSeat" min="0" step="100" required placeholder="Prix en FCFA">
|
|
|
</div>
|
|
</div>
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Nombre total de places</label>
|
|
|
|
|
- <input type="number" v-model="formData.totalSeats" readonly>
|
|
|
|
|
|
|
+ <label>Nombre total de places <span class="required">*</span></label>
|
|
|
|
|
+ <input type="number" v-model="formData.totalSeats" min="1" required placeholder="Nombre de places">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-row">
|
|
<div class="form-row">
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Places disponibles <span class="required">*</span></label>
|
|
|
|
|
- <input type="number" v-model="formData.availableSeats" min="0" :max="formData.totalSeats" required>
|
|
|
|
|
|
|
+ <label>Statut</label>
|
|
|
|
|
+ <select v-model="formData.status">
|
|
|
|
|
+ <option value="upcoming">À venir</option>
|
|
|
|
|
+ <option value="ongoing">En cours</option>
|
|
|
|
|
+ <option value="completed">Terminé</option>
|
|
|
|
|
+ <option value="cancelled">Annulé</option>
|
|
|
|
|
+ </select>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="form-group">
|
|
<div class="form-group">
|
|
|
- <label>Chauffeur <span class="required">*</span></label>
|
|
|
|
|
- <select v-model="formData.driverId" required>
|
|
|
|
|
- <option value="">Sélectionner un chauffeur</option>
|
|
|
|
|
- <option v-for="driver in drivers" :key="driver.id" :value="driver.id">
|
|
|
|
|
- {{ driver.firstName }} {{ driver.lastName }}
|
|
|
|
|
- </option>
|
|
|
|
|
- </select>
|
|
|
|
|
|
|
+ <label>Date d'arrivée réelle</label>
|
|
|
|
|
+ <input type="datetime-local" v-model="formData.actualArrivalDateTime">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="form-group">
|
|
|
|
|
- <label>Statut <span class="required">*</span></label>
|
|
|
|
|
- <select v-model="formData.status" required>
|
|
|
|
|
- <option value="upcoming">À venir</option>
|
|
|
|
|
- <option value="ongoing">En cours</option>
|
|
|
|
|
- <option value="completed">Terminé</option>
|
|
|
|
|
- <option value="cancelled">Annulé</option>
|
|
|
|
|
- </select>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="form-group">
|
|
|
|
|
- <label>Notes</label>
|
|
|
|
|
- <textarea v-model="formData.notes" rows="3"></textarea>
|
|
|
|
|
|
|
+ <div class="form-row">
|
|
|
|
|
+ <div class="form-group full-width">
|
|
|
|
|
+ <label>Notes</label>
|
|
|
|
|
+ <textarea v-model="formData.notes" rows="3" placeholder="Notes optionnelles sur le voyage..."></textarea>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="py-4 px-5 border-t border-gray-200 flex justify-end gap-2.5">
|
|
<div class="py-4 px-5 border-t border-gray-200 flex justify-end gap-2.5">
|
|
|
<button class="bg-gray-100 text-gray-600 border-0 rounded py-2.5 px-4 cursor-pointer" @click="closeForm">Annuler</button>
|
|
<button class="bg-gray-100 text-gray-600 border-0 rounded py-2.5 px-4 cursor-pointer" @click="closeForm">Annuler</button>
|
|
|
- <button class="bg-blue-500 text-white border-0 rounded py-2.5 px-4 cursor-pointer hover:bg-blue-600" @click="saveTrip" :disabled="!isFormValid">
|
|
|
|
|
|
|
+ <button class="bg-blue-500 text-white border-0 rounded py-2.5 px-4 cursor-pointer hover:bg-blue-600" @click="saveTrip" > <!-- :disabled="!isFormValid" à vérifier et a ajouter -->
|
|
|
{{ editingTrip ? 'Mettre à jour' : 'Enregistrer' }}
|
|
{{ editingTrip ? 'Mettre à jour' : 'Enregistrer' }}
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
@@ -227,26 +238,26 @@
|
|
|
<div class="trip-details">
|
|
<div class="trip-details">
|
|
|
<div class="trip-route">
|
|
<div class="trip-route">
|
|
|
<div class="departure">
|
|
<div class="departure">
|
|
|
- <h4>{{ getCityName(viewingTrip.departureCity) }}</h4>
|
|
|
|
|
- <p>{{ formatDateTime(viewingTrip.departureDateTime) }}</p>
|
|
|
|
|
|
|
+ <h4>{{ getStationName(viewingTrip.departStationId) }}</h4>
|
|
|
|
|
+ <p>{{ formatDateTime(viewingTrip.departAt) }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="route-line">
|
|
<div class="route-line">
|
|
|
<i class="fas fa-arrow-right"></i>
|
|
<i class="fas fa-arrow-right"></i>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="arrival">
|
|
<div class="arrival">
|
|
|
- <h4>{{ getCityName(viewingTrip.arrivalCity) }}</h4>
|
|
|
|
|
- <p>{{ formatDateTime(viewingTrip.estimatedArrivalDateTime) }}</p>
|
|
|
|
|
|
|
+ <h4>{{ getStationName(viewingTrip.arriveStationId) }}</h4>
|
|
|
|
|
+ <p>{{ formatDateTime(viewingTrip.arriveAt) }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="trip-info-grid">
|
|
<div class="trip-info-grid">
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Prix par place</div>
|
|
<div class="info-label">Prix par place</div>
|
|
|
- <div class="info-value">{{ viewingTrip.pricePerSeat }} FCFA</div>
|
|
|
|
|
|
|
+ <div class="info-value">{{ viewingTrip.pricePerSeat || 0 }} FCFA</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Places disponibles</div>
|
|
<div class="info-label">Places disponibles</div>
|
|
|
- <div class="info-value">{{ viewingTrip.availableSeats }}/{{ viewingTrip.totalSeats }}</div>
|
|
|
|
|
|
|
+ <div class="info-value">{{ viewingTrip.availableSeats || 0 }}/{{ viewingTrip.totalSeats || 0 }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Chauffeur</div>
|
|
<div class="info-label">Chauffeur</div>
|
|
@@ -254,13 +265,13 @@
|
|
|
</div>
|
|
</div>
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Véhicule</div>
|
|
<div class="info-label">Véhicule</div>
|
|
|
- <div class="info-value">{{ getVehicleName(viewingTrip.vehicleId) }}</div>
|
|
|
|
|
|
|
+ <div class="info-value">{{ getVehicleName(viewingTrip.vehiculeId) }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Statut</div>
|
|
<div class="info-label">Statut</div>
|
|
|
<div class="info-value">
|
|
<div class="info-value">
|
|
|
- <span class="status-badge" :class="getStatusClass(viewingTrip.status)">
|
|
|
|
|
- {{ getStatusLabel(viewingTrip.status) }}
|
|
|
|
|
|
|
+ <span class="status-badge" :class="getStatusClass(viewingTrip.status || 'upcoming')">
|
|
|
|
|
+ {{ getStatusLabel(viewingTrip.status || 'upcoming') }}
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -271,16 +282,16 @@
|
|
|
<!-- Ajout des sommes -->
|
|
<!-- Ajout des sommes -->
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Totale des places prises</div>
|
|
<div class="info-label">Totale des places prises</div>
|
|
|
- <div class="info-value">{{ (viewingTrip.totalSeats - viewingTrip.availableSeats) * viewingTrip.pricePerSeat }} FCFA</div>
|
|
|
|
|
|
|
+ <div class="info-value">{{ ((viewingTrip.totalSeats || 0) - (viewingTrip.availableSeats || 0)) * (viewingTrip.pricePerSeat || 0) }} FCFA</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Totale des places non occupés</div>
|
|
<div class="info-label">Totale des places non occupés</div>
|
|
|
- <div class="info-value">{{ viewingTrip.availableSeats * viewingTrip.pricePerSeat }} FCFA</div>
|
|
|
|
|
|
|
+ <div class="info-value">{{ (viewingTrip.availableSeats || 0) * (viewingTrip.pricePerSeat || 0) }} FCFA</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="info-item">
|
|
<div class="info-item">
|
|
|
<div class="info-label">Totale des places en espèces</div>
|
|
<div class="info-label">Totale des places en espèces</div>
|
|
|
<!-- Calcul à faire -->
|
|
<!-- Calcul à faire -->
|
|
|
- <div class="info-value">{{ viewingTrip.availableSeats * viewingTrip.pricePerSeat }} FCFA</div>
|
|
|
|
|
|
|
+ <div class="info-value">{{ (viewingTrip.availableSeats || 0) * (viewingTrip.pricePerSeat || 0) }} FCFA</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -293,9 +304,9 @@
|
|
|
<div class="trip-map" v-if="viewingTrip">
|
|
<div class="trip-map" v-if="viewingTrip">
|
|
|
<h4>Trajet sur la carte</h4>
|
|
<h4>Trajet sur la carte</h4>
|
|
|
<TripMap
|
|
<TripMap
|
|
|
- :departureCityId="viewingTrip.departureCity"
|
|
|
|
|
- :arrivalCityId="viewingTrip.arrivalCity"
|
|
|
|
|
- :cities="cities"
|
|
|
|
|
|
|
+ :departureCityId="viewingTrip.departStationId"
|
|
|
|
|
+ :arrivalCityId="viewingTrip.arriveStationId"
|
|
|
|
|
+ :cities="stationStore.stations"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -335,57 +346,29 @@ import TopBar from '@/components/TopBar.vue'
|
|
|
import 'leaflet/dist/leaflet.css'
|
|
import 'leaflet/dist/leaflet.css'
|
|
|
import TripMap from '@/components/TripMap.vue'
|
|
import TripMap from '@/components/TripMap.vue'
|
|
|
import { useUserStore } from '@/stores/useUserStore'
|
|
import { useUserStore } from '@/stores/useUserStore'
|
|
|
|
|
+import { useStationStore } from '@/stores/useStationStore'
|
|
|
import { useToast } from '@/composables/useToast'
|
|
import { useToast } from '@/composables/useToast'
|
|
|
|
|
+import TripService from '@/services/TripService'
|
|
|
|
|
+import VehiculeService from '@/services/VehiculeService'
|
|
|
|
|
+import CompanyService from '@/services/CompanyService'
|
|
|
|
|
+import type { Trip, Driver, Vehicle, Station } from '@/types'
|
|
|
|
|
+import { Role } from '@/Enum/Role'
|
|
|
|
|
|
|
|
-interface City {
|
|
|
|
|
- id: number
|
|
|
|
|
- name: string
|
|
|
|
|
- latitude: number
|
|
|
|
|
- longitude: number
|
|
|
|
|
- commune: string
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface Driver {
|
|
|
|
|
- id: number
|
|
|
|
|
- firstName: string
|
|
|
|
|
- lastName: string
|
|
|
|
|
- phone: string
|
|
|
|
|
- email?: string
|
|
|
|
|
- companyId: number
|
|
|
|
|
- licenseNumber: string
|
|
|
|
|
- active: boolean
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface Vehicle {
|
|
|
|
|
- id: number
|
|
|
|
|
- make: string
|
|
|
|
|
- model: string
|
|
|
|
|
- registrationNumber: string
|
|
|
|
|
- capacity: number
|
|
|
|
|
- companyId: number
|
|
|
|
|
- active: boolean
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface Trip {
|
|
|
|
|
- id: number
|
|
|
|
|
- departureCity: number
|
|
|
|
|
- arrivalCity: number
|
|
|
|
|
- departureDateTime: string
|
|
|
|
|
- estimatedArrivalDateTime: string
|
|
|
|
|
- actualArrivalDateTime?: string
|
|
|
|
|
- pricePerSeat: number
|
|
|
|
|
- totalSeats: number
|
|
|
|
|
- availableSeats: number
|
|
|
|
|
- driverId: number
|
|
|
|
|
- vehicleId: number
|
|
|
|
|
- status: 'upcoming' | 'ongoing' | 'completed' | 'cancelled'
|
|
|
|
|
- notes?: string
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
|
|
+// Variables réactives pour les données
|
|
|
const trips = ref<Trip[]>([])
|
|
const trips = ref<Trip[]>([])
|
|
|
-const cities = ref<City[]>([])
|
|
|
|
|
-const drivers = ref<Driver[]>([])
|
|
|
|
|
|
|
+const drivers = ref<any[]>([])
|
|
|
const vehicles = ref<Vehicle[]>([])
|
|
const vehicles = ref<Vehicle[]>([])
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
|
|
+const stationStore = useStationStore()
|
|
|
|
|
+const pagination = ref({
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ limit: 10,
|
|
|
|
|
+ totalPages: 1
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// Variables pour les modales et formulaires
|
|
|
const showAddForm = ref(false)
|
|
const showAddForm = ref(false)
|
|
|
|
|
|
|
|
// Toast notifications
|
|
// Toast notifications
|
|
@@ -398,7 +381,8 @@ const searchQuery = ref('')
|
|
|
const driverFilter = ref('')
|
|
const driverFilter = ref('')
|
|
|
const statusFilter = ref('')
|
|
const statusFilter = ref('')
|
|
|
|
|
|
|
|
-const formData = ref({
|
|
|
|
|
|
|
+const formData = ref<Trip>({
|
|
|
|
|
+ id: 0,
|
|
|
departureCity: 0,
|
|
departureCity: 0,
|
|
|
arrivalCity: 0,
|
|
arrivalCity: 0,
|
|
|
departureDateTime: '',
|
|
departureDateTime: '',
|
|
@@ -409,29 +393,30 @@ const formData = ref({
|
|
|
availableSeats: 0,
|
|
availableSeats: 0,
|
|
|
driverId: 0,
|
|
driverId: 0,
|
|
|
vehicleId: 0,
|
|
vehicleId: 0,
|
|
|
- status: 'upcoming' as 'upcoming' | 'ongoing' | 'completed' | 'cancelled',
|
|
|
|
|
- notes: ''
|
|
|
|
|
|
|
+ status: 'upcoming',
|
|
|
|
|
+ notes: '',
|
|
|
|
|
+ companyId: 0,
|
|
|
|
|
+ createdAt: '',
|
|
|
|
|
+ updatedAt: ''
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const isFormValid = computed(() => {
|
|
const isFormValid = computed(() => {
|
|
|
- return formData.value.departureCity &&
|
|
|
|
|
- formData.value.arrivalCity &&
|
|
|
|
|
- formData.value.departureDateTime &&
|
|
|
|
|
- formData.value.estimatedArrivalDateTime &&
|
|
|
|
|
- formData.value.pricePerSeat > 0 &&
|
|
|
|
|
- formData.value.totalSeats > 0 &&
|
|
|
|
|
- formData.value.availableSeats >= 0 &&
|
|
|
|
|
- formData.value.availableSeats <= formData.value.totalSeats &&
|
|
|
|
|
- formData.value.driverId &&
|
|
|
|
|
- formData.value.vehicleId
|
|
|
|
|
|
|
+ return formData.value.departureCity &&
|
|
|
|
|
+ formData.value.arrivalCity &&
|
|
|
|
|
+ formData.value.departureDateTime &&
|
|
|
|
|
+ formData.value.estimatedArrivalDateTime &&
|
|
|
|
|
+ formData.value.vehicleId &&
|
|
|
|
|
+ formData.value.driverId &&
|
|
|
|
|
+ formData.value.pricePerSeat > 0 &&
|
|
|
|
|
+ formData.value.totalSeats > 0
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const filteredTrips = computed(() => {
|
|
const filteredTrips = computed(() => {
|
|
|
return trips.value.filter(trip => {
|
|
return trips.value.filter(trip => {
|
|
|
// Filtre de recherche
|
|
// Filtre de recherche
|
|
|
const searchLower = searchQuery.value.toLowerCase()
|
|
const searchLower = searchQuery.value.toLowerCase()
|
|
|
- const departureMatch = getCityName(trip.departureCity).toLowerCase().includes(searchLower)
|
|
|
|
|
- const arrivalMatch = getCityName(trip.arrivalCity).toLowerCase().includes(searchLower)
|
|
|
|
|
|
|
+ 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(trip.driverId).toLowerCase().includes(searchLower)
|
|
|
|
|
|
|
|
if (searchQuery.value && !departureMatch && !arrivalMatch && !driverMatch) {
|
|
if (searchQuery.value && !departureMatch && !arrivalMatch && !driverMatch) {
|
|
@@ -465,19 +450,19 @@ const formatDateTime = (dateTime: string): string => {
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const getCityName = (cityId: number): string => {
|
|
|
|
|
- const city = cities.value.find(c => c.id === cityId)
|
|
|
|
|
- return city ? city.name : 'Inconnue'
|
|
|
|
|
|
|
+const getStationName = (stationId: number): string => {
|
|
|
|
|
+ const station = stationStore.stations.find(s => s.id === stationId)
|
|
|
|
|
+ return station ? station.name : 'Station inconnue'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const getDriverName = (driverId: number): string => {
|
|
const getDriverName = (driverId: number): string => {
|
|
|
const driver = drivers.value.find(d => d.id === driverId)
|
|
const driver = drivers.value.find(d => d.id === driverId)
|
|
|
- return driver ? `${driver.firstName} ${driver.lastName}` : 'Inconnu'
|
|
|
|
|
|
|
+ return driver ? `${driver.name} ${driver.otherName}` : 'Inconnu'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const getVehicleName = (vehicleId: number): string => {
|
|
const getVehicleName = (vehicleId: number): string => {
|
|
|
const vehicle = vehicles.value.find(v => v.id === vehicleId)
|
|
const vehicle = vehicles.value.find(v => v.id === vehicleId)
|
|
|
- return vehicle ? `${vehicle.make} ${vehicle.model} (${vehicle.registrationNumber})` : 'Inconnu'
|
|
|
|
|
|
|
+ return vehicle ? `${vehicle.model} ${vehicle.type}` : 'Inconnu'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const getStatusLabel = (status: string): string => {
|
|
const getStatusLabel = (status: string): string => {
|
|
@@ -509,6 +494,7 @@ const closeForm = () => {
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
const resetForm = () => {
|
|
|
formData.value = {
|
|
formData.value = {
|
|
|
|
|
+ id: 0,
|
|
|
departureCity: 0,
|
|
departureCity: 0,
|
|
|
arrivalCity: 0,
|
|
arrivalCity: 0,
|
|
|
departureDateTime: '',
|
|
departureDateTime: '',
|
|
@@ -520,22 +506,116 @@ const resetForm = () => {
|
|
|
driverId: 0,
|
|
driverId: 0,
|
|
|
vehicleId: 0,
|
|
vehicleId: 0,
|
|
|
status: 'upcoming',
|
|
status: 'upcoming',
|
|
|
- notes: ''
|
|
|
|
|
|
|
+ notes: '',
|
|
|
|
|
+ companyId: 0,
|
|
|
|
|
+ createdAt: '',
|
|
|
|
|
+ updatedAt: ''
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const updateTotalSeats = () => {
|
|
|
|
|
- const vehicle = vehicles.value.find(v => v.id === formData.value.vehicleId)
|
|
|
|
|
- if (vehicle) {
|
|
|
|
|
- formData.value.totalSeats = vehicle.capacity
|
|
|
|
|
- formData.value.availableSeats = vehicle.capacity
|
|
|
|
|
|
|
+// Store utilisateur (déjà déclaré plus haut)
|
|
|
|
|
+
|
|
|
|
|
+// Fonctions de chargement des données
|
|
|
|
|
+const loadTrips = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const userRole = userStore.userRole
|
|
|
|
|
+ const companyId = userStore.companyId
|
|
|
|
|
+
|
|
|
|
|
+ let result
|
|
|
|
|
+ if (userRole === 'admin') {
|
|
|
|
|
+ // Admin peut voir tous les trajets
|
|
|
|
|
+ result = await TripService.getTrips(pagination.value.page, pagination.value.limit)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Utilisateur d'entreprise ne voit que les trajets de son entreprise
|
|
|
|
|
+ result = await TripService.getTripsByCompany(companyId, pagination.value.page, pagination.value.limit)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ trips.value = result.trips
|
|
|
|
|
+ pagination.value = result.pagination
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erreur lors du chargement des trajets:', error)
|
|
|
|
|
+ error('Erreur lors du chargement des trajets')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadDrivers = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const userRole = userStore.userRole
|
|
|
|
|
+ const companyId = userStore.decodedCompanyId || userStore.companyId
|
|
|
|
|
+
|
|
|
|
|
+ if (userRole === Role.Manager || userRole === Role.Admin) {
|
|
|
|
|
+
|
|
|
|
|
+ drivers.value = (await CompanyService.getCompanyEmployees(parseInt(companyId))).employees.filter(driver => driver.role === Role.Driver)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Pour les entreprises, récupérer uniquement les chauffeurs de l'entreprise
|
|
|
|
|
+ const companyId = userStore.decodedCompanyId || userStore.companyId
|
|
|
|
|
+ if (companyId) {
|
|
|
|
|
+ drivers.value = (await CompanyService.getCompanyEmployees(parseInt(companyId))).employees.filter(driver => driver.role === Role.Driver)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.error('Aucune entreprise associée à l\'utilisateur')
|
|
|
|
|
+ drivers.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log('Driver valeur ======>',drivers.value)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erreur lors du chargement des chauffeurs:', error)
|
|
|
|
|
+ drivers.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadVehicles = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const userRole = userStore.userRole
|
|
|
|
|
+ const companyId = userStore.companyId
|
|
|
|
|
+
|
|
|
|
|
+ let result
|
|
|
|
|
+ if (userRole === 'admin') {
|
|
|
|
|
+ result = await VehiculeService.getVehicles(1, 100) // Charger tous les véhicules pour admin
|
|
|
|
|
+ } else {
|
|
|
|
|
+ result = await VehiculeService.getVehiclesByCompany(companyId, 1, 100)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ vehicles.value = result.vehicles
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erreur lors du chargement des véhicules:', error)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const loadStations = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await stationStore.fetchStations()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erreur lors du chargement des stations:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Fonction updateTotalSeats supprimée car non nécessaire avec la nouvelle structure
|
|
|
|
|
+
|
|
|
const editTrip = (trip: Trip) => {
|
|
const editTrip = (trip: Trip) => {
|
|
|
editingTrip.value = trip
|
|
editingTrip.value = trip
|
|
|
viewingTrip.value = null
|
|
viewingTrip.value = null
|
|
|
- formData.value = { ...trip }
|
|
|
|
|
|
|
+ formData.value = {
|
|
|
|
|
+ id: trip.id,
|
|
|
|
|
+ // Mapper les données du backend vers les propriétés du formulaire
|
|
|
|
|
+ departureCity: trip.departStationId,
|
|
|
|
|
+ arrivalCity: trip.arriveStationId,
|
|
|
|
|
+ departureDateTime: trip.departAt,
|
|
|
|
|
+ estimatedArrivalDateTime: trip.arriveAt,
|
|
|
|
|
+ actualArrivalDateTime: trip.actualArrivalDateTime || '',
|
|
|
|
|
+ pricePerSeat: trip.pricePerSeat || 0,
|
|
|
|
|
+ totalSeats: trip.totalSeats || 0,
|
|
|
|
|
+ availableSeats: trip.availableSeats || 0,
|
|
|
|
|
+ driverId: trip.driverId || 0,
|
|
|
|
|
+ vehicleId: trip.vehiculeId,
|
|
|
|
|
+ status: trip.status || 'upcoming',
|
|
|
|
|
+ notes: trip.notes || '',
|
|
|
|
|
+ companyId: trip.companyId || 0,
|
|
|
|
|
+ createdAt: trip.createdAt || '',
|
|
|
|
|
+ updatedAt: trip.updatedAt || ''
|
|
|
|
|
+ }
|
|
|
showAddForm.value = true
|
|
showAddForm.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -552,16 +632,14 @@ const deleteTrip = async () => {
|
|
|
if (!tripToDelete.value) return
|
|
if (!tripToDelete.value) return
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // Dans un environnement réel, vous feriez un appel API ici
|
|
|
|
|
- // await fetch(`http://localhost:3001/api/trips/${tripToDelete.value.id}`, {
|
|
|
|
|
- // method: 'DELETE'
|
|
|
|
|
- // })
|
|
|
|
|
-
|
|
|
|
|
- // Supprimer le voyage de la liste locale
|
|
|
|
|
- trips.value = trips.value.filter(t => t.id !== tripToDelete.value?.id)
|
|
|
|
|
-
|
|
|
|
|
- // Afficher une notification de succès
|
|
|
|
|
- success('Voyage supprimé avec succès')
|
|
|
|
|
|
|
+ const result = await TripService.deleteTrip(tripToDelete.value.id)
|
|
|
|
|
+ if (result) {
|
|
|
|
|
+ // Recharger la liste des trajets
|
|
|
|
|
+ await loadTrips()
|
|
|
|
|
+ success('Voyage supprimé avec succès')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ error('Erreur lors de la suppression du voyage')
|
|
|
|
|
+ }
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Erreur lors de la suppression:', error)
|
|
console.error('Erreur lors de la suppression:', error)
|
|
|
error('Erreur lors de la suppression du voyage')
|
|
error('Erreur lors de la suppression du voyage')
|
|
@@ -572,62 +650,113 @@ const deleteTrip = async () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const saveTrip = async () => {
|
|
const saveTrip = async () => {
|
|
|
- if (!isFormValid.value) return
|
|
|
|
|
|
|
+ console.log('=== DEBUT saveTrip ===')
|
|
|
|
|
+ console.log('Données du formulaire:', formData.value)
|
|
|
|
|
+
|
|
|
|
|
+ // Vérification détaillée de la validité
|
|
|
|
|
+ console.log('Validation détaillée:')
|
|
|
|
|
+ console.log('- departureCity:', formData.value.departureCity, 'valide:', !!formData.value.departureCity)
|
|
|
|
|
+ console.log('- arrivalCity:', formData.value.arrivalCity, 'valide:', !!formData.value.arrivalCity)
|
|
|
|
|
+ console.log('- departureDateTime:', formData.value.departureDateTime, 'valide:', !!formData.value.departureDateTime)
|
|
|
|
|
+ console.log('- estimatedArrivalDateTime:', formData.value.estimatedArrivalDateTime, 'valide:', !!formData.value.estimatedArrivalDateTime)
|
|
|
|
|
+ console.log('- pricePerSeat:', formData.value.pricePerSeat, 'valide:', formData.value.pricePerSeat > 0)
|
|
|
|
|
+ console.log('- totalSeats:', formData.value.totalSeats, 'valide:', formData.value.totalSeats > 0)
|
|
|
|
|
+ console.log('- availableSeats:', formData.value.availableSeats, 'valide:', formData.value.availableSeats >= 0 && formData.value.availableSeats <= formData.value.totalSeats)
|
|
|
|
|
+ console.log('- driverId:', formData.value.driverId, 'valide:', !!formData.value.driverId)
|
|
|
|
|
+ console.log('- vehicleId:', formData.value.vehicleId, 'valide:', !!formData.value.vehicleId)
|
|
|
|
|
+
|
|
|
|
|
+ // Validation spécifique pour les champs requis par l'API
|
|
|
|
|
+ if (!formData.value.departureCity || !formData.value.arrivalCity) {
|
|
|
|
|
+ console.log('Erreur: Ville de départ ou d\'arrivée manquante')
|
|
|
|
|
+ error('Veuillez sélectionner les villes de départ et d\'arrivée')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ if (!formData.value.departureDateTime || !formData.value.estimatedArrivalDateTime) {
|
|
|
|
|
+ console.log('Erreur: Date de départ ou d\'arrivée manquante')
|
|
|
|
|
+ error('Veuillez renseigner les dates de départ et d\'arrivée')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!formData.value.vehicleId) {
|
|
|
|
|
+ console.log('Erreur: Véhicule non sélectionné')
|
|
|
|
|
+ error('Veuillez sélectionner un véhicule')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!formData.value.pricePerSeat || formData.value.pricePerSeat <= 0) {
|
|
|
|
|
+ console.log('Erreur: Prix par place invalide')
|
|
|
|
|
+ error('Veuillez renseigner un prix par place valide')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!formData.value.totalSeats || formData.value.totalSeats <= 0) {
|
|
|
|
|
+ console.log('Erreur: Nombre de places invalide')
|
|
|
|
|
+ error('Veuillez renseigner un nombre de places valide')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!formData.value.driverId) {
|
|
|
|
|
+ console.log('Erreur: Chauffeur non sélectionné')
|
|
|
|
|
+ error('Veuillez sélectionner un chauffeur')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // Pour un nouveau voyage, initialiser les places disponibles au nombre total de places
|
|
|
|
|
+ if (!editingTrip.value) {
|
|
|
|
|
+ formData.value.availableSeats = formData.value.totalSeats
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Mapper les données du formulaire vers la structure attendue par l'API (CreateTrajetDto)
|
|
|
|
|
+ const departStationId = Number(formData.value.departureCity)
|
|
|
|
|
+ const arriveStationId = Number(formData.value.arrivalCity)
|
|
|
|
|
+ const vehiculeId = Number(formData.value.vehicleId)
|
|
|
|
|
+
|
|
|
|
|
+ // Validation des nombres
|
|
|
|
|
+ if (isNaN(departStationId) || isNaN(arriveStationId) || isNaN(vehiculeId)) {
|
|
|
|
|
+ console.log('Erreur: Valeurs invalides pour les IDs')
|
|
|
|
|
+ error('Erreur dans les données du formulaire. Veuillez réessayer.')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const tripData = {
|
|
|
|
|
+ departAt: formData.value.departureDateTime,
|
|
|
|
|
+ arriveAt: formData.value.estimatedArrivalDateTime,
|
|
|
|
|
+ departStationId,
|
|
|
|
|
+ arriveStationId,
|
|
|
|
|
+ vehiculeId,
|
|
|
|
|
+ pricePerSeat: Number(formData.value.pricePerSeat)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log('Données pour l\'API:', tripData)
|
|
|
|
|
+
|
|
|
if (editingTrip.value) {
|
|
if (editingTrip.value) {
|
|
|
|
|
+ console.log('Mode édition - ID:', editingTrip.value.id)
|
|
|
// Mise à jour d'un voyage existant
|
|
// Mise à jour d'un voyage existant
|
|
|
- // Dans un environnement réel, vous feriez un appel API ici
|
|
|
|
|
- // await fetch(`http://localhost:3001/api/trips/${editingTrip.value.id}`, {
|
|
|
|
|
- // method: 'PUT',
|
|
|
|
|
- // headers: {
|
|
|
|
|
- // 'Content-Type': 'application/json'
|
|
|
|
|
- // },
|
|
|
|
|
- // body: JSON.stringify(formData.value)
|
|
|
|
|
- // })
|
|
|
|
|
-
|
|
|
|
|
- // Mettre à jour le voyage dans la liste locale
|
|
|
|
|
- const index = trips.value.findIndex(t => t.id === editingTrip.value?.id)
|
|
|
|
|
- if (index !== -1) {
|
|
|
|
|
- trips.value[index] = {
|
|
|
|
|
- ...trips.value[index],
|
|
|
|
|
- ...formData.value,
|
|
|
|
|
- id: editingTrip.value.id
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Afficher une notification de succès
|
|
|
|
|
|
|
+ const result = await TripService.updateTrip(editingTrip.value.id, tripData)
|
|
|
|
|
+ console.log('Résultat updateTrip:', result)
|
|
|
success('Voyage mis à jour avec succès')
|
|
success('Voyage mis à jour avec succès')
|
|
|
|
|
+ await loadTrips() // Recharger la liste
|
|
|
} else {
|
|
} else {
|
|
|
- // Ajout d'un nouveau voyage
|
|
|
|
|
- // Dans un environnement réel, vous feriez un appel API ici
|
|
|
|
|
- // const response = await fetch('http://localhost:3001/api/trips', {
|
|
|
|
|
- // method: 'POST',
|
|
|
|
|
- // headers: {
|
|
|
|
|
- // 'Content-Type': 'application/json'
|
|
|
|
|
- // },
|
|
|
|
|
- // body: JSON.stringify(formData.value)
|
|
|
|
|
- // })
|
|
|
|
|
- // const data = await response.json()
|
|
|
|
|
-
|
|
|
|
|
- // Simuler l'ajout d'un nouveau voyage avec un ID généré
|
|
|
|
|
- const newTrip: Trip = {
|
|
|
|
|
- ...formData.value,
|
|
|
|
|
- id: Math.max(0, ...trips.value.map(t => t.id)) + 1
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Ajouter le voyage à la liste locale
|
|
|
|
|
- trips.value.push(newTrip)
|
|
|
|
|
-
|
|
|
|
|
- // Afficher une notification de succès
|
|
|
|
|
- success('Voyage ajouté avec succès')
|
|
|
|
|
|
|
+ console.log('Mode création')
|
|
|
|
|
+ // Création d'un nouveau voyage
|
|
|
|
|
+ const result = await TripService.createTrip(tripData)
|
|
|
|
|
+ console.log('Résultat createTrip:', result)
|
|
|
|
|
+ success('Voyage créé avec succès')
|
|
|
|
|
+ await loadTrips() // Recharger la liste
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Fermer le formulaire
|
|
|
|
|
- closeForm()
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('Erreur lors de l\'enregistrement:', error)
|
|
|
|
|
- error('Erreur lors de l\'enregistrement du voyage')
|
|
|
|
|
|
|
+ // Réinitialiser le formulaire
|
|
|
|
|
+ resetForm()
|
|
|
|
|
+ showAddForm.value = false
|
|
|
|
|
+ editingTrip.value = null
|
|
|
|
|
+
|
|
|
|
|
+ console.log('=== FIN saveTrip (succès) ===')
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Erreur lors de la sauvegarde:', err)
|
|
|
|
|
+ error('Erreur lors de la sauvegarde du voyage')
|
|
|
|
|
+ console.log('=== FIN saveTrip (erreur) ===')
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -647,135 +776,43 @@ const handleMessages = () => {
|
|
|
const handleUserMenu = () => {
|
|
const handleUserMenu = () => {
|
|
|
// Gérer l'affichage du menu utilisateur
|
|
// Gérer l'affichage du menu utilisateur
|
|
|
}
|
|
}
|
|
|
-// Charger les données
|
|
|
|
|
|
|
+// Initialisation des données
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
- try {
|
|
|
|
|
- // Charger les villes depuis le fichier JSON
|
|
|
|
|
- const citiesResponse = await fetch('/datas/civ.cities.json')
|
|
|
|
|
- if (!citiesResponse.ok) {
|
|
|
|
|
- throw new Error('Erreur HTTP: ' + citiesResponse.status)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ await loadStations()
|
|
|
|
|
+ await loadDrivers()
|
|
|
|
|
+ await loadVehicles()
|
|
|
|
|
+ await loadTrips()
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
- console.log('citiesResponse:', citiesResponse)
|
|
|
|
|
- cities.value = await citiesResponse.json()
|
|
|
|
|
- console.log('citiesResponse:', cities.value)
|
|
|
|
|
- console.log('citiesResponse:', await cities.value)
|
|
|
|
|
-
|
|
|
|
|
- // Simuler le chargement des chauffeurs
|
|
|
|
|
- drivers.value = [
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- firstName: 'Jean',
|
|
|
|
|
- lastName: 'Dupont',
|
|
|
|
|
- phone: '+33 6 12 34 56 78',
|
|
|
|
|
- email: 'jean.dupont@example.com',
|
|
|
|
|
- companyId: 1,
|
|
|
|
|
- licenseNumber: 'DRV-12345',
|
|
|
|
|
- active: true
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2,
|
|
|
|
|
- firstName: 'Marie',
|
|
|
|
|
- lastName: 'Laurent',
|
|
|
|
|
- phone: '+33 6 23 45 67 89',
|
|
|
|
|
- email: 'marie.laurent@example.com',
|
|
|
|
|
- companyId: 2,
|
|
|
|
|
- licenseNumber: 'DRV-67890',
|
|
|
|
|
- active: true
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 3,
|
|
|
|
|
- firstName: 'Ahmed',
|
|
|
|
|
- lastName: 'Koné',
|
|
|
|
|
- phone: '+225 07 12 34 56',
|
|
|
|
|
- email: 'ahmed.kone@example.com',
|
|
|
|
|
- companyId: 3,
|
|
|
|
|
- licenseNumber: 'DRV-54321',
|
|
|
|
|
- active: false
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- // // Simuler le chargement des véhicules
|
|
|
|
|
- vehicles.value = [
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- make: 'Toyota',
|
|
|
|
|
- model: 'Hiace',
|
|
|
|
|
- registrationNumber: 'AB-123-CD',
|
|
|
|
|
- capacity: 15,
|
|
|
|
|
- companyId: 1,
|
|
|
|
|
- active: true
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2,
|
|
|
|
|
- make: 'Mercedes',
|
|
|
|
|
- model: 'Sprinter',
|
|
|
|
|
- registrationNumber: 'EF-456-GH',
|
|
|
|
|
- capacity: 22,
|
|
|
|
|
- companyId: 2,
|
|
|
|
|
- active: true
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 3,
|
|
|
|
|
- make: 'Renault',
|
|
|
|
|
- model: 'Master',
|
|
|
|
|
- registrationNumber: 'IJ-789-KL',
|
|
|
|
|
- capacity: 18,
|
|
|
|
|
- companyId: 3,
|
|
|
|
|
- active: true
|
|
|
|
|
|
|
+// Watchers pour la pagination
|
|
|
|
|
+watch(() => pagination.value.page, () => {
|
|
|
|
|
+ loadTrips()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+watch(() => pagination.value.limit, () => {
|
|
|
|
|
+ pagination.value.page = 1
|
|
|
|
|
+ loadTrips()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// Watcher pour mettre à jour les places disponibles lors de la création d'un nouveau voyage
|
|
|
|
|
+watch(() => formData.value.totalSeats, (newTotalSeats) => {
|
|
|
|
|
+ // Seulement pour un nouveau voyage (pas en mode édition)
|
|
|
|
|
+ if (!editingTrip.value && newTotalSeats > 0) {
|
|
|
|
|
+ formData.value.availableSeats = newTotalSeats
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// Watcher pour mettre à jour automatiquement le nombre total de places quand un véhicule est sélectionné
|
|
|
|
|
+watch(() => formData.value.vehicleId, (newVehicleId) => {
|
|
|
|
|
+ if (newVehicleId) {
|
|
|
|
|
+ const selectedVehicle = vehicles.value.find(v => v.id === newVehicleId)
|
|
|
|
|
+ if (selectedVehicle) {
|
|
|
|
|
+ formData.value.totalSeats = selectedVehicle.number_places
|
|
|
|
|
+ // Si c'est un nouveau voyage, mettre à jour aussi les places disponibles
|
|
|
|
|
+ if (!editingTrip.value) {
|
|
|
|
|
+ formData.value.availableSeats = selectedVehicle.number_places
|
|
|
}
|
|
}
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- // Simuler le chargement des voyages
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- trips.value = [
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- departureCity: 1, // Abidjan
|
|
|
|
|
- arrivalCity: 2, // Bouaké
|
|
|
|
|
- departureDateTime: '2023-06-15T08:00:00',
|
|
|
|
|
- estimatedArrivalDateTime: '2023-06-15T14:00:00',
|
|
|
|
|
- actualArrivalDateTime: '2023-06-15T14:30:00',
|
|
|
|
|
- pricePerSeat: 5000,
|
|
|
|
|
- totalSeats: 15,
|
|
|
|
|
- availableSeats: 3,
|
|
|
|
|
- driverId: 1,
|
|
|
|
|
- vehicleId: 1,
|
|
|
|
|
- status: 'completed',
|
|
|
|
|
- notes: 'Voyage régulier hebdomadaire'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2,
|
|
|
|
|
- departureCity: 2, // Bouaké
|
|
|
|
|
- arrivalCity: 3, // Yamoussoukro
|
|
|
|
|
- departureDateTime: '2023-06-20T10:00:00',
|
|
|
|
|
- estimatedArrivalDateTime: '2023-06-20T12:30:00',
|
|
|
|
|
- pricePerSeat: 3000,
|
|
|
|
|
- totalSeats: 22,
|
|
|
|
|
- availableSeats: 8,
|
|
|
|
|
- driverId: 2,
|
|
|
|
|
- vehicleId: 2,
|
|
|
|
|
- status: 'upcoming',
|
|
|
|
|
- notes: 'Voyage express'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 3,
|
|
|
|
|
- departureCity: 1, // Abidjan
|
|
|
|
|
- arrivalCity: 3, // Yamoussoukro
|
|
|
|
|
- departureDateTime: '2023-06-18T07:30:00',
|
|
|
|
|
- estimatedArrivalDateTime: '2023-06-18T11:30:00',
|
|
|
|
|
- actualArrivalDateTime: '2023-06-18T11:45:00',
|
|
|
|
|
- pricePerSeat: 4500,
|
|
|
|
|
- totalSeats: 18,
|
|
|
|
|
- availableSeats: 0,
|
|
|
|
|
- driverId: 3,
|
|
|
|
|
- vehicleId: 3,
|
|
|
|
|
- status: 'completed'
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
- }, 500)
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('Erreur lors du chargement des données:', error)
|
|
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
@@ -952,10 +989,68 @@ onMounted(async () => {
|
|
|
background-color: #f8f9fc;
|
|
background-color: #f8f9fc;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.loading-state {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 60px 20px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.loading-spinner {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ border: 4px solid #f3f3f3;
|
|
|
|
|
+ border-top: 4px solid #007bff;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes spin {
|
|
|
|
|
+ 0% { transform: rotate(0deg); }
|
|
|
|
|
+ 100% { transform: rotate(360deg); }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.no-data {
|
|
.no-data {
|
|
|
- padding: 40px;
|
|
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
- color: #858796;
|
|
|
|
|
|
|
+ padding: 60px 20px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.no-data-icon {
|
|
|
|
|
+ font-size: 48px;
|
|
|
|
|
+ color: #ddd;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.no-data h3 {
|
|
|
|
|
+ margin: 0 0 8px 0;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.no-data p {
|
|
|
|
|
+ margin: 0 0 20px 0;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.no-data .add-button {
|
|
|
|
|
+ background: #007bff;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ padding: 12px 24px;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ transition: background-color 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.no-data .add-button:hover {
|
|
|
|
|
+ background: #0056b3;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Modal styles */
|
|
/* Modal styles */
|
|
@@ -1047,6 +1142,10 @@ onMounted(async () => {
|
|
|
margin-bottom: 15px;
|
|
margin-bottom: 15px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.form-group.full-width {
|
|
|
|
|
+ flex: 1 1 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.form-group label {
|
|
.form-group label {
|
|
|
margin-bottom: 5px;
|
|
margin-bottom: 5px;
|
|
|
font-size: 14px;
|
|
font-size: 14px;
|