// ChatListPage.ts 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'; import { Api } from '@api'; import type { Chat, User, Message } from '../types'; export default defineComponent({ name: 'ChatListPage', setup() { const router = useRouter(); const $q = useQuasar(); const { user: currentUser } = useAuth(); const { chats, loading, fetchChats, getOrCreateDirectChat, toggleMuteChat, deleteChat, onlineUsers, totalUnreadCount } = useChat(); // State const searchQuery = ref(''); const activeTab = ref('all'); const loadingMore = ref(false); const hasMore = ref(true); const page = ref(1); // βœ… User search const showUserSearch = 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(() => totalUnreadCount.value); const filteredChats = computed(() => { let result = [...chats.value]; // Filter by tab switch (activeTab.value) { case 'unread': result = result.filter(chat => (chat.unreadCount || 0) > 0); break; case 'rides': result = result.filter(chat => chat.rideId); break; case 'archived': result = result.filter(chat => chat.archived); break; default: result = result.filter(chat => !chat.archived); } // Filter by search if (searchQuery.value) { const query = searchQuery.value.toLowerCase(); result = result.filter(chat => { const otherUser = getOtherParticipant(chat); const fullName = `${otherUser?.name || ''} ${otherUser?.surname || ''}`.toLowerCase(); const username = otherUser?.username?.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) || lastMessageText.includes(query); }); } // 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?.timestamp || a.updatedAt).getTime(); const dateB = new Date(b.lastMessage?.timestamp || b.updatedAt).getTime(); return dateB - dateA; }); return result; }); const emptyStateIcon = computed(() => { if (searchQuery.value) return 'search_off'; switch (activeTab.value) { case 'unread': return 'mark_email_read'; case 'rides': return 'no_transfer'; case 'archived': return 'unarchive'; default: return 'forum'; } }); const emptyStateTitle = computed(() => { if (searchQuery.value) return 'Nessun risultato'; switch (activeTab.value) { case 'unread': return 'Tutto letto!'; case 'rides': return 'Nessuna chat viaggio'; case 'archived': return 'Nessuna chat archiviata'; default: return 'Nessuna conversazione'; } }); const emptyStateMessage = computed(() => { if (searchQuery.value) return 'Prova con altri termini di ricerca'; 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 conversazioni archiviate appariranno qui'; default: return 'Inizia a cercare viaggi per connetterti con altri utenti'; } }); // Methods const getOtherParticipant = (chat: Chat): User | undefined => { 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() || '?'; }; const isOnline = (userId?: string): boolean => { if (!userId) return false; return onlineUsers.value.includes(userId); }; const formatTime = (date?: string | Date): string => { if (!date) return ''; const messageDate = new Date(date); const now = new Date(); const diff = now.getTime() - messageDate.getTime(); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); if (days === 0) { return messageDate.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); } else if (days === 1) { return 'Ieri'; } else if (days < 7) { return messageDate.toLocaleDateString('it-IT', { weekday: 'short' }); } else { return messageDate.toLocaleDateString('it-IT', { day: '2-digit', month: '2-digit' }); } }; // βœ… Fixed: Riceve lastMessage invece di chat const getMessagePreview = (lastMessage?: Message | null): string => { if (!lastMessage) return 'Nessun messaggio'; const msgType = lastMessage.type || 'text'; 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'; }; // βœ… 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'; }; const openChat = (chat: Chat) => { 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 { // TODO: Implementa nel backend $q.notify({ type: 'positive', message: 'Conversazione archiviata' }); await fetchChats(); } catch (error) { $q.notify({ type: 'negative', message: 'Errore durante l\'archiviazione' }); } }; // βœ… Fixed: Delete chat const onDeleteChat = async (chat: Chat) => { $q.dialog({ title: 'Elimina conversazione', 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' }); await fetchChats(); } catch (error) { $q.notify({ type: 'negative', message: 'Errore durante l\'eliminazione' }); } }); }; // βœ… Added: Search users const searchUsers = async () => { if (!userSearchQuery.value || userSearchQuery.value.length < 2) { searchedUsers.value = []; return; } searchingUsers.value = true; try { 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; } }; // βœ… Added: Start chat with user const startChatWith = async (user: User) => { try { const chat = await getOrCreateDirectChat(user._id); showUserSearch.value = false; userSearchQuery.value = ''; searchedUsers.value = []; if (chat) { router.push(`/trasporti/chat/${chat._id}`); } } catch (error) { $q.notify({ type: 'negative', message: 'Errore nella creazione della chat' }); } }; 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(async () => { await fetchChats(1, 20); }); return { // State searchQuery, activeTab, loading, loadingMore, hasMore, showUserSearch, showGroupCreate, userSearchQuery, searchedUsers, searchingUsers, // Computed currentUserId, unreadCount, filteredChats, emptyStateIcon, emptyStateTitle, emptyStateMessage, // Methods getOtherParticipant, getInitials, isOnline, formatTime, getMessagePreview, getMessageStatusIcon, openChat, onMuteChat, onArchiveChat, onDeleteChat, loadMore, startNewChat, searchUsers, startChatWith }; } });