Files
myprojplanet_vite/src/modules/viaggi/composables/useRides.ts
2025-12-30 11:36:37 +01:00

655 lines
16 KiB
TypeScript

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<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,
});
// 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<RideSearchFilters>(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<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 completeRideApi = 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
*/
/**
* 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<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.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,
};
}