Files
myprojplanet_vite/src/modules/trasporti/pages/ChatListPage.vue
2025-12-22 23:39:42 +01:00

378 lines
13 KiB
Vue

<!-- ChatListPage.vue -->
<template>
<q-page class="chat-list-page">
<!-- Header -->
<div class="chat-list-page__header">
<div class="chat-list-page__title-section">
<q-icon name="forum" class="chat-list-page__icon" size="32px" color="primary" />
<div>
<h1 class="chat-list-page__title">Messaggi</h1>
<p class="chat-list-page__subtitle">Le tue conversazioni</p>
</div>
</div>
<!-- Search -->
<q-input
v-model="searchQuery"
placeholder="Cerca conversazione..."
outlined
dense
class="chat-list-page__search q-mt-md"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
<template v-if="searchQuery" v-slot:append>
<q-icon name="close" class="cursor-pointer" @click="searchQuery = ''" />
</template>
</q-input>
</div>
<!-- Tabs -->
<q-tabs
v-model="activeTab"
class="chat-list-page__tabs"
active-color="primary"
indicator-color="primary"
align="justify"
dense
>
<q-tab name="all" label="Tutte" icon="inbox" />
<q-tab name="unread" icon="mark_email_unread">
<template v-slot:default>
<div class="chat-list-page__tab-content">
<span>Non lette</span>
<q-badge
v-if="unreadCount > 0"
color="negative"
:label="unreadCount > 99 ? '99+' : unreadCount"
class="q-ml-xs"
/>
</div>
</template>
</q-tab>
<q-tab name="rides" label="Viaggi" icon="directions_car" />
<q-tab name="archived" label="Archiviate" icon="archive" />
</q-tabs>
<!-- Content -->
<div class="chat-list-page__content">
<!-- Loading -->
<div v-if="loading && filteredChats.length === 0" class="chat-list-page__loading">
<q-spinner-dots size="50px" color="primary" />
<p class="text-grey q-mt-md">Caricamento conversazioni...</p>
</div>
<!-- Empty State -->
<div v-else-if="filteredChats.length === 0" class="chat-list-page__empty">
<q-icon :name="emptyStateIcon" size="80px" color="grey-4" />
<h3 class="q-mt-md q-mb-sm">{{ emptyStateTitle }}</h3>
<p class="text-grey">{{ emptyStateMessage }}</p>
<q-btn
v-if="activeTab === 'all' && !searchQuery"
color="primary"
icon="explore"
label="Esplora viaggi"
rounded
unelevated
class="q-mt-md"
to="/trasporti"
/>
</div>
<!-- Chat List -->
<q-list v-else class="chat-list-page__list" separator>
<q-slide-item
v-for="chat in filteredChats"
:key="chat._id"
class="chat-list-page__item"
:class="{ 'chat-list-page__item--unread': (chat.unreadCount || 0) > 0 }"
@left="(details) => { details.reset(); onArchiveChat(chat); }"
@right="(details) => { details.reset(); onDeleteChat(chat); }"
>
<!-- Left Slide Action: Archive -->
<template v-slot:left>
<div class="chat-list-page__slide-action chat-list-page__slide-action--archive">
<q-icon name="archive" size="24px" />
<span class="text-caption q-mt-xs">Archivia</span>
</div>
</template>
<!-- Right Slide Action: Delete -->
<template v-slot:right>
<div class="chat-list-page__slide-action chat-list-page__slide-action--delete">
<q-icon name="delete" size="24px" />
<span class="text-caption q-mt-xs">Elimina</span>
</div>
</template>
<!-- Chat Item -->
<q-item clickable @click="openChat(chat)">
<!-- Avatar -->
<q-item-section avatar>
<div class="chat-list-page__avatar-wrapper">
<q-avatar size="56px">
<img
v-if="getOtherParticipant(chat)?.profile?.img"
:src="getOtherParticipant(chat).profile.img"
:alt="getOtherParticipant(chat)?.name"
/>
<div v-else class="chat-list-page__avatar-placeholder">
{{ getInitials(getOtherParticipant(chat)) }}
</div>
</q-avatar>
<!-- Online indicator -->
<div
v-if="isOnline(getOtherParticipant(chat)?._id)"
class="chat-list-page__online-dot"
/>
<!-- Ride type badge -->
<q-badge
v-if="chat.rideId && typeof chat.rideId === 'object' && chat.rideId.type"
:color="chat.rideId.type === 'offer' ? 'positive' : 'warning'"
floating
rounded
class="chat-list-page__ride-badge"
>
<q-icon
:name="chat.rideId.type === 'offer' ? 'directions_car' : 'hail'"
size="12px"
/>
</q-badge>
</div>
</q-item-section>
<!-- Content -->
<q-item-section>
<!-- Name -->
<q-item-label class="chat-list-page__name">
<span class="text-weight-medium">
{{ getOtherParticipant(chat)?.name || 'Utente' }}
{{ getOtherParticipant(chat)?.surname || '' }}
</span>
<q-icon
v-if="chat.pinned"
name="push_pin"
size="16px"
color="grey"
class="q-ml-xs"
/>
</q-item-label>
<!-- Ride info -->
<q-item-label
v-if="chat.rideId && typeof chat.rideId === 'object'"
caption
class="chat-list-page__ride-info"
>
<q-icon name="place" size="14px" class="q-mr-xs" />
<template v-if="chat.rideId.departure && chat.rideId.destination">
{{ typeof chat.rideId.departure === 'string'
? chat.rideId.departure
: chat.rideId.departure.city }}
{{ typeof chat.rideId.destination === 'string'
? chat.rideId.destination
: chat.rideId.destination.city }}
</template>
</q-item-label>
<!-- Last message -->
<q-item-label
caption
lines="1"
class="chat-list-page__last-message"
:class="{
'chat-list-page__last-message--unread': (chat.unreadCount || 0) > 0,
'text-weight-medium': (chat.unreadCount || 0) > 0
}"
>
<q-icon
v-if="chat.lastMessage && chat.lastMessage.senderId === currentUserId"
:name="getMessageStatusIcon(chat.lastMessage)"
size="14px"
:color="chat.lastMessage.readBy && chat.lastMessage.readBy.length > 1 ? 'primary' : 'grey'"
class="q-mr-xs"
/>
{{ getMessagePreview(chat.lastMessage) }}
</q-item-label>
</q-item-section>
<!-- Right side: Time & Badge -->
<q-item-section side top>
<div class="chat-list-page__meta">
<!-- Time -->
<q-item-label caption class="chat-list-page__time">
{{ formatTime(chat.lastMessage?.timestamp || chat.updatedAt) }}
</q-item-label>
<!-- Unread badge -->
<q-badge
v-if="(chat.unreadCount || 0) > 0"
color="primary"
:label="chat.unreadCount > 99 ? '99+' : chat.unreadCount"
rounded
class="chat-list-page__unread-badge q-mt-xs"
/>
<!-- Muted icon -->
<q-icon
v-else-if="chat.mutedBy && chat.mutedBy.includes(currentUserId)"
name="notifications_off"
size="18px"
color="grey"
class="q-mt-xs"
/>
</div>
</q-item-section>
</q-item>
</q-slide-item>
</q-list>
<!-- Load More -->
<div v-if="hasMore && filteredChats.length > 0" class="chat-list-page__load-more">
<q-btn
flat
color="primary"
label="Carica altre conversazioni"
icon="expand_more"
:loading="loadingMore"
@click="loadMore"
/>
</div>
</div>
<!-- FAB for new message -->
<q-page-sticky position="bottom-right" :offset="[18, 18]">
<q-fab
icon="edit"
direction="up"
color="primary"
:disable="loading"
>
<q-fab-action
color="secondary"
icon="person_search"
label="Cerca utente"
external-label
label-position="left"
@click="showUserSearch = true"
/>
<q-fab-action
color="accent"
icon="group"
label="Nuovo gruppo"
external-label
label-position="left"
@click="showGroupCreate = true"
/>
</q-fab>
</q-page-sticky>
<!-- User Search Dialog -->
<q-dialog v-model="showUserSearch" position="top">
<q-card class="chat-list-page__search-dialog" style="width: 100%; max-width: 500px;">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Nuova conversazione</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<q-input
v-model="userSearchQuery"
placeholder="Cerca per nome o username..."
outlined
dense
autofocus
@update:model-value="searchUsers"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
<template v-if="userSearchQuery" v-slot:append>
<q-icon
name="close"
class="cursor-pointer"
@click="userSearchQuery = ''; searchedUsers = []"
/>
</template>
</q-input>
<!-- Searching spinner -->
<div v-if="searchingUsers" class="text-center q-pa-md">
<q-spinner color="primary" size="40px" />
</div>
<!-- Search results -->
<q-list v-else-if="searchedUsers.length > 0" class="q-mt-md">
<q-item
v-for="user in searchedUsers"
:key="user._id"
clickable
v-ripple
@click="startChatWith(user)"
>
<q-item-section avatar>
<q-avatar>
<img v-if="user.profile?.img" :src="user.profile.img" />
<div v-else class="chat-list-page__avatar-placeholder">
{{ getInitials(user) }}
</div>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label>{{ user.name }} {{ user.surname }}</q-item-label>
<q-item-label caption>@{{ user.username }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" color="grey" />
</q-item-section>
</q-item>
</q-list>
<!-- No results -->
<div
v-else-if="userSearchQuery && userSearchQuery.length >= 2 && !searchingUsers"
class="text-center q-pa-md text-grey"
>
<q-icon name="person_off" size="48px" color="grey-4" />
<p class="q-mt-md">Nessun utente trovato</p>
</div>
<!-- Hint -->
<div
v-else-if="!userSearchQuery || userSearchQuery.length < 2"
class="text-center q-pa-md text-grey"
>
<q-icon name="search" size="48px" color="grey-4" />
<p class="q-mt-md">Digita almeno 2 caratteri per cercare</p>
</div>
</q-card-section>
</q-card>
</q-dialog>
<!-- Group Create Dialog (placeholder) -->
<q-dialog v-model="showGroupCreate">
<q-card style="min-width: 350px;">
<q-card-section>
<div class="text-h6">Crea gruppo</div>
</q-card-section>
<q-card-section class="q-pt-none">
<p class="text-grey">Funzionalità in arrivo...</p>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Chiudi" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
</q-page>
</template>
<script lang="ts" src="./ChatListPage.ts" />
<style lang="scss" src="./ChatListPage.scss" />