Files
myprojplanet_vite/src/modules/viaggi/pages/ChatPage.ts
Surya Paolo 11c17bdd8e - Parte 3 : Viaggi
- Chat
2025-12-24 00:26:29 +01:00

510 lines
13 KiB
TypeScript

// ChatPage.ts
import {
defineComponent,
ref,
computed,
onMounted,
onUnmounted,
nextTick,
watch,
} from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import { useChat } from '../composables/useChat';
import { useRealtimeChat } from '../composables/useRealtimeChat';
import { useAuth } from '../composables/useAuth';
import MessageBubble from '../components/chat/MessageBubble.vue';
import type { Message, RideInfo } from '../types/viaggi.types';
import { debounce } from 'quasar';
interface MessageGroup {
date: string;
messages: Message[];
}
export default defineComponent({
name: 'ChatPage',
components: {
MessageBubble,
},
setup() {
const route = useRoute();
const router = useRouter();
const $q = useQuasar();
const { user: currentUser } = useAuth();
const {
currentChat,
messages,
loading,
loadChat,
loadMessages,
sendMessage: sendMessageApi,
markAsRead,
deleteMessage: deleteMsg,
onlineUsers,
typingUsers,
deleteChat,
fetchChats,
toggleMuteChat,
startPolling, // AGGIUNGI
stopPolling, // AGGIUNGI
} = useChat();
const { subscribeToChat, unsubscribeFromChat, sendTyping } = useRealtimeChat();
// Refs
const messagesContainer = ref<HTMLElement>();
const messageInput = ref();
// State
const messageText = ref('');
const sending = ref(false);
const loadingMore = ref(false);
const hasMoreMessages = ref(true);
const showScrollButton = ref(false);
const newMessagesCount = ref(0);
const replyTo = ref<Message | null>(null);
const showEmoji = ref(false);
const showAttachMenu = ref(false);
const showUserProfile = ref(false);
const searchInChat = ref(false);
const isMuted = ref(false);
const lastSeen = ref<Date | null>(null);
const commonEmojis = [
'😊',
'😂',
'❤️',
'👍',
'🙏',
'😍',
'🎉',
'🚗',
'📍',
'✅',
'❌',
'⏰',
];
// Computed
const chatId = computed(() => route.params.id as string);
const currentUserId = computed(() => currentUser.value?._id);
const otherUser = computed((): any | undefined => {
if (!currentChat.value?.participants) return undefined;
return currentChat.value.participants.find((p) => p._id !== currentUserId.value);
});
const rideInfo = computed((): RideInfo | undefined => {
return currentChat.value?.rideInfo;
});
const isOnline = computed(() => {
return otherUser.value ? onlineUsers.value.includes(otherUser.value._id) : false;
});
const isTyping = computed(() => {
return otherUser.value ? typingUsers.value.includes(otherUser.value._id) : false;
});
const groupedMessages = computed((): MessageGroup[] => {
const groups: MessageGroup[] = [];
let currentDate = '';
messages.value.forEach((message) => {
const messageDate = formatDateHeader(new Date(message.createdAt));
if (messageDate !== currentDate) {
currentDate = messageDate;
groups.push({
date: messageDate,
messages: [message],
});
} else {
groups[groups.length - 1].messages.push(message);
}
});
return groups;
});
// Methods
const goBack = () => {
router.back();
};
const getInitials = (user?: User): string => {
if (!user) return '?';
return `${user.name?.charAt(0) || ''}${user.surname?.charAt(0) || ''}`.toUpperCase();
};
const formatDateHeader = (date: Date): string => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === today.toDateString()) {
return 'Oggi';
} else if (date.toDateString() === yesterday.toDateString()) {
return 'Ieri';
} else {
return date.toLocaleDateString('it-IT', {
weekday: 'long',
day: 'numeric',
month: 'long',
});
}
};
const formatLastSeen = (date: Date): string => {
const now = new Date();
const diff = now.getTime() - date.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'adesso';
if (minutes < 60) return `${minutes} min fa`;
if (hours < 24) return `${hours} ore fa`;
if (days === 1) return 'ieri';
return `${days} giorni fa`;
};
const formatRideDate = (date: string): string => {
return new Date(date).toLocaleDateString('it-IT', {
weekday: 'short',
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
});
};
const shouldShowAvatar = (message: Message, allMessages: Message[]): boolean => {
const index = allMessages.indexOf(message);
if (index === allMessages.length - 1) return true;
return allMessages[index + 1].senderId !== message.senderId;
};
const scrollToBottom = (smooth = true) => {
nextTick(() => {
if (messagesContainer.value) {
messagesContainer.value.scrollTo({
top: messagesContainer.value.scrollHeight,
behavior: smooth ? 'smooth' : 'auto',
});
newMessagesCount.value = 0;
}
});
};
const onScroll = () => {
if (!messagesContainer.value) return;
const { scrollTop, scrollHeight, clientHeight } = messagesContainer.value;
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
showScrollButton.value = distanceFromBottom > 200;
// Load more when scrolled to top
if (scrollTop < 100 && hasMoreMessages.value && !loadingMore.value) {
loadMoreMessages();
}
};
const loadMoreMessages = async () => {
if (loadingMore.value || !hasMoreMessages.value) return;
loadingMore.value = true;
const oldHeight = messagesContainer.value?.scrollHeight || 0;
try {
const olderMessages = await loadMessages(chatId.value, {
before: messages.value[0]?.createdAt,
limit: 30,
});
if (olderMessages.length < 30) {
hasMoreMessages.value = false;
}
// Maintain scroll position
nextTick(() => {
if (messagesContainer.value) {
const newHeight = messagesContainer.value.scrollHeight;
messagesContainer.value.scrollTop = newHeight - oldHeight;
}
});
} finally {
loadingMore.value = false;
}
};
const sendMessage = async () => {
const content = messageText.value.trim();
if (!content || sending.value) return;
sending.value = true;
const replyToId = replyTo.value?._id;
try {
await sendMessageApi(chatId.value, {
content,
type: 'text',
replyTo: replyToId,
});
messageText.value = '';
replyTo.value = null;
scrollToBottom();
} catch (error) {
$q.notify({
type: 'negative',
message: "Errore nell'invio del messaggio",
});
} finally {
sending.value = false;
}
};
const onTyping = debounce(() => {
sendTyping(chatId.value);
}, 500);
const addEmoji = (emoji: string) => {
messageText.value += emoji;
showEmoji.value = false;
messageInput.value?.focus();
};
const onReact = async (data: { messageId: string; emoji: string }) => {
// TODO: Implementa reazione
console.log('React:', data);
};
const onDeleteMessage = async (messageId: string) => {
$q.dialog({
title: 'Elimina messaggio',
message: 'Eliminare questo messaggio?',
cancel: true,
}).onOk(async () => {
try {
await deleteMsg(chatId.value, messageId);
} catch (error) {
$q.notify({
type: 'negative',
message: "Errore nell'eliminazione",
});
}
});
};
const callUser = () => {
if (otherUser.value?.profile?.cell) {
window.open(`tel:${otherUser.value.profile.cell}`);
} else {
$q.notify({
type: 'warning',
message: 'Numero di telefono non disponibile',
});
}
};
const viewRide = () => {
if (rideInfo.value?.rideId) {
router.push(`/viaggi/ride/${rideInfo.value.rideId}`);
}
};
const viewDriverProfile = () => {
if (otherUser.value) {
router.push(`/viaggi/profilo/${otherUser.value._id}`);
}
};
const toggleMute = async () => {
try {
await toggleMuteChat(chatId.value, !isMuted.value);
isMuted.value = !isMuted.value;
$q.notify({
type: 'info',
message: isMuted.value ? 'Notifiche silenziate' : 'Notifiche attivate',
icon: isMuted.value ? 'notifications_off' : 'notifications',
});
} catch (error) {
$q.notify({
type: 'negative',
message: 'Errore',
});
}
};
const deleteConversation = async () => {
$q.dialog({
title: 'Elimina conversazione',
message: 'Sei sicuro? Questa azione non è reversibile.',
cancel: true,
persistent: true,
}).onOk(async () => {
try {
await deleteChat(currentChat.value._id);
$q.notify({
type: 'positive',
message: 'Conversazione eliminata',
});
await fetchChats();
router.push('/viaggi/chat')
} catch (error) {
$q.notify({
type: 'negative',
message: "Errore nell'eliminazione",
});
}
});
};
const blockUser = () => {
$q.dialog({
title: 'Blocca utente',
message: `Bloccare ${otherUser.value?.name}? Non potrete più scambiarvi messaggi.`,
cancel: true,
}).onOk(() => {
// TODO: Implementa blocco
showUserProfile.value = false;
router.push('/viaggi/chat');
});
};
const attachImage = () => {
showAttachMenu.value = false;
// TODO: Implementa upload immagine
};
const attachDocument = () => {
showAttachMenu.value = false;
// TODO: Implementa upload documento
};
const shareLocation = () => {
showAttachMenu.value = false;
// TODO: Implementa condivisione posizione
};
const sendRideRequest = () => {
showAttachMenu.value = false;
// TODO: Implementa richiesta passaggio
};
const startVoiceMessage = () => {
// TODO: Implementa messaggio vocale
};
// Watch for new messages
watch(
() => messages.value.length,
(newLen, oldLen) => {
if (newLen > oldLen) {
const lastMessage = messages.value[messages.value.length - 1];
if (lastMessage.senderId === currentUserId.value) {
scrollToBottom();
} else if (showScrollButton.value) {
newMessagesCount.value++;
} else {
scrollToBottom();
markAsRead(chatId.value);
}
}
}
);
// Lifecycle
onMounted(async () => {
await loadChat(chatId.value);
await loadMessages(chatId.value, { limit: 50 });
scrollToBottom(false);
markAsRead(chatId.value);
// AVVIA POLLING
startPolling(chatId.value);
// subscribeToChat(chatId.value);
});
// Aggiungi questa funzione nel setup()
const getIsOwn = (message: Message): boolean => {
const senderId =
typeof message.senderId === 'object'
? (message.senderId as any)._id
: message.senderId;
return senderId === currentUserId.value;
};
onUnmounted(() => {
stopPolling();
//unsubscribeFromChat(chatId.value);
});
return {
// Refs
messagesContainer,
messageInput,
// State
messageText,
sending,
loadingMore,
hasMoreMessages,
showScrollButton,
newMessagesCount,
replyTo,
showEmoji,
showAttachMenu,
showUserProfile,
searchInChat,
isMuted,
lastSeen,
commonEmojis,
// Computed
currentUserId,
currentUser,
otherUser,
rideInfo,
isOnline,
isTyping,
groupedMessages,
// Methods
goBack,
getInitials,
formatLastSeen,
formatRideDate,
shouldShowAvatar,
scrollToBottom,
onScroll,
loadMoreMessages,
sendMessage,
onTyping,
addEmoji,
onReact,
onDeleteMessage,
callUser,
viewRide,
viewDriverProfile,
toggleMute,
deleteConversation,
blockUser,
attachImage,
attachDocument,
shareLocation,
sendRideRequest,
startVoiceMessage,
getIsOwn,
};
},
});