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'; const STOR_TRASP_CITIE = 'trasp_ride_filters'; // ============================================================ // STATE // ============================================================ const rides = ref([]); const currentRide = ref(null); const myRides = reactive<{ all: Ride[]; upcoming: Ride[]; past: Ride[]; }>({ all: [], upcoming: [], past: [], }); const stats = ref(null); const loading = ref(false); const error = ref(null); const pagination = reactive({ page: 1, limit: 20, total: 0, pages: 0, }); // Carica filtri da localStorage const loadFiltersFromStorage = (): RideSearchFilters => { try { const stored = localStorage.getItem(STOR_TRASP_CITIE); if (stored) { return JSON.parse(stored); } } catch (error) { console.error('Errore caricamento filtri da localStorage:', error); } // Default return { type: undefined, from: '', to: '', date: '', seats: 1, }; }; const filters = reactive(loadFiltersFromStorage()); // ------------------------------------------------------------ // WATCHERS // ------------------------------------------------------------ // Salva filtri in localStorage quando cambiano watch( filters, (newFilters) => { try { localStorage.setItem(STOR_TRASP_CITIE, JSON.stringify(newFilters)); } catch (error) { console.error('Errore salvataggio filtri in localStorage:', error); } }, { deep: true } ); // ============================================================ // 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; 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; 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; 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) => { try { loading.value = true; error.value = null; const response = (await Api.SendReqWithData( '/api/viaggi/rides', 'POST', rideData )) as ApiResponse; 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) => { try { loading.value = true; error.value = null; const response = (await Api.SendReqWithData( `/api/viaggi/rides/${rideId}`, 'PUT', updateData )) as ApiResponse; 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; 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 completeRideApi = async (rideId: string) => { try { loading.value = true; error.value = null; const response = (await Api.SendReqWithData( `/api/viaggi/rides/${rideId}/complete`, 'POST' )) as ApiResponse; 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; 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 */ /** * Reset filtri */ const resetFilters = () => { filters.type = undefined; filters.from = ''; filters.to = ''; filters.date = ''; filters.seats = 1; filters.passingThrough = ''; // Rimuovi da localStorage try { localStorage.removeItem(STOR_TRASP_CITIE); } catch (error) { console.error('Errore rimozione filtri da localStorage:', error); } }; /** * 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 = { 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 = { 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.departureDate) < 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; }; // Aggiungi il metodo const fetchCancelledRides = async (page = 1, limit = 20) => { try { const response = await Api.SendReqWithData( `/api/viaggi/rides/cancelled?page=${page}&limit=${limit}`, 'GET' ); return response; } catch (error: any) { console.error('Error fetching cancelled rides:', error); throw error; } }; // ------------------------------------------------------------ // 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, completeRideApi, fetchMyRides, fetchStats, // Utilities loadMore, resetFilters, clearState, formatRideDate, formatDuration, formatDistance, getStatusColor, getStatusLabel, isPastRide, canBook, fetchCancelledRides, }; }