- Parte 3 : Viaggi
- Chat
This commit is contained in:
586
src/modules/viaggi/composables/useRides.ts
Normal file
586
src/modules/viaggi/composables/useRides.ts
Normal file
@@ -0,0 +1,586 @@
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { Api } from '@api'; // Adatta al tuo path
|
||||
import type {
|
||||
Ride,
|
||||
RideFormData,
|
||||
RideSearchFilters,
|
||||
RideType,
|
||||
RideStatus,
|
||||
ApiResponse,
|
||||
PaginatedResponse,
|
||||
MyRidesResponse,
|
||||
RidesStatsResponse,
|
||||
Waypoint,
|
||||
Location,
|
||||
} from '../types';
|
||||
|
||||
// ============================================================
|
||||
// STATE
|
||||
// ============================================================
|
||||
|
||||
const rides = ref<Ride[]>([]);
|
||||
const currentRide = ref<Ride | null>(null);
|
||||
const myRides = reactive<{
|
||||
all: Ride[];
|
||||
upcoming: Ride[];
|
||||
past: Ride[];
|
||||
}>({
|
||||
all: [],
|
||||
upcoming: [],
|
||||
past: [],
|
||||
});
|
||||
const stats = ref<RidesStatsResponse | null>(null);
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
pages: 0,
|
||||
});
|
||||
|
||||
const filters = reactive<RideSearchFilters>({
|
||||
type: undefined,
|
||||
from: '',
|
||||
to: '',
|
||||
date: '',
|
||||
seats: 1,
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// COMPOSABLE
|
||||
// ============================================================
|
||||
|
||||
export function useRides() {
|
||||
// ------------------------------------------------------------
|
||||
// COMPUTED
|
||||
// ------------------------------------------------------------
|
||||
|
||||
const hasRides = computed(() => rides.value.length > 0);
|
||||
const hasMorePages = computed(() => pagination.page < pagination.pages);
|
||||
|
||||
const offersCount = computed(() =>
|
||||
Array.isArray(rides.value) ? rides.value.filter((r) => r.type === 'offer').length : 0
|
||||
);
|
||||
|
||||
const requestsCount = computed(() =>
|
||||
Array.isArray(rides.value)
|
||||
? rides.value.filter((r) => r.type === 'request').length
|
||||
: 0
|
||||
);
|
||||
|
||||
// ✅ Fixed: was returning array instead of count, and added .length
|
||||
const activeRides = computed(() =>
|
||||
Array.isArray(rides.value)
|
||||
? rides.value.filter((r) => ['active', 'full'].includes(r.status)).length
|
||||
: 0
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// API CALLS
|
||||
// ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ottieni lista viaggi con filtri
|
||||
*/
|
||||
const fetchRides = async (
|
||||
options: {
|
||||
reset?: boolean;
|
||||
filters?: RideSearchFilters;
|
||||
} = {}
|
||||
) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
if (options.reset) {
|
||||
pagination.page = 1;
|
||||
rides.value = [];
|
||||
}
|
||||
|
||||
if (options.filters) {
|
||||
Object.assign(filters, options.filters);
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('page', pagination.page.toString());
|
||||
queryParams.append('limit', pagination.limit.toString());
|
||||
|
||||
if (filters.type) queryParams.append('type', filters.type);
|
||||
if (filters.from) queryParams.append('departureCity', filters.from);
|
||||
if (filters.to) queryParams.append('destinationCity', filters.to);
|
||||
if (filters.date) queryParams.append('date', filters.date);
|
||||
if (filters.seats) queryParams.append('minSeats', filters.seats.toString());
|
||||
if (filters.passingThrough)
|
||||
queryParams.append('passingThrough', filters.passingThrough);
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
`/api/viaggi/rides?${queryParams.toString()}`,
|
||||
'GET'
|
||||
)) as PaginatedResponse<Ride>;
|
||||
|
||||
if (response.success) {
|
||||
// ✅ Ensure response.data is always an array
|
||||
const newRides = Array.isArray(response.data) ? response.data : [];
|
||||
|
||||
if (options.reset) {
|
||||
rides.value = newRides;
|
||||
} else {
|
||||
rides.value = [...rides.value, ...newRides];
|
||||
}
|
||||
|
||||
if (response?.data.pagination) {
|
||||
Object.assign(pagination, response?.data.pagination);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Errore nel recupero dei viaggi');
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nel recupero dei viaggi';
|
||||
rides.value = []; // ✅ Reset to empty array on error
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ricerca viaggi avanzata
|
||||
*/
|
||||
const searchRides = async (searchFilters: RideSearchFilters) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
if (searchFilters.from) queryParams.append('from', searchFilters.from);
|
||||
if (searchFilters.to) queryParams.append('to', searchFilters.to);
|
||||
if (searchFilters.date) queryParams.append('date', searchFilters.date);
|
||||
if (searchFilters.seats)
|
||||
queryParams.append('seats', searchFilters.seats.toString());
|
||||
if (searchFilters.type) queryParams.append('type', searchFilters.type);
|
||||
queryParams.append('page', pagination.page.toString());
|
||||
queryParams.append('limit', pagination.limit.toString());
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
`/api/viaggi/rides/search?${queryParams.toString()}`,
|
||||
'GET'
|
||||
)) as PaginatedResponse<Ride>;
|
||||
|
||||
if (response.success) {
|
||||
// ✅ Ensure response.data is always an array
|
||||
rides.value = Array.isArray(response.data) ? response.data : [];
|
||||
|
||||
if (response?.data.pagination) {
|
||||
Object.assign(pagination, response?.data.pagination);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nella ricerca';
|
||||
rides.value = []; // ✅ Reset to empty array on error
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ottieni singolo viaggio
|
||||
*/
|
||||
const fetchRide = async (rideId: string) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
`/api/viaggi/rides/${rideId}`,
|
||||
'GET'
|
||||
)) as ApiResponse<Ride>;
|
||||
|
||||
if (response.success && response.data) {
|
||||
currentRide.value = response.data;
|
||||
} else {
|
||||
throw new Error('Viaggio non trovato');
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nel recupero del viaggio';
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Crea nuovo viaggio
|
||||
*/
|
||||
const createRide = async (rideData: Partial<RideFormData>) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
'/api/viaggi/rides',
|
||||
'POST',
|
||||
rideData
|
||||
)) as ApiResponse<Ride>;
|
||||
|
||||
if (response.success && response.data) {
|
||||
// Aggiungi in testa alla lista
|
||||
rides.value.unshift(response.data);
|
||||
currentRide.value = response.data;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nella creazione del viaggio';
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aggiorna viaggio
|
||||
*/
|
||||
const updateRide = async (rideId: string, updateData: Partial<RideFormData>) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
`/api/viaggi/rides/${rideId}`,
|
||||
'PUT',
|
||||
updateData
|
||||
)) as ApiResponse<Ride>;
|
||||
|
||||
if (response.success && response.data) {
|
||||
// Aggiorna nella lista
|
||||
const index = rides.value.findIndex((r) => r._id === rideId);
|
||||
if (index !== -1) {
|
||||
rides.value[index] = response.data;
|
||||
}
|
||||
if (currentRide.value?._id === rideId) {
|
||||
currentRide.value = response.data;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||"Errore nell'aggiornamento del viaggio";
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancella viaggio
|
||||
*/
|
||||
const deleteRide = async (rideId: string, reason?: string) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const response = (await Api.SendReqWithData(`/api/viaggi/rides/${rideId}`, 'DELETE', {
|
||||
reason,
|
||||
})) as ApiResponse<void>;
|
||||
|
||||
if (response.success) {
|
||||
// Rimuovi dalla lista
|
||||
rides.value = rides.value.filter((r) => r._id !== rideId);
|
||||
if (currentRide.value?._id === rideId) {
|
||||
currentRide.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nella cancellazione del viaggio';
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Completa viaggio
|
||||
*/
|
||||
const completeRide = async (rideId: string) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
`/api/viaggi/rides/${rideId}/complete`,
|
||||
'POST'
|
||||
)) as ApiResponse<Ride>;
|
||||
|
||||
if (response.success && response.data) {
|
||||
const index = rides.value.findIndex((r) => r._id === rideId);
|
||||
if (index !== -1) {
|
||||
rides.value[index] = response.data;
|
||||
}
|
||||
if (currentRide.value?._id === rideId) {
|
||||
currentRide.value = response.data;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nel completamento del viaggio';
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ottieni i miei viaggi
|
||||
*/
|
||||
const fetchMyRides = async (options?: {
|
||||
type?: RideType;
|
||||
role?: 'driver' | 'passenger';
|
||||
status?: RideStatus;
|
||||
}) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
if (options?.type) queryParams.append('type', options.type);
|
||||
if (options?.role) queryParams.append('role', options.role);
|
||||
if (options?.status) queryParams.append('status', options.status);
|
||||
queryParams.append('page', pagination.page.toString());
|
||||
queryParams.append('limit', pagination.limit.toString());
|
||||
|
||||
const response = (await Api.SendReqWithData(
|
||||
`/api/viaggi/rides/my?${queryParams.toString()}`,
|
||||
'GET'
|
||||
)) as MyRidesResponse;
|
||||
|
||||
if (response.success && response.data) {
|
||||
myRides.all = response.data.all;
|
||||
myRides.upcoming = response.data.upcoming;
|
||||
myRides.past = response.data.past;
|
||||
Object.assign(pagination, response?.data.pagination);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
error.value = err.data?.message || err.message ||'Errore nel recupero dei tuoi viaggi';
|
||||
throw err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ottieni statistiche per widget
|
||||
*/
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = (await Api.SendReqWithData(
|
||||
'/api/viaggi/rides/stats',
|
||||
'GET'
|
||||
)) as ApiResponse<RidesStatsResponse>;
|
||||
|
||||
if (response.success && response.data) {
|
||||
stats.value = response.data;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
console.error('Errore recupero statistiche:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// UTILITIES
|
||||
// ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Carica pagina successiva
|
||||
*/
|
||||
const loadMore = async () => {
|
||||
if (hasMorePages.value && !loading.value) {
|
||||
pagination.page++;
|
||||
await fetchRides({ reset: false });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset filtri
|
||||
*/
|
||||
const resetFilters = () => {
|
||||
filters.type = undefined;
|
||||
filters.from = '';
|
||||
filters.to = '';
|
||||
filters.date = '';
|
||||
filters.seats = 1;
|
||||
filters.passingThrough = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Pulisci stato
|
||||
*/
|
||||
const clearState = () => {
|
||||
rides.value = [];
|
||||
currentRide.value = null;
|
||||
myRides.all = [];
|
||||
myRides.upcoming = [];
|
||||
myRides.past = [];
|
||||
error.value = null;
|
||||
pagination.page = 1;
|
||||
pagination.total = 0;
|
||||
pagination.pages = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatta data viaggio
|
||||
*/
|
||||
const formatRideDate = (date: Date | string) => {
|
||||
const d = new Date(date);
|
||||
const today = new Date();
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
if (d.toDateString() === today.toDateString()) {
|
||||
return `Oggi, ${d.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
} else if (d.toDateString() === tomorrow.toDateString()) {
|
||||
return `Domani, ${d.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
} else {
|
||||
return d.toLocaleDateString('it-IT', {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatta durata viaggio
|
||||
*/
|
||||
const formatDuration = (minutes: number) => {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
if (hours === 0) return `${mins} min`;
|
||||
if (mins === 0) return `${hours} h`;
|
||||
return `${hours} h ${mins} min`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatta distanza
|
||||
*/
|
||||
const formatDistance = (km: number) => {
|
||||
if (km < 1) return `${Math.round(km * 1000)} m`;
|
||||
return `${km.toFixed(1)} km`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ottieni colore status
|
||||
*/
|
||||
const getStatusColor = (status: RideStatus): string => {
|
||||
const colors: Record<RideStatus, string> = {
|
||||
draft: 'grey',
|
||||
active: 'positive',
|
||||
full: 'warning',
|
||||
in_progress: 'info',
|
||||
completed: 'positive',
|
||||
cancelled: 'negative',
|
||||
expired: 'grey',
|
||||
};
|
||||
return colors[status] || 'grey';
|
||||
};
|
||||
|
||||
/**
|
||||
* Ottieni label status
|
||||
*/
|
||||
const getStatusLabel = (status: RideStatus): string => {
|
||||
const labels: Record<RideStatus, string> = {
|
||||
draft: 'Bozza',
|
||||
active: 'Attivo',
|
||||
full: 'Completo',
|
||||
in_progress: 'In corso',
|
||||
completed: 'Completato',
|
||||
cancelled: 'Cancellato',
|
||||
expired: 'Scaduto',
|
||||
};
|
||||
return labels[status] || status;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifica se il viaggio è nel passato
|
||||
*/
|
||||
const isPastRide = (ride: Ride) => {
|
||||
return new Date(ride.dateTime) < new Date();
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifica se l'utente può prenotare
|
||||
*/
|
||||
const canBook = (ride: Ride, userId: string) => {
|
||||
if (ride.type !== 'offer') return false;
|
||||
if (ride.status !== 'active') return false;
|
||||
if (ride.passengers && ride.passengers.available <= 0) return false;
|
||||
if (typeof ride.userId === 'string' && ride.userId === userId) return false;
|
||||
if (typeof ride.userId === 'object' && ride.userId._id === userId) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// RETURN
|
||||
// ------------------------------------------------------------
|
||||
|
||||
return {
|
||||
// State
|
||||
rides,
|
||||
currentRide,
|
||||
myRides,
|
||||
stats,
|
||||
loading,
|
||||
error,
|
||||
pagination,
|
||||
filters,
|
||||
|
||||
// Computed
|
||||
hasRides,
|
||||
hasMorePages,
|
||||
offersCount,
|
||||
requestsCount,
|
||||
activeRides,
|
||||
|
||||
// API Methods
|
||||
fetchRides,
|
||||
searchRides,
|
||||
fetchRide,
|
||||
createRide,
|
||||
updateRide,
|
||||
deleteRide,
|
||||
completeRide,
|
||||
fetchMyRides,
|
||||
fetchStats,
|
||||
|
||||
// Utilities
|
||||
loadMore,
|
||||
resetFilters,
|
||||
clearState,
|
||||
formatRideDate,
|
||||
formatDuration,
|
||||
formatDistance,
|
||||
getStatusColor,
|
||||
getStatusLabel,
|
||||
isPastRide,
|
||||
canBook,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user