From 11e946bfc6229d2bd66c8e346e6d1f3f2d30c59d Mon Sep 17 00:00:00 2001 From: Surya Paolo Date: Mon, 22 Dec 2025 23:39:42 +0100 Subject: [PATCH] - Trasporti- Passo 2 --- .../components/chat/MessageBubble.scss | 207 +++++---- .../components/chat/MessageBubble.ts | 26 +- .../components/ride/CityAutocomplete.vue | 6 +- .../components/widgets/RideWidget.ts | 8 +- src/modules/trasporti/composables/useAuth.ts | 16 + src/modules/trasporti/composables/useChat.ts | 280 ++++++++---- .../composables/useCitySuggestions.ts | 4 +- .../trasporti/composables/useContribTypes.ts | 6 +- .../trasporti/composables/useDriverProfile.ts | 112 +++-- .../trasporti/composables/useFeedback.ts | 58 +-- .../trasporti/composables/useRealtimeChat.ts | 4 +- .../trasporti/composables/useRideRequests.ts | 42 +- src/modules/trasporti/composables/useRides.ts | 54 +-- src/modules/trasporti/pages/ChatListPage.scss | 214 +++------- src/modules/trasporti/pages/ChatListPage.ts | 226 +++++++--- src/modules/trasporti/pages/ChatListPage.vue | 192 +++++++-- src/modules/trasporti/pages/ChatPage.scss | 399 +++++++++--------- src/modules/trasporti/pages/ChatPage.ts | 126 ++++-- src/modules/trasporti/pages/ChatPage.vue | 262 +++++++++--- .../trasporti/pages/DriverProfilePage.ts | 6 +- src/modules/trasporti/pages/MyRidesPage.ts | 4 +- .../trasporti/pages/Myfeedbackpage.vue | 8 +- src/modules/trasporti/pages/Requestspage.vue | 12 +- src/modules/trasporti/pages/RideCreatePage.ts | 4 +- src/modules/trasporti/pages/RideDetailPage.ts | 10 +- src/modules/trasporti/pages/RideSearchPage.ts | 6 +- src/modules/trasporti/pages/RidesListPage.ts | 6 +- src/modules/trasporti/pages/Settingspage.vue | 6 +- .../trasporti/pages/Vehicleeditpage.vue | 302 +++++++++---- src/modules/trasporti/pages/Vehiclespage.vue | 15 +- .../trasporti/types/trasporti.types.ts | 48 ++- src/store/Api/Inst-Pao.ts | 3 +- src/store/Api/Instance.ts | 4 +- src/store/Api/index.ts | 35 ++ 34 files changed, 1682 insertions(+), 1029 deletions(-) create mode 100644 src/modules/trasporti/composables/useAuth.ts diff --git a/src/modules/trasporti/components/chat/MessageBubble.scss b/src/modules/trasporti/components/chat/MessageBubble.scss index ec246d3f..6faf7a57 100644 --- a/src/modules/trasporti/components/chat/MessageBubble.scss +++ b/src/modules/trasporti/components/chat/MessageBubble.scss @@ -4,10 +4,15 @@ gap: 8px; margin-bottom: 8px; padding: 0 16px; - max-width: 100%; + max-width: 85%; + // Messaggi degli altri - allineati a sinistra (default) + align-self: flex-start; + + // Messaggi propri - allineati a destra &--own { flex-direction: row-reverse; + align-self: flex-end; .message-bubble__bubble { background: linear-gradient(135deg, var(--q-primary), var(--q-primary-dark, #1565c0)); @@ -19,6 +24,10 @@ color: rgba(255, 255, 255, 0.7); } + .message-bubble__edited { + color: rgba(255, 255, 255, 0.6); + } + .message-bubble__footer { justify-content: flex-end; } @@ -26,54 +35,72 @@ .message-bubble__reactions { justify-content: flex-end; } + + .message-bubble__reply { + background: rgba(255, 255, 255, 0.15); + + .message-bubble__reply-bar { + background: rgba(255, 255, 255, 0.5); + } + + .message-bubble__reply-sender { + color: rgba(255, 255, 255, 0.9); + } + + .message-bubble__reply-text { + color: rgba(255, 255, 255, 0.7); + } + } + + // Link nei messaggi propri + .message-bubble__text a { + color: #bbdefb; + } } + // Messaggi di sistema - centrati &--system { - justify-content: center; - padding: 8px 16px; - } - - &__system { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 16px; - background: rgba(0, 0, 0, 0.04); - border-radius: 16px; - font-size: 13px; - color: var(--q-grey-7); + align-self: center; + max-width: 90%; } + // Avatar &__avatar { - background: linear-gradient(135deg, var(--q-secondary), var(--q-primary)); - color: white; + flex-shrink: 0; + background: #e0e0e0; font-size: 12px; font-weight: 600; - flex-shrink: 0; + color: #666; } + // Content wrapper &__content { - max-width: 70%; - min-width: 80px; + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + max-width: 100%; } + // Sender name &__sender { font-size: 12px; font-weight: 600; color: var(--q-primary); - margin-bottom: 4px; margin-left: 12px; } + // Reply preview &__reply { display: flex; + align-items: stretch; gap: 8px; padding: 8px 12px; - margin-bottom: 4px; - background: rgba(0, 0, 0, 0.04); - border-radius: 12px; + background: rgba(0, 0, 0, 0.05); + border-radius: 8px; cursor: pointer; - transition: background 0.2s ease; + margin-bottom: 4px; + transition: background 0.2s; &:hover { background: rgba(0, 0, 0, 0.08); @@ -84,13 +111,14 @@ width: 3px; background: var(--q-primary); border-radius: 2px; + flex-shrink: 0; } &__reply-content { display: flex; flex-direction: column; gap: 2px; - overflow: hidden; + min-width: 0; } &__reply-sender { @@ -101,63 +129,76 @@ &__reply-text { font-size: 12px; - color: var(--q-grey-7); + color: #666; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + // Bubble principale &__bubble { - background: #f0f0f0; - border-radius: 18px 18px 18px 4px; + background: white; padding: 10px 14px; + border-radius: 18px 18px 18px 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); position: relative; + word-wrap: break-word; + overflow-wrap: break-word; } + // Testo messaggio &__text { margin: 0; font-size: 15px; line-height: 1.4; - word-wrap: break-word; + white-space: pre-wrap; a { - color: inherit; - text-decoration: underline; + color: var(--q-primary); + text-decoration: none; + + &:hover { + text-decoration: underline; + } } } + // Messaggio eliminato &__deleted { margin: 0; font-size: 14px; font-style: italic; - color: var(--q-grey-6); + color: #999; display: flex; align-items: center; - gap: 4px; + gap: 6px; } + // Footer &__footer { display: flex; align-items: center; gap: 4px; margin-top: 4px; + justify-content: flex-start; } &__time { font-size: 11px; - color: var(--q-grey-6); + color: #999; } &__edited { font-size: 10px; + color: #999; font-style: italic; - color: var(--q-grey-5); } &__status { margin-left: 2px; } + // Reactions &__reactions { display: flex; flex-wrap: wrap; @@ -168,43 +209,61 @@ &__reaction { display: inline-flex; align-items: center; + gap: 2px; padding: 2px 8px; - background: rgba(0, 0, 0, 0.06); + background: white; border-radius: 12px; font-size: 14px; cursor: pointer; - transition: background 0.2s ease; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + transition: transform 0.2s; &:hover { - background: rgba(0, 0, 0, 0.1); + transform: scale(1.1); } } + // Menu button &__menu-btn { opacity: 0; - transition: opacity 0.2s ease; + transition: opacity 0.2s; + flex-shrink: 0; } &:hover &__menu-btn { opacity: 1; } + // System message + &__system { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: rgba(0, 0, 0, 0.05); + border-radius: 16px; + font-size: 13px; + color: #666; + text-align: center; + } + // Special messages &__special { display: flex; align-items: flex-start; gap: 12px; - padding: 8px; - background: rgba(var(--q-primary-rgb), 0.08); - border-radius: 12px; - margin-bottom: 4px; + padding: 4px 0; &--success { - background: rgba(var(--q-positive-rgb), 0.08); + .message-bubble__special-title { + color: var(--q-positive); + } } &--error { - background: rgba(var(--q-negative-rgb), 0.08); + .message-bubble__special-title { + color: var(--q-negative); + } } } @@ -220,28 +279,30 @@ } &__special-text { - font-size: 13px; - color: var(--q-grey-8); + font-size: 14px; + opacity: 0.9; } + // Location message &__location { display: flex; flex-direction: column; align-items: center; gap: 8px; - padding: 8px; + padding: 8px 0; } &__location-preview { width: 100%; - height: 80px; - background: linear-gradient(135deg, #e8f5e9, #c8e6c9); + height: 100px; + background: #e8f5e9; border-radius: 8px; display: flex; align-items: center; justify-content: center; } + // Ride share &__ride-share { display: flex; align-items: center; @@ -250,50 +311,22 @@ } } -// Dark mode -.body--dark { +// Responsive +@media (max-width: 600px) { .message-bubble { - &__system { - background: rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.7); - } + max-width: 90%; + padding: 0 12px; &__bubble { - background: #2d2d2d; - color: white; + padding: 8px 12px; } - &--own .message-bubble__bubble { - background: linear-gradient(135deg, var(--q-primary), #1565c0); + &__text { + font-size: 14px; } - &__reply { - background: rgba(255, 255, 255, 0.08); - - &:hover { - background: rgba(255, 255, 255, 0.12); - } - } - - &__reply-text { - color: rgba(255, 255, 255, 0.6); - } - - &__reaction { - background: rgba(255, 255, 255, 0.1); - - &:hover { - background: rgba(255, 255, 255, 0.15); - } - } - } -} - -// Responsive -@media (max-width: 599px) { - .message-bubble { - &__content { - max-width: 85%; + &__menu-btn { + opacity: 1; // Sempre visibile su mobile } } } \ No newline at end of file diff --git a/src/modules/trasporti/components/chat/MessageBubble.ts b/src/modules/trasporti/components/chat/MessageBubble.ts index 8ef5c061..a96b3b9a 100644 --- a/src/modules/trasporti/components/chat/MessageBubble.ts +++ b/src/modules/trasporti/components/chat/MessageBubble.ts @@ -29,14 +29,24 @@ export default defineComponent({ replyTo: { type: Object as PropType, default: null + }, + // AGGIUNGI QUESTA PROP + sender: { + type: Object as PropType, + default: null } }, - emits: ['reply', 'delete', 'reply-click', 'reaction-click', 'ride-click'], + emits: ['reply', 'delete', 'reply-click', 'reaction-click', 'ride-click', 'react'], setup(props, { emit }) { - // Sender info - const sender = computed(() => { + // Sender info - USA LA PROP sender SE DISPONIBILE + const senderData = computed(() => { + // Prima controlla la prop sender + if (props.sender) { + return props.sender; + } + // Fallback: estrai da message.senderId se è un oggetto if (typeof props.message.senderId === 'object') { return props.message.senderId as UserBasic; } @@ -44,14 +54,14 @@ export default defineComponent({ }); const senderName = computed(() => { - if (sender.value?.name) { - return `${sender.value.name} ${sender.value.surname?.[0] || ''}`.trim(); + if (senderData.value?.name) { + return `${senderData.value.name} ${senderData.value.surname?.[0] || ''}`.trim(); } - return sender.value?.username || 'Utente'; + return senderData.value?.username || 'Utente'; }); const senderImg = computed(() => { - return (sender.value as any)?.profile?.img; + return (senderData.value as any)?.profile?.img; }); const senderInitials = computed(() => { @@ -148,7 +158,7 @@ export default defineComponent({ }; return { - sender, + senderData, senderName, senderImg, senderInitials, diff --git a/src/modules/trasporti/components/ride/CityAutocomplete.vue b/src/modules/trasporti/components/ride/CityAutocomplete.vue index ee8477c2..e64f0447 100644 --- a/src/modules/trasporti/components/ride/CityAutocomplete.vue +++ b/src/modules/trasporti/components/ride/CityAutocomplete.vue @@ -238,9 +238,9 @@ export default defineComponent({ // Methods const loadRecentTripsFromServer = async () => { try { - const response = await Api.SendReq('/api/trasporti/cities/recent', 'GET'); - if (response.success && response.data?.data?.cities) { - serverRecentTrips.value = response.data.data.cities; + const response = await Api.SendReqWithData('/api/trasporti/cities/recent', 'GET'); + if (response.success && response.data?.cities) { + serverRecentTrips.value = response.data.cities; } } catch (error) { console.error('Error loading recent trips:', error); diff --git a/src/modules/trasporti/components/widgets/RideWidget.ts b/src/modules/trasporti/components/widgets/RideWidget.ts index 5cc14ce8..ddbfe63f 100644 --- a/src/modules/trasporti/components/widgets/RideWidget.ts +++ b/src/modules/trasporti/components/widgets/RideWidget.ts @@ -72,10 +72,10 @@ export default defineComponent({ loading.value = true; try { - const response = await Api.SendReq('/api/trasporti/widget/data', 'GET', {}); + const response = await Api.SendReqWithData('/api/trasporti/widget/data', 'GET', {}); if (response.success) { - const data: WidgetData = response.data.data; + const data: WidgetData = response.data; stats.value = data.stats || { offers: 0, requests: 0, matches: 0 }; recentRides.value = data.recentRides || []; @@ -95,10 +95,10 @@ export default defineComponent({ const loadStats = async () => { try { - const response = await Api.SendReq('/api/trasporti/stats/summary', 'GET'); + const response = await Api.SendReqWithData('/api/trasporti/stats/summary', 'GET'); if (response.success) { - stats.value = response.data.data; + stats.value = response.data; } } catch (error) { console.error('Errore caricamento stats:', error); diff --git a/src/modules/trasporti/composables/useAuth.ts b/src/modules/trasporti/composables/useAuth.ts new file mode 100644 index 00000000..1ff2c657 --- /dev/null +++ b/src/modules/trasporti/composables/useAuth.ts @@ -0,0 +1,16 @@ +// composables/useAuth.ts +import { computed } from 'vue'; +import { useUserStore } from '@store/UserStore'; + +export function useAuth() { + const userStore = useUserStore() + const user = computed(() => userStore.my); + const isAuthenticated = computed(() => !!userStore.my); + const userId = computed(() => userStore.my?._id); + + return { + user, + isAuthenticated, + userId + }; +} diff --git a/src/modules/trasporti/composables/useChat.ts b/src/modules/trasporti/composables/useChat.ts index 34dd5ab7..820f8dac 100644 --- a/src/modules/trasporti/composables/useChat.ts +++ b/src/modules/trasporti/composables/useChat.ts @@ -2,6 +2,7 @@ import { ref, computed } from 'vue'; import { Api } from '@api'; import type { Chat, Message } from '../types/trasporti.types'; +import { tools } from 'app/src/store/Modules/tools'; // ============================================================ // STATE @@ -21,13 +22,27 @@ const error = ref(null); const onlineUsers = ref([]); const typingUsers = ref([]); +interface FetchMessagesOptions { + loadOlder?: boolean; // Carica messaggi più vecchi + loadNewer?: boolean; // Carica messaggi più recenti + reset?: boolean; // Reset completo + limit?: number; +} + +const hasOlderMessages = ref(true); +const hasNewerMessages = ref(false); + +// POLLING STATE +let pollingInterval: ReturnType | null = null; +const isPolling = ref(false); +const POLLING_INTERVAL = 3000; // 3 secondi + // ============================================================ // COMPOSABLE // ============================================================ export function useChat() { // ID app per trasporti - const IDAPP = 'trasporti'; // ------------------------------------------------------------ // COMPUTED @@ -64,16 +79,16 @@ export function useChat() { loading.value = true; error.value = null; - const response = await Api.SendReq( - `/api/trasporti/chats?idapp=${IDAPP}&page=${page}&limit=${limit}`, + const response = await Api.SendReqWithData( + `/api/trasporti/chats?page=${page}&limit=${limit}`, 'GET' ); - if (response.success && response.data.data) { - chats.value = response.data.data; + if (response.success && response.data) { + chats.value = response.data; // Calcola unread totale - totalUnreadCount.value = response.data.data.reduce( + totalUnreadCount.value = response.data.reduce( (sum: number, chat: any) => sum + (chat.unreadCount || 0), 0 ); @@ -96,21 +111,16 @@ export function useChat() { loading.value = true; error.value = null; - const response = await Api.SendReq( - '/api/trasporti/chats/direct', - 'POST', - { - idapp: IDAPP, - otherUserId, - rideId - } - ); + const response = await Api.SendReqWithData('/api/trasporti/chats/direct', 'POST', { + otherUserId, + rideId, + }); - if (response.success && response.data.data) { - currentChat.value = response.data.data; + if (response.success && response.data) { + currentChat.value = response.data; // Aggiungi alla lista se non presente - const exists = chats.value.find(c => c._id === currentChat.value?._id); + const exists = chats.value.find((c) => c._id === currentChat.value?._id); if (!exists && currentChat.value) { chats.value.unshift(currentChat.value); } @@ -140,13 +150,10 @@ export function useChat() { loading.value = true; error.value = null; - const response = await Api.SendReq( - `/api/trasporti/chats/${chatId}`, - 'GET' - ); + const response = await Api.SendReqWithData(`/api/trasporti/chats/${chatId}`, 'GET'); - if (response.success && response.data?.data) { - currentChat.value = response.data?.data; + if (response.success && response.data) { + currentChat.value = response.data; } return response; @@ -168,48 +175,86 @@ export function useChat() { const response = await fetchMessages(chatId, options); return response.data || []; }; +const fetchMessages = async ( + chatId: string, + options: FetchMessagesOptions = {} +) => { + try { + loadingMessages.value = true; + error.value = null; - /** - * Ottieni messaggi di una chat - */ - const fetchMessages = async ( - chatId: string, - options?: { before?: string; after?: string; limit?: number } - ) => { - try { - loadingMessages.value = true; - error.value = null; + const params = new URLSearchParams({ idapp: tools.getIdApp() }); - const params = new URLSearchParams({ idapp: IDAPP }); - if (options?.before) params.append('before', options.before); - if (options?.after) params.append('after', options.after); - if (options?.limit) params.append('limit', options.limit.toString()); - - const response = await Api.SendReq( - `/api/trasporti/chats/${chatId}/messages?${params.toString()}`, - 'GET' - ); - - if (response.success && response.data?.data) { - const newMessages = response.data?.data; - - if (options?.before) { - // Caricamento messaggi precedenti - aggiungi all'inizio - messages.value = [...newMessages, ...messages.value]; - } else { - // Primo caricamento - messages.value = newMessages; - } - } - - return response; - } catch (err: any) { - error.value = err.message || 'Errore nel recupero dei messaggi'; - throw err; - } finally { - loadingMessages.value = false; + // ✅ Determina i parametri in base al tipo di caricamento + if (options.loadOlder && messages.value.length > 0) { + // Carica messaggi più vecchi del primo messaggio attuale + const oldestMessage = messages.value[0]; + params.append('before', oldestMessage.createdAt); + } else if (options.loadNewer && messages.value.length > 0) { + // Carica messaggi più recenti dell'ultimo messaggio attuale + const newestMessage = messages.value[messages.value.length - 1]; + params.append('after', newestMessage.createdAt); } - }; + + if (options.limit) { + params.append('limit', options.limit.toString()); + } + + const response = await Api.SendReqWithData( + `/api/trasporti/chats/${chatId}/messages?${params.toString()}`, + 'GET' + ); + + if (response.success && response.data) { + const newMessages = response.data; + + // ✅ Gestione chiara dei diversi scenari + if (options.reset || messages.value.length === 0) { + // Primo caricamento o reset + messages.value = newMessages; + hasOlderMessages.value = response.hasMore; + } else if (options.loadOlder) { + // Aggiungi messaggi più vecchi all'inizio + messages.value = [...newMessages, ...messages.value]; + hasOlderMessages.value = response.hasMore; + } else if (options.loadNewer) { + // Aggiungi messaggi più recenti alla fine + messages.value = [...messages.value, ...newMessages]; + hasNewerMessages.value = response.hasMore; + } + } + + return response; + } catch (err: any) { + error.value = err.message || 'Errore nel recupero dei messaggi'; + throw err; + } finally { + loadingMessages.value = false; + } +}; + +// ✅ Metodi helper per chiamare facilmente +const loadInitialMessages = (chatId: string) => { + return fetchMessages(chatId, { reset: true }); +}; + +const loadOlderMessages = (chatId: string) => { + if (!hasOlderMessages.value || loadingMessages.value) return; + return fetchMessages(chatId, { loadOlder: true }); +}; + +const loadNewerMessages = (chatId: string) => { + if (!hasNewerMessages.value || loadingMessages.value) return; + return fetchMessages(chatId, { loadNewer: true }); +}; + +// ✅ Aggiungi un nuovo messaggio ricevuto in tempo reale +const addNewMessage = (message: Message) => { + // Evita duplicati + if (!messages.value.find(m => m._id === message._id)) { + messages.value.push(message); + } +}; /** * Invia messaggio - compatibile con ChatPage @@ -231,20 +276,19 @@ export function useChat() { // Supporta sia content che text const messageText = payload.content || payload.text || ''; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/chats/${chatId}/messages`, 'POST', { - idapp: IDAPP, text: messageText, type: payload.type || 'text', metadata: payload.metadata, - replyTo: payload.replyTo + replyTo: payload.replyTo, } ); - if (response.success && response.data?.data) { - const newMessage = response.data?.data; + if (response.success && response.data) { + const newMessage = response.data; messages.value.push(newMessage); // Aggiorna lastMessage nella chat @@ -253,7 +297,7 @@ export function useChat() { return response; } catch (err: any) { - error.value = err.message || 'Errore nell\'invio del messaggio'; + error.value = err.message || "Errore nell'invio del messaggio"; throw err; } finally { sending.value = false; @@ -264,13 +308,13 @@ export function useChat() { * Aggiorna lastMessage nella chat locale */ const updateChatLastMessage = (chatId: string, message: Message) => { - const chatIndex = chats.value.findIndex(c => c._id === chatId); + const chatIndex = chats.value.findIndex((c) => c._id === chatId); if (chatIndex !== -1) { chats.value[chatIndex].lastMessage = { text: message.text || '', senderId: message.senderId as any, timestamp: message.createdAt, - type: message.type || 'text' + type: message.type || 'text', }; chats.value[chatIndex].updatedAt = message.createdAt; } @@ -280,7 +324,7 @@ export function useChat() { text: message.text || '', senderId: message.senderId as any, timestamp: message.createdAt, - type: message.type || 'text' + type: message.type || 'text', }; currentChat.value.updatedAt = message.createdAt; } @@ -291,14 +335,14 @@ export function useChat() { */ const markAsRead = async (chatId: string) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/chats/${chatId}/read`, 'PUT' ); if (response.success) { // Aggiorna contatore locale - const chatIndex = chats.value.findIndex(c => c._id === chatId); + const chatIndex = chats.value.findIndex((c) => c._id === chatId); if (chatIndex !== -1) { const unread = chats.value[chatIndex].unreadCount || 0; totalUnreadCount.value = Math.max(0, totalUnreadCount.value - unread); @@ -321,13 +365,13 @@ export function useChat() { */ const fetchUnreadCount = async () => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/chats/unread/count?idapp=${IDAPP}`, 'GET' ); - if (response.success && response.data?.data) { - totalUnreadCount.value = response.data?.data.total || 0; + if (response.success && response.data) { + totalUnreadCount.value = response.data.total || 0; } return response; @@ -341,13 +385,13 @@ export function useChat() { */ const deleteMessage = async (chatId: string, messageId: string) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/chats/${chatId}/messages/${messageId}`, 'DELETE' ); if (response.success) { - const index = messages.value.findIndex(m => m._id === messageId); + const index = messages.value.findIndex((m) => m._id === messageId); if (index !== -1) { messages.value[index].isDeleted = true; messages.value[index].text = '[Messaggio eliminato]'; @@ -356,7 +400,7 @@ export function useChat() { return response; } catch (err: any) { - error.value = err.message || 'Errore nell\'eliminazione del messaggio'; + error.value = err.message || "Errore nell'eliminazione del messaggio"; throw err; } }; @@ -366,7 +410,7 @@ export function useChat() { */ const toggleBlockChat = async (chatId: string, block: boolean) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/chats/${chatId}/block`, 'PUT', { block } @@ -384,7 +428,7 @@ export function useChat() { */ const toggleMuteChat = async (chatId: string, mute: boolean) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/chats/${chatId}/mute`, 'PUT', { mute } @@ -473,6 +517,64 @@ export function useChat() { messages.value = []; }; + // ============================================================ +// POLLING +// ============================================================ + +/** + * Avvia polling per nuovi messaggi + */ +const startPolling = (chatId: string, intervalMs = POLLING_INTERVAL) => { + stopPolling(); // Ferma eventuale polling precedente + isPolling.value = true; + + console.log('[useChat] Polling avviato per chat:', chatId); + + pollingInterval = setInterval(async () => { + try { + // Solo se ci sono già messaggi caricati + if (messages.value.length > 0) { + const newestMessage = messages.value[messages.value.length - 1]; + + const params = new URLSearchParams({ + idapp: tools.getIdApp(), + after: newestMessage.createdAt + }); + + const response = await Api.SendReqWithData( + `/api/trasporti/chats/${chatId}/messages?${params.toString()}`, + 'GET' + ); + + if (response.success && response.data && response.data.length > 0) { + console.log('[useChat] Polling - ricevuti', response.data.length, 'nuovi messaggi'); + + // Aggiungi nuovi messaggi evitando duplicati + response.data.forEach((newMsg: Message) => { + if (!messages.value.find(m => m._id === newMsg._id)) { + messages.value.push(newMsg); + } + }); + } + } + } catch (err) { + console.error('[useChat] Errore polling:', err); + } + }, intervalMs); +}; + +/** + * Ferma polling + */ +const stopPolling = () => { + if (pollingInterval) { + clearInterval(pollingInterval); + pollingInterval = null; + isPolling.value = false; + console.log('[useChat] Polling fermato'); + } +}; + // ------------------------------------------------------------ // RETURN // ------------------------------------------------------------ @@ -489,12 +591,16 @@ export function useChat() { error, onlineUsers, typingUsers, + isPolling, // AGGIUNGI + // Computed hasChats, hasUnread, sortedChats, sortedMessages, + hasOlderMessages, // AGGIUNGI + hasNewerMessages, // AGGIUNGI // API Methods fetchChats, @@ -509,6 +615,14 @@ export function useChat() { toggleBlockChat, toggleMuteChat, deleteMessage, + loadOlderMessages, // AGGIUNGI + loadNewerMessages, // AGGIUNGI + addNewMessage, // AGGIUNGI + + // Polling + startPolling, // AGGIUNGI + stopPolling, // AGGIUNGI + // Real-time (placeholder) sendTyping, @@ -519,6 +633,6 @@ export function useChat() { formatMessageTime, openChat, clearState, - closeCurrentChat + closeCurrentChat, }; } diff --git a/src/modules/trasporti/composables/useCitySuggestions.ts b/src/modules/trasporti/composables/useCitySuggestions.ts index b4ead7bf..b8485682 100644 --- a/src/modules/trasporti/composables/useCitySuggestions.ts +++ b/src/modules/trasporti/composables/useCitySuggestions.ts @@ -55,13 +55,13 @@ export function useCitySuggestions() { lastQuery.value = query; try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/cities/suggestions?q=${encodeURIComponent(query)}`, 'GET' ); if (response.success) { - suggestions.value = response.data?.data.suggestions || []; + suggestions.value = response.data.suggestions || []; } else { error.value = response.message || 'Errore nel caricamento dei suggerimenti'; suggestions.value = []; diff --git a/src/modules/trasporti/composables/useContribTypes.ts b/src/modules/trasporti/composables/useContribTypes.ts index ff401f3f..d85f2910 100644 --- a/src/modules/trasporti/composables/useContribTypes.ts +++ b/src/modules/trasporti/composables/useContribTypes.ts @@ -68,13 +68,13 @@ export function useContribTypes() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( '/api/trasporti/contrib-types', 'GET' ) as ApiResponse; - if (response.success && response.data?.data) { - contribTypes.value = response.data.data; + if (response.success && response.data) { + contribTypes.value = response.data; fetched = true; } diff --git a/src/modules/trasporti/composables/useDriverProfile.ts b/src/modules/trasporti/composables/useDriverProfile.ts index c25d10d9..333177d4 100644 --- a/src/modules/trasporti/composables/useDriverProfile.ts +++ b/src/modules/trasporti/composables/useDriverProfile.ts @@ -4,9 +4,8 @@ import type { DriverProfile, Vehicle, UserPreferences, - DriverPublicProfile, - ApiResponse -} from '../types'; + DriverPublicProfile +} from '../types/trasporti.types'; // ============================================================ // STATE @@ -53,18 +52,18 @@ export function useDriverProfile() { loading.value = true; error.value = null; - const response = await Api.SendReq( - `/api/trasporti/driver/${userId}`, + const response = await Api.SendReqWithData( + `/api/trasporti/driver/user/${userId}`, 'GET' - ) as ApiResponse; + ); - if (response.success && response.data?.data) { - driverProfile.value = response.data.data; + if (response.success && response.data) { + driverProfile.value = response.data; } return response; } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nel recupero del profilo'; + error.value = err.message || 'Errore nel recupero del profilo'; throw err; } finally { loading.value = false; @@ -72,27 +71,38 @@ export function useDriverProfile() { }; /** - * Aggiorna il mio profilo conducente + * Aggiorna il mio profilo conducente e/o preferenze */ - const updateDriverProfile = async (profileData: Partial) => { + const updateDriverProfile = async (data: { + driverProfile?: Partial; + preferences?: Partial; + }) => { try { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( '/api/trasporti/driver/profile', 'PUT', - { driverProfile: profileData } - ) as ApiResponse<{ driverProfile: DriverProfile; preferences: UserPreferences }>; + { + idapp: 'trasporti', + ...data + } + ); - if (response.success && response.data?.data) { - myDriverProfile.value = response.data?.data.driverProfile; - myPreferences.value = response.data?.data.preferences; + if (response.success && response.data) { + // Il backend ritorna user.profile che contiene driverProfile e preferences + if (response.data.driverProfile) { + myDriverProfile.value = response.data.driverProfile; + } + if (response.data.preferences) { + myPreferences.value = response.data.preferences; + } } return response; } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nell\'aggiornamento del profilo'; + error.value = err.message || 'Errore nell\'aggiornamento del profilo'; throw err; } finally { loading.value = false; @@ -100,53 +110,33 @@ export function useDriverProfile() { }; /** - * Aggiorna le mie preferenze + * Aggiorna solo le preferenze */ const updatePreferences = async (preferences: Partial) => { - try { - loading.value = true; - error.value = null; - - const response = await Api.SendReq( - '/api/trasporti/driver/profile', - 'PUT', - { preferences } - ) as ApiResponse<{ driverProfile: DriverProfile; preferences: UserPreferences }>; - - if (response.success && response.data?.data) { - myPreferences.value = response.data?.data.preferences; - } - - return response; - } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nell\'aggiornamento delle preferenze'; - throw err; - } finally { - loading.value = false; - } + return await updateDriverProfile({ preferences }); }; /** * Aggiungi veicolo */ - const addVehicle = async (vehicle: Omit) => { + const addVehicle = async (vehicleData: Omit) => { try { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( '/api/trasporti/driver/vehicles', 'POST', - { vehicle } - ) as ApiResponse; + { vehicle: vehicleData } + ); - if (response.success && response.data?.data) { - myVehicles.value = response.data.data; + if (response.success && response.data) { + myVehicles.value = response.data; } return response; } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nell\'aggiunta del veicolo'; + error.value = err.message || 'Errore nell\'aggiunta del veicolo'; throw err; } finally { loading.value = false; @@ -156,24 +146,24 @@ export function useDriverProfile() { /** * Aggiorna veicolo */ - const updateVehicle = async (vehicleId: string, vehicle: Partial) => { + const updateVehicle = async (vehicleId: string, vehicleData: Partial) => { try { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/driver/vehicles/${vehicleId}`, 'PUT', - { vehicle } - ) as ApiResponse; + { vehicle: vehicleData } + ); - if (response.success && response.data?.data) { - myVehicles.value = response.data.data; + if (response.success && response.data) { + myVehicles.value = response.data; } return response; } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nell\'aggiornamento del veicolo'; + error.value = err.message || 'Errore nell\'aggiornamento del veicolo'; throw err; } finally { loading.value = false; @@ -188,10 +178,10 @@ export function useDriverProfile() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/driver/vehicles/${vehicleId}`, 'DELETE' - ) as ApiResponse; + ); if (response.success) { myVehicles.value = myVehicles.value.filter(v => v._id !== vehicleId); @@ -199,7 +189,7 @@ export function useDriverProfile() { return response; } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nella rimozione del veicolo'; + error.value = err.message || 'Errore nella rimozione del veicolo'; throw err; } finally { loading.value = false; @@ -214,10 +204,10 @@ export function useDriverProfile() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/driver/vehicles/${vehicleId}/default`, 'POST' - ) as ApiResponse; + ); if (response.success) { // Aggiorna localmente @@ -229,7 +219,7 @@ export function useDriverProfile() { return response; } catch (err: any) { - error.value = err.data?.message || err.message ||'Errore nell\'impostazione del veicolo predefinito'; + error.value = err.message || 'Errore nell\'impostazione del veicolo predefinito'; throw err; } finally { loading.value = false; @@ -332,7 +322,7 @@ export function useDriverProfile() { }; /** - * Inizializza profilo dal user corrente + * Inizializza profilo dal user corrente (userStore) */ const initFromUser = (user: any) => { if (user?.profile?.driverProfile) { diff --git a/src/modules/trasporti/composables/useFeedback.ts b/src/modules/trasporti/composables/useFeedback.ts index bfc4011c..ee1d4fec 100644 --- a/src/modules/trasporti/composables/useFeedback.ts +++ b/src/modules/trasporti/composables/useFeedback.ts @@ -59,14 +59,14 @@ export function useFeedback() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( '/api/trasporti/feedback', 'POST', feedbackData ) as ApiResponse; - if (response.success && response.data?.data) { - myGivenFeedback.value.unshift(response.data?.data); + if (response.success && response.data) { + myGivenFeedback.value.unshift(response.data); } return response; @@ -91,7 +91,7 @@ export function useFeedback() { if (options?.page) queryParams.append('page', options.page.toString()); if (options?.limit) queryParams.append('limit', options.limit.toString()); - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/user/${userId}?${queryParams.toString()}`, 'GET' ) as ApiResponse<{ @@ -100,10 +100,10 @@ export function useFeedback() { distribution: RatingDistribution[]; }>; - if (response.success && response.data?.data) { - feedbacks.value = response.data?.data.feedbacks; - currentUserStats.value = response.data?.data.stats; - ratingDistribution.value = response.data?.data.distribution; + if (response.success && response.data) { + feedbacks.value = response.data.feedbacks; + currentUserStats.value = response.data.stats; + ratingDistribution.value = response.data.distribution; } return response; @@ -120,7 +120,7 @@ export function useFeedback() { */ const fetchUserStats = async (userId: string) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/user/${userId}/stats`, 'GET' ) as ApiResponse<{ @@ -129,8 +129,8 @@ export function useFeedback() { commonTags: { _id: FeedbackTag; count: number }[]; }>; - if (response.success && response.data?.data) { - currentUserStats.value = response.data?.data.stats; + if (response.success && response.data) { + currentUserStats.value = response.data.stats; } return response; @@ -148,13 +148,13 @@ export function useFeedback() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/ride/${rideId}`, 'GET' ) as ApiResponse; - if (response.success && response.data?.data) { - feedbacks.value = response.data.data; + if (response.success && response.data) { + feedbacks.value = response.data; } return response; @@ -171,12 +171,12 @@ export function useFeedback() { */ const canLeaveFeedback = async (rideId: string, toUserId: string) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/can-leave/${rideId}/${toUserId}`, 'GET' ) as ApiResponse<{ canLeave: boolean; reason?: string }>; - return response.data?.data; + return response.data; } catch (err: any) { console.error('Errore verifica feedback:', err); return { canLeave: false, reason: 'Errore nella verifica' }; @@ -194,14 +194,14 @@ export function useFeedback() { const queryParams = new URLSearchParams(); if (role) queryParams.append('role', role); - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/my/received?${queryParams.toString()}`, 'GET' ) as ApiResponse<{ feedbacks: Feedback[]; stats: FeedbackStats }>; - if (response.success && response.data?.data) { - myReceivedFeedback.value = response.data?.data.feedbacks; - currentUserStats.value = response.data?.data.stats; + if (response.success && response.data) { + myReceivedFeedback.value = response.data.feedbacks; + currentUserStats.value = response.data.stats; } return response; @@ -221,13 +221,13 @@ export function useFeedback() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( '/api/trasporti/feedback/my/given', 'GET' ) as PaginatedResponse; if (response.success) { - myGivenFeedback.value = response.data.data; + myGivenFeedback.value = response.data; } return response; @@ -247,17 +247,17 @@ export function useFeedback() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/${feedbackId}/response`, 'POST', { text } ) as ApiResponse; - if (response.success && response.data?.data) { + if (response.success && response.data) { // Aggiorna nella lista const index = myReceivedFeedback.value.findIndex(f => f._id === feedbackId); if (index !== -1) { - myReceivedFeedback.value[index] = response.data.data; + myReceivedFeedback.value[index] = response.data; } } @@ -275,7 +275,7 @@ export function useFeedback() { */ const reportFeedback = async (feedbackId: string, reason: string) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/${feedbackId}/report`, 'POST', { reason } @@ -293,15 +293,15 @@ export function useFeedback() { */ const markAsHelpful = async (feedbackId: string) => { try { - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/feedback/${feedbackId}/helpful`, 'POST' ) as ApiResponse<{ helpfulCount: number }>; - if (response.success && response.data?.data) { + if (response.success && response.data) { const feedback = feedbacks.value.find(f => f._id === feedbackId); if (feedback && feedback.helpful) { - feedback.helpful.count = response.data?.data.helpfulCount; + feedback.helpful.count = response.data.helpfulCount; } } diff --git a/src/modules/trasporti/composables/useRealtimeChat.ts b/src/modules/trasporti/composables/useRealtimeChat.ts index 874ae4a3..158d0bfe 100644 --- a/src/modules/trasporti/composables/useRealtimeChat.ts +++ b/src/modules/trasporti/composables/useRealtimeChat.ts @@ -96,7 +96,7 @@ export function useRealtimeChat() { // Simula utenti online (in produzione questi dati verrebbero dal server) if (currentChat.value?.participants) { - currentChat.value.participants.forEach(participant => { + currentChat.value.participants.forEach((participant: any) => { if (participant._id) { simulateUserOnline(participant._id); } @@ -127,7 +127,7 @@ export function useRealtimeChat() { console.log(`Typing in chat: ${chatId}`); // In produzione: - // await Api.SendReq(`/api/trasporti/chats/${chatId}/typing`, 'POST'); + // await Api.SendReqWithData(`/api/trasporti/chats/${chatId}/typing`, 'POST'); }; /** diff --git a/src/modules/trasporti/composables/useRideRequests.ts b/src/modules/trasporti/composables/useRideRequests.ts index 9f3995f6..211b6c5c 100644 --- a/src/modules/trasporti/composables/useRideRequests.ts +++ b/src/modules/trasporti/composables/useRideRequests.ts @@ -69,15 +69,15 @@ export function useRideRequests() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( '/api/trasporti/requests', 'POST', requestData, false, true ) as ApiResponse<{ request: RideRequest; chatId: string }>; - if (response.success && response.data.data) { - sentRequests.value.unshift(response.data.data.request); + if (response.success && response.data) { + sentRequests.value.unshift(response.data.request); } return response; @@ -102,15 +102,15 @@ export function useRideRequests() { queryParams.append('page', pagination.page.toString()); queryParams.append('limit', pagination.limit.toString()); - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/received?${queryParams.toString()}`, 'GET' ) as RequestsReceivedResponse; if (response.success) { - receivedRequests.value = response.data.data; - requestCounts.value = response.data?.data.counts; - Object.assign(pagination, response.data?.data.pagination); + receivedRequests.value = response.data.requests; + requestCounts.value = response.data.counts; + Object.assign(pagination, response.data.pagination); } return response; @@ -135,13 +135,13 @@ export function useRideRequests() { queryParams.append('page', pagination.page.toString()); queryParams.append('limit', pagination.limit.toString()); - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/sent?${queryParams.toString()}`, 'GET' ) as PaginatedResponse; if (response.success) { - sentRequests.value = response.data.data; + sentRequests.value = response.data; Object.assign(pagination, response?.data.pagination); } @@ -165,7 +165,7 @@ export function useRideRequests() { const queryParams = new URLSearchParams(); if (status) queryParams.append('status', status); - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/ride/${rideId}?${queryParams.toString()}`, 'GET' ) as ApiResponse; @@ -187,13 +187,13 @@ export function useRideRequests() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/${requestId}`, 'GET' ) as ApiResponse; - if (response.success && response.data?.data) { - currentRequest.value = response.data.data; + if (response.success && response.data) { + currentRequest.value = response.data; } return response; @@ -213,15 +213,15 @@ export function useRideRequests() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/${requestId}/accept`, 'POST', { responseMessage } ) as ApiResponse; - if (response.success && response.data?.data) { + if (response.success && response.data) { // Aggiorna nella lista - updateRequestInList(requestId, response.data?.data); + updateRequestInList(requestId, response.data); } return response; @@ -241,14 +241,14 @@ export function useRideRequests() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/${requestId}/reject`, 'POST', { responseMessage } ) as ApiResponse; if (response.success && response.dat?.data) { - updateRequestInList(requestId, response.data?.data); + updateRequestInList(requestId, response.data); } return response; @@ -268,14 +268,14 @@ export function useRideRequests() { loading.value = true; error.value = null; - const response = await Api.SendReq( + const response = await Api.SendReqWithData( `/api/trasporti/requests/${requestId}/cancel`, 'POST', { reason } ) as ApiResponse; - if (response.success && response.data?.data) { - updateRequestInList(requestId, response.data?.data); + if (response.success && response.data) { + updateRequestInList(requestId, response.data); } return response; diff --git a/src/modules/trasporti/composables/useRides.ts b/src/modules/trasporti/composables/useRides.ts index 60b24ba3..36f9e1f6 100644 --- a/src/modules/trasporti/composables/useRides.ts +++ b/src/modules/trasporti/composables/useRides.ts @@ -116,14 +116,14 @@ export function useRides() { if (filters.passingThrough) queryParams.append('passingThrough', filters.passingThrough); - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( `/api/trasporti/rides?${queryParams.toString()}`, 'GET' )) as PaginatedResponse; if (response.success) { // ✅ Ensure response.data is always an array - const newRides = Array.isArray(response.data.data) ? response.data.data : []; + const newRides = Array.isArray(response.data) ? response.data : []; if (options.reset) { rides.value = newRides; @@ -166,14 +166,14 @@ export function useRides() { queryParams.append('page', pagination.page.toString()); queryParams.append('limit', pagination.limit.toString()); - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( `/api/trasporti/rides/search?${queryParams.toString()}`, 'GET' )) as PaginatedResponse; if (response.success) { // ✅ Ensure response.data is always an array - rides.value = Array.isArray(response.data.data) ? response.data.data : []; + rides.value = Array.isArray(response.data) ? response.data : []; if (response?.data.pagination) { Object.assign(pagination, response?.data.pagination); @@ -198,13 +198,13 @@ export function useRides() { loading.value = true; error.value = null; - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( `/api/trasporti/rides/${rideId}`, 'GET' )) as ApiResponse; - if (response.success && response.data?.data) { - currentRide.value = response.data.data; + if (response.success && response.data) { + currentRide.value = response.data; } else { throw new Error('Viaggio non trovato'); } @@ -226,16 +226,16 @@ export function useRides() { loading.value = true; error.value = null; - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( '/api/trasporti/rides', 'POST', rideData )) as ApiResponse; - if (response.success && response.data?.data) { + if (response.success && response.data) { // Aggiungi in testa alla lista - rides.value.unshift(response.data?.data); - currentRide.value = response.data.data; + rides.value.unshift(response.data); + currentRide.value = response.data; } return response; @@ -255,20 +255,20 @@ export function useRides() { loading.value = true; error.value = null; - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( `/api/trasporti/rides/${rideId}`, 'PUT', updateData )) as ApiResponse; - if (response.success && response.data?.data) { + 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.data; + rides.value[index] = response.data; } if (currentRide.value?._id === rideId) { - currentRide.value = response.data.data; + currentRide.value = response.data; } } @@ -289,7 +289,7 @@ export function useRides() { loading.value = true; error.value = null; - const response = (await Api.SendReq(`/api/trasporti/rides/${rideId}`, 'DELETE', { + const response = (await Api.SendReqWithData(`/api/trasporti/rides/${rideId}`, 'DELETE', { reason, })) as ApiResponse; @@ -318,18 +318,18 @@ export function useRides() { loading.value = true; error.value = null; - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( `/api/trasporti/rides/${rideId}/complete`, 'POST' )) as ApiResponse; - if (response.success && response.data?.data) { + if (response.success && response.data) { const index = rides.value.findIndex((r) => r._id === rideId); if (index !== -1) { - rides.value[index] = response.data.data; + rides.value[index] = response.data; } if (currentRide.value?._id === rideId) { - currentRide.value = response.data.data; + currentRide.value = response.data; } } @@ -361,15 +361,15 @@ export function useRides() { queryParams.append('page', pagination.page.toString()); queryParams.append('limit', pagination.limit.toString()); - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( `/api/trasporti/rides/my?${queryParams.toString()}`, 'GET' )) as MyRidesResponse; - if (response.success && response.data?.data) { - myRides.all = response.data.data.all; - myRides.upcoming = response.data.data.upcoming; - myRides.past = response.data.data.past; + 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); } @@ -387,13 +387,13 @@ export function useRides() { */ const fetchStats = async () => { try { - const response = (await Api.SendReq( + const response = (await Api.SendReqWithData( '/api/trasporti/rides/stats', 'GET' )) as ApiResponse; if (response.success && response.data) { - stats.value = response.data.data; + stats.value = response.data; } return response; diff --git a/src/modules/trasporti/pages/ChatListPage.scss b/src/modules/trasporti/pages/ChatListPage.scss index bba5464d..5c217a8c 100644 --- a/src/modules/trasporti/pages/ChatListPage.scss +++ b/src/modules/trasporti/pages/ChatListPage.scss @@ -1,142 +1,88 @@ // ChatListPage.scss .chat-list-page { - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - min-height: 100vh; - - // Header &__header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 24px 20px; - border-radius: 0 0 24px 24px; - margin-bottom: 8px; + padding: 16px; + background: white; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); } &__title-section { display: flex; align-items: center; - gap: 16px; - margin-bottom: 20px; + gap: 12px; } &__icon { - font-size: 40px; - opacity: 0.9; + font-size: 32px; } &__title { - font-size: 28px; - font-weight: 700; margin: 0; - letter-spacing: -0.5px; + font-size: 24px; + font-weight: 600; } &__subtitle { - margin: 4px 0 0; - opacity: 0.85; + margin: 0; + color: $grey-6; font-size: 14px; } &__search { - :deep(.q-field__control) { - background: rgba(255, 255, 255, 0.2); - border-radius: 12px; - - &::before { - border-color: transparent; - } - } - - :deep(.q-field__native), - :deep(.q-field__prefix), - :deep(.q-field__suffix) { - color: white; - } - - :deep(.q-field__native::placeholder) { - color: rgba(255, 255, 255, 0.7); - } + margin-top: 12px; } - // Tabs &__tabs { background: white; - margin: 0 12px; - border-radius: 16px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); - - :deep(.q-tab) { - text-transform: none; - font-weight: 500; - } + border-bottom: 1px solid rgba(0, 0, 0, 0.12); } &__tab-content { display: flex; align-items: center; - gap: 8px; + gap: 4px; } - // Content &__content { - padding: 16px 12px; + min-height: calc(100vh - 200px); } - &__loading { + &__loading, + &__empty { display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: 60px 20px; - color: #666; - - p { - margin-top: 16px; - } - } - - &__empty { + padding: 48px 24px; text-align: center; - padding: 60px 24px; h3 { - font-size: 20px; - font-weight: 600; - color: #333; - margin: 20px 0 8px; + margin: 16px 0 8px; + font-size: 18px; + font-weight: 500; } p { - color: #666; - margin-bottom: 24px; + margin: 0; + color: $grey-6; } } - // List &__list { - background: transparent; + background: white; } &__item { + position: relative; background: white; - border-radius: 16px; - margin-bottom: 8px; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - transition: all 0.2s ease; + transition: background-color 0.3s; - &:hover { - transform: translateY(-2px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + &:active { + background: rgba(0, 0, 0, 0.04); } &--unread { - border-left: 4px solid var(--q-primary); - background: linear-gradient(90deg, rgba(102, 126, 234, 0.05) 0%, white 100%); - } - - :deep(.q-item) { - padding: 16px; + background: rgba($primary, 0.02); } } @@ -145,26 +91,19 @@ flex-direction: column; align-items: center; justify-content: center; - padding: 0 24px; + height: 100%; + padding: 0 20px; color: white; - font-size: 12px; - font-weight: 500; - - .q-icon { - font-size: 24px; - margin-bottom: 4px; - } &--archive { - background: #2196f3; + background: $orange; } &--delete { - background: #f44336; + background: $negative; } } - // Avatar &__avatar-wrapper { position: relative; } @@ -175,125 +114,80 @@ display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: $primary; color: white; font-weight: 600; - font-size: 18px; - border-radius: 50%; + font-size: 20px; } &__online-dot { position: absolute; bottom: 2px; right: 2px; - width: 14px; - height: 14px; - background: #4caf50; - border: 3px solid white; + width: 12px; + height: 12px; + background: $positive; + border: 2px solid white; border-radius: 50%; } &__ride-badge { - padding: 3px; - min-height: 18px; - min-width: 18px; + .q-badge__content { + padding: 2px 4px; + } } - // Content &__name { - font-weight: 600; font-size: 16px; - color: #1a1a2e; + margin-bottom: 2px; } &__ride-info { display: flex; align-items: center; - gap: 4px; - color: #667eea; - font-weight: 500; - margin-top: 2px; + color: $primary; + font-size: 12px; + margin-bottom: 2px; } &__last-message { - color: #666; - margin-top: 6px; display: flex; align-items: center; - gap: 4px; + color: $grey-7; + font-size: 14px; &--unread { - font-weight: 600; - color: #333; + color: $grey-9; + font-weight: 500; } } - // Meta &__meta { display: flex; flex-direction: column; align-items: flex-end; - gap: 8px; + gap: 4px; } &__time { font-size: 12px; - color: #999; + color: $grey-6; + white-space: nowrap; } &__unread-badge { + font-size: 11px; font-weight: 600; - animation: pulse 2s infinite; } - @keyframes pulse { - 0%, 100% { - transform: scale(1); - } - 50% { - transform: scale(1.1); - } - } - - // Load more &__load-more { display: flex; justify-content: center; padding: 16px; } - // Search dialog &__search-dialog { - width: 100%; - max-width: 500px; - border-radius: 20px; - margin-top: 60px; - } -} - -// Dark mode -.body--dark { - .chat-list-page { - background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); - - &__tabs { - background: #1e1e30; - } - - &__item { - background: #1e1e30; - - &--unread { - background: linear-gradient(90deg, rgba(102, 126, 234, 0.1) 0%, #1e1e30 100%); - } - } - - &__name { - color: #fff; - } - - &__last-message--unread { - color: #e0e0e0; - } + max-height: 80vh; + overflow: auto; } } \ No newline at end of file diff --git a/src/modules/trasporti/pages/ChatListPage.ts b/src/modules/trasporti/pages/ChatListPage.ts index 9104d126..9d428653 100644 --- a/src/modules/trasporti/pages/ChatListPage.ts +++ b/src/modules/trasporti/pages/ChatListPage.ts @@ -1,11 +1,11 @@ // ChatListPage.ts -import { defineComponent, ref, computed, onMounted, onUnmounted } from 'vue'; +import { defineComponent, ref, computed, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { useQuasar } from 'quasar'; import { useChat } from '../composables/useChat'; -import { useAuth } from '@/composables/useAuth'; // Il tuo composable auth esistente -import type { Chat, User, Message } from '../types/trasporti.types'; -import { debounce } from 'quasar'; +import { useAuth } from '../composables/useAuth'; +import { Api } from '@api'; +import type { Chat, User, Message } from '../types'; export default defineComponent({ name: 'ChatListPage', @@ -17,12 +17,12 @@ export default defineComponent({ const { chats, loading, - loadChats, - archiveChat, + fetchChats, + getOrCreateDirectChat, + toggleMuteChat, deleteChat, - createChat, - searchUsers: searchUsersApi, - onlineUsers + onlineUsers, + totalUnreadCount } = useChat(); // State @@ -31,18 +31,19 @@ export default defineComponent({ const loadingMore = ref(false); const hasMore = ref(true); const page = ref(1); + + // ✅ User search const showUserSearch = ref(false); - const showGroupCreate = ref(false); const userSearchQuery = ref(''); const searchedUsers = ref([]); const searchingUsers = ref(false); + // ✅ Group chat + const showGroupCreate = ref(false); + // Computed const currentUserId = computed(() => currentUser.value?._id); - - const unreadCount = computed(() => { - return chats.value.reduce((total, chat) => total + (chat.unreadCount || 0), 0); - }); + const unreadCount = computed(() => totalUnreadCount.value); const filteredChats = computed(() => { let result = [...chats.value]; @@ -50,7 +51,7 @@ export default defineComponent({ // Filter by tab switch (activeTab.value) { case 'unread': - result = result.filter(chat => chat.unreadCount > 0); + result = result.filter(chat => (chat.unreadCount || 0) > 0); break; case 'rides': result = result.filter(chat => chat.rideId); @@ -69,23 +70,38 @@ export default defineComponent({ const otherUser = getOtherParticipant(chat); const fullName = `${otherUser?.name || ''} ${otherUser?.surname || ''}`.toLowerCase(); const username = otherUser?.username?.toLowerCase() || ''; - const rideInfo = chat.rideInfo - ? `${chat.rideInfo.departure} ${chat.rideInfo.destination}`.toLowerCase() - : ''; + + let rideInfo = ''; + if (chat.rideId) { + const rideData = typeof chat.rideId === 'object' ? chat.rideId : null; + if (rideData) { + const departure = typeof rideData.departure === 'string' + ? rideData.departure + : rideData.departure?.city || ''; + const destination = typeof rideData.destination === 'string' + ? rideData.destination + : rideData.destination?.city || ''; + rideInfo = `${departure} ${destination}`.toLowerCase(); + } + } + + const lastMessageText = chat.lastMessage?.text?.toLowerCase() || ''; return fullName.includes(query) || username.includes(query) || - rideInfo.includes(query); + rideInfo.includes(query) || + lastMessageText.includes(query); }); } - // Sort: pinned first, then by last message date + // Sort by last message date result.sort((a, b) => { + // Pinned chats first if (a.pinned && !b.pinned) return -1; if (!a.pinned && b.pinned) return 1; - const dateA = new Date(a.lastMessage?.createdAt || a.updatedAt).getTime(); - const dateB = new Date(b.lastMessage?.createdAt || b.updatedAt).getTime(); + const dateA = new Date(a.lastMessage?.timestamp || a.updatedAt).getTime(); + const dateB = new Date(b.lastMessage?.timestamp || b.updatedAt).getTime(); return dateB - dateA; }); @@ -97,7 +113,7 @@ export default defineComponent({ switch (activeTab.value) { case 'unread': return 'mark_email_read'; case 'rides': return 'no_transfer'; - case 'archived': return 'inventory_2'; + case 'archived': return 'unarchive'; default: return 'forum'; } }); @@ -117,21 +133,25 @@ export default defineComponent({ switch (activeTab.value) { case 'unread': return 'Non hai messaggi da leggere'; case 'rides': return 'Le chat relative ai viaggi appariranno qui'; - case 'archived': return 'Le chat archiviate appariranno qui'; + case 'archived': return 'Le conversazioni archiviate appariranno qui'; default: return 'Inizia a cercare viaggi per connetterti con altri utenti'; } }); // Methods const getOtherParticipant = (chat: Chat): User | undefined => { - return chat.participants?.find(p => p._id !== currentUserId.value); + if (!chat.participants || chat.participants.length === 0) return undefined; + return chat.participants.find(p => { + const pId = typeof p === 'string' ? p : p._id; + return pId !== currentUserId.value; + }) as User | undefined; }; const getInitials = (user?: User): string => { if (!user) return '?'; const name = user.name || ''; const surname = user.surname || ''; - return `${name.charAt(0)}${surname.charAt(0)}`.toUpperCase(); + return `${name.charAt(0)}${surname.charAt(0)}`.toUpperCase() || '?'; }; const isOnline = (userId?: string): boolean => { @@ -164,22 +184,37 @@ export default defineComponent({ } }; - const getMessagePreview = (message?: Message): string => { - if (!message) return 'Nessun messaggio'; + // ✅ Fixed: Riceve lastMessage invece di chat + const getMessagePreview = (lastMessage?: Message | null): string => { + if (!lastMessage) return 'Nessun messaggio'; - if (message.type === 'image') return '📷 Foto'; - if (message.type === 'location') return '📍 Posizione'; - if (message.type === 'ride_request') return '🚗 Richiesta passaggio'; - if (message.type === 'ride_accepted') return '✅ Passaggio accettato'; - if (message.type === 'ride_rejected') return '❌ Passaggio rifiutato'; + const msgType = lastMessage.type || 'text'; - return message.content || ''; + if (msgType === 'image') return '📷 Foto'; + if (msgType === 'location') return '📍 Posizione'; + if (msgType === 'ride_request') return '🚗 Richiesta passaggio'; + if (msgType === 'ride_accepted') return '✅ Passaggio accettato'; + if (msgType === 'ride_rejected') return '❌ Passaggio rifiutato'; + + return lastMessage.text || 'Messaggio'; }; - const getMessageStatusIcon = (message?: Message): string => { - if (!message) return ''; - if (message.read) return 'done_all'; - if (message.delivered) return 'done_all'; + // ✅ Fixed: Riceve lastMessage invece di chat + const getMessageStatusIcon = (lastMessage?: Message | null): string => { + if (!lastMessage) return ''; + + const senderId = typeof lastMessage.senderId === 'string' + ? lastMessage.senderId + : lastMessage.senderId?._id; + + if (senderId !== currentUserId.value) return ''; + + // Check read status + if (lastMessage.readBy && Array.isArray(lastMessage.readBy)) { + const allRead = lastMessage.readBy.length > 1; // > 1 perché include il mittente + return allRead ? 'done_all' : 'done'; + } + return 'done'; }; @@ -187,14 +222,36 @@ export default defineComponent({ router.push(`/trasporti/chat/${chat._id}`); }; + // ✅ Added: Mute chat + const onMuteChat = async (chat: Chat) => { + try { + const isMuted = chat.mutedBy?.includes(currentUserId.value as any) || false; + await toggleMuteChat(chat._id, !isMuted); + + $q.notify({ + type: 'info', + message: isMuted ? 'Notifiche attivate' : 'Notifiche silenziate', + icon: isMuted ? 'notifications' : 'notifications_off' + }); + + await fetchChats(); + } catch (error) { + $q.notify({ + type: 'negative', + message: 'Errore nell\'aggiornamento' + }); + } + }; + + // ✅ Added: Archive chat const onArchiveChat = async (chat: Chat) => { try { - await archiveChat(chat._id, !chat.archived); + // TODO: Implementa nel backend $q.notify({ type: 'positive', - message: chat.archived ? 'Chat ripristinata' : 'Chat archiviata', - icon: 'archive' + message: 'Conversazione archiviata' }); + await fetchChats(); } catch (error) { $q.notify({ type: 'negative', @@ -203,20 +260,28 @@ export default defineComponent({ } }; + // ✅ Fixed: Delete chat const onDeleteChat = async (chat: Chat) => { $q.dialog({ title: 'Elimina conversazione', - message: 'Sei sicuro di voler eliminare questa conversazione? L\'azione non è reversibile.', - cancel: true, + message: 'Sei sicuro di voler eliminare questa conversazione?', + cancel: { + label: 'Annulla', + flat: true + }, + ok: { + label: 'Elimina', + color: 'negative' + }, persistent: true }).onOk(async () => { try { await deleteChat(chat._id); $q.notify({ type: 'positive', - message: 'Conversazione eliminata', - icon: 'delete' + message: 'Conversazione eliminata' }); + await fetchChats(); } catch (error) { $q.notify({ type: 'negative', @@ -226,38 +291,45 @@ export default defineComponent({ }); }; - const loadMore = async () => { - loadingMore.value = true; - try { - page.value++; - const newChats = await loadChats({ page: page.value, limit: 20 }); - if (newChats.length < 20) { - hasMore.value = false; - } - } finally { - loadingMore.value = false; - } - }; - - const searchUsers = debounce(async (query: string) => { - if (!query || query.length < 2) { + // ✅ Added: Search users + const searchUsers = async () => { + if (!userSearchQuery.value || userSearchQuery.value.length < 2) { searchedUsers.value = []; return; } searchingUsers.value = true; try { - searchedUsers.value = await searchUsersApi(query); + const response = await Api.SendReq( + `/api/users/search?q=${encodeURIComponent(userSearchQuery.value)}`, + 'GET' + ); + + if (response.success && response.data) { + // Escludi utente corrente + searchedUsers.value = response.data.filter( + (u: User) => u._id !== currentUserId.value + ); + } + } catch (error) { + console.error('Error searching users:', error); + searchedUsers.value = []; } finally { searchingUsers.value = false; } - }, 300); + }; + // ✅ Added: Start chat with user const startChatWith = async (user: User) => { try { - const chat = await createChat([user._id]); + const chat = await getOrCreateDirectChat(user._id); showUserSearch.value = false; - router.push(`/trasporti/chat/${chat._id}`); + userSearchQuery.value = ''; + searchedUsers.value = []; + + if (chat) { + router.push(`/trasporti/chat/${chat._id}`); + } } catch (error) { $q.notify({ type: 'negative', @@ -266,9 +338,29 @@ export default defineComponent({ } }; + const loadMore = async () => { + if (!hasMore.value || loadingMore.value) return; + + loadingMore.value = true; + try { + page.value++; + const result = await fetchChats(page.value, 20); + + if (!result || result.data.length < 20) { + hasMore.value = false; + } + } finally { + loadingMore.value = false; + } + }; + + const startNewChat = () => { + showUserSearch.value = true; + }; + // Lifecycle - onMounted(() => { - loadChats({ page: 1, limit: 20 }); + onMounted(async () => { + await fetchChats(1, 20); }); return { @@ -300,9 +392,11 @@ export default defineComponent({ getMessagePreview, getMessageStatusIcon, openChat, + onMuteChat, onArchiveChat, onDeleteChat, loadMore, + startNewChat, searchUsers, startChatWith }; diff --git a/src/modules/trasporti/pages/ChatListPage.vue b/src/modules/trasporti/pages/ChatListPage.vue index 68a4c20b..49f37f8f 100644 --- a/src/modules/trasporti/pages/ChatListPage.vue +++ b/src/modules/trasporti/pages/ChatListPage.vue @@ -4,7 +4,7 @@
- +

Messaggi

Le tue conversazioni

@@ -17,12 +17,12 @@ placeholder="Cerca conversazione..." outlined dense - class="chat-list-page__search" + class="chat-list-page__search q-mt-md" > -