Updating ConversationsView wiring and the nav badge next.

This commit is contained in:
2026-09-29 16:22:37 +03:30
parent 032eb70524
commit d4972891f1
3 changed files with 374 additions and 64 deletions
+113 -7
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import axios from 'axios';
import { api } from '@/lib/api';
import type {
@@ -8,7 +8,12 @@ import type {
ConversationRoomDto,
} from '@mento/shared';
import { formatClockFa, toFaDigits } from '@/lib/jalali';
import { useOrgChat } from '@/lib/useOrgChat';
import {
onRoomLiveEvent,
useOrgChat,
type ChatDelivery,
type RoomLiveEvent,
} from '@/lib/useOrgChat';
import UiIcon from '@/components/UiIcon.vue';
import UserAvatar from '@/components/UserAvatar.vue';
@@ -24,9 +29,22 @@ const showNewGroup = ref(false);
const groupName = ref('');
const groupMemberIds = ref<string[]>([]);
const dmPeerId = ref('');
const chatUserId = ref('');
const { messages, loadingMessages, status, loadMessages, sendText, sendRead } = useOrgChat();
const {
messages,
loadingMessages,
status,
loadMessages,
sendText,
sendRead,
ensureConnected,
setConversationsPageOpen,
refreshUnreadTotal,
clearActiveRoom,
} = useOrgChat();
const msgStreamEl = ref<HTMLElement | null>(null);
let stopRoomLive: (() => boolean) | undefined;
const canCreateGroup = computed(() => access.value?.canCreateGroup ?? false);
@@ -45,8 +63,6 @@ const sortedRooms = computed(() =>
}),
);
const chatUserId = ref('');
function roomTitle(room: ConversationRoomDto) {
if (room.kind === 'group') return room.name;
const peer = peers.value.find((p) => p.id === room.peerUserId);
@@ -62,6 +78,44 @@ function formatListTime(iso: string | null) {
}
}
function deliveryLabel(delivery?: ChatDelivery) {
if (delivery === 'read') return '✓✓';
if (delivery === 'sent') return '✓';
return '…';
}
function applyRoomLive(event: RoomLiveEvent) {
const idx = rooms.value.findIndex((r) => r.externalRoomId === event.roomId);
if (idx < 0) {
void refreshRoomsSoft();
return;
}
const room = rooms.value[idx];
const isActive = activeRoom.value?.externalRoomId === event.roomId;
const next: ConversationRoomDto = {
...room,
lastMessagePreview: event.preview,
lastMessageAt: event.at,
unreadCount: event.fromSelf || isActive ? 0 : (room.unreadCount || 0) + 1,
};
const nextRooms = [...rooms.value];
nextRooms[idx] = next;
rooms.value = nextRooms;
if (isActive && activeRoom.value) {
activeRoom.value = next;
}
}
async function refreshRoomsSoft() {
try {
const { data } = await api.get<ConversationRoomDto[]>('/conversations/rooms');
rooms.value = data;
await refreshUnreadTotal();
} catch {
// keep previous
}
}
async function bootstrap() {
loading.value = true;
error.value = '';
@@ -87,6 +141,7 @@ async function bootstrap() {
}
if (roomsOutcome.status === 'fulfilled') {
rooms.value = roomsOutcome.value.data;
await refreshUnreadTotal();
} else {
const e = roomsOutcome.reason;
if (axios.isAxiosError(e)) {
@@ -101,6 +156,8 @@ async function bootstrap() {
error.value = 'بارگذاری لیست گفتگو ناموفق بود.';
}
}
await ensureConnected();
} catch (e) {
if (axios.isAxiosError(e) && e.response?.status === 403) {
error.value = 'ماژول گفتگو برای این محیط فعال نیست.';
@@ -118,9 +175,14 @@ async function bootstrap() {
}
async function openRoom(room: ConversationRoomDto) {
activeRoom.value = room;
const cleared = { ...room, unreadCount: 0 };
activeRoom.value = cleared;
rooms.value = rooms.value.map((r) =>
r.externalRoomId === room.externalRoomId ? cleared : r,
);
await loadMessages(room.externalRoomId);
sendRead(room.externalRoomId);
void refreshUnreadTotal();
}
async function submitMessage() {
@@ -193,8 +255,16 @@ watch(loadingMessages, (v) => {
});
onMounted(() => {
setConversationsPageOpen(true);
stopRoomLive = onRoomLiveEvent(applyRoomLive);
void bootstrap();
});
onUnmounted(() => {
setConversationsPageOpen(false);
stopRoomLive?.();
clearActiveRoom();
});
</script>
<template>
@@ -274,7 +344,21 @@ onMounted(() => {
>
<div class="bubble">
<p>{{ msg.content }}</p>
<time>{{ formatListTime(msg.createdAt) }}</time>
<div class="bubble-meta">
<time>{{ formatListTime(msg.createdAt) }}</time>
<span
v-if="msg.senderId === chatUserId || msg.optimistic"
class="ticks"
:class="msg.delivery"
:title="
msg.delivery === 'read'
? 'خوانده شد'
: msg.delivery === 'sent'
? 'ارسال شد'
: 'در حال ارسال'
"
>{{ deliveryLabel(msg.delivery) }}</span>
</div>
</div>
</li>
</ul>
@@ -572,10 +656,32 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
.bubble-meta {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.35rem;
}
.bubble time {
font-size: 0.68rem;
color: var(--muted);
}
.ticks {
font-size: 0.72rem;
letter-spacing: -0.08em;
line-height: 1;
color: #94a3b8;
user-select: none;
}
.ticks.sending {
color: #cbd5e1;
}
.ticks.sent {
color: #64748b;
}
.ticks.read {
color: #0d9488;
}
.composer {
display: flex;
gap: 0.5rem;