Track per-employee chat read state and surface real unread counts
Adds chat_read_state migration (employee_id + space_id, last read message + timestamp) wired through ChatService.markSpaceRead, IPC markChatSpaceRead, and an effect in ChatPage that flushes the active space whenever the latest message id changes. Shell.computeBadges now derives the Messages badge from (message.createdAt > read_state.last_read_at) minus messages the viewer sent, so the sidebar shows true unread instead of total throughput.
This commit is contained in:
1 parent
b44e0e3657
commit
1f78ae6f2e
11 files changed
+100
-3
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useRef, useState, type FormEvent } from "react";
|
||||
import { useEffect, useMemo, useRef, useState, type FormEvent } from "react";
|
||||
import { Field } from "../shared/ui";
|
||||
import { Dialog } from "../shared/Dialog";
|
||||
import { ChatSidebar } from "./chat/ChatSidebar";
|
||||
@@ -46,6 +46,22 @@ export function ChatPage({ snapshot, actions }: PageProps) {
|
||||
? selectedSpaceId
|
||||
: fallbackSpaceId;
|
||||
const activeSpace = spaces.find((space) => space.id === activeSpaceId) ?? null;
|
||||
const latestActiveMessageId = useMemo(() => {
|
||||
if (!activeSpace) return null;
|
||||
const inSpace = messagesAll.filter((message) => message.spaceId === activeSpace.id);
|
||||
if (inSpace.length === 0) return null;
|
||||
return inSpace[inSpace.length - 1].id;
|
||||
}, [messagesAll, activeSpace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeSpace || !latestActiveMessageId) return;
|
||||
void actions.run(() => window.agentCompany.markChatSpaceRead({
|
||||
orgId,
|
||||
employeeId: employee.id,
|
||||
spaceId: activeSpace.id,
|
||||
}));
|
||||
}, [activeSpace, latestActiveMessageId, orgId, employee.id, actions]);
|
||||
|
||||
const messagesInSpace = useMemo(
|
||||
() => (activeSpace ? messagesAll.filter((m) => m.spaceId === activeSpace.id) : []),
|
||||
[messagesAll, activeSpace],
|
||||
|
||||
@@ -136,10 +136,29 @@ function renderBadge(value: number | undefined): ReactNode {
|
||||
}
|
||||
|
||||
function computeBadges(snapshot: AppSnapshot, orgId: string): Partial<Record<RouteId, number>> {
|
||||
const employee = snapshot.employees.find((item) => item.orgId === orgId);
|
||||
const tasks = snapshot.tasks.filter((item) => item.orgId === orgId);
|
||||
const approvals = snapshot.approvals.filter((item) => item.orgId === orgId);
|
||||
return {
|
||||
work: tasks.filter((task) => task.status !== "done").length,
|
||||
chat: employee ? countUnreadMessages(snapshot, orgId, employee.id) : 0,
|
||||
approvals: approvals.filter((approval) => approval.state === "pending").length,
|
||||
};
|
||||
}
|
||||
|
||||
function countUnreadMessages(snapshot: AppSnapshot, orgId: string, employeeId: string): number {
|
||||
const readByspace = new Map<string, string>();
|
||||
for (const state of snapshot.chatReadStates) {
|
||||
if (state.orgId !== orgId || state.employeeId !== employeeId) continue;
|
||||
readByspace.set(state.spaceId, state.lastReadAt);
|
||||
}
|
||||
let unread = 0;
|
||||
for (const message of snapshot.chatMessages) {
|
||||
if (message.orgId !== orgId) continue;
|
||||
if (message.senderType === "employee" && message.senderId === employeeId) continue;
|
||||
const lastRead = readByspace.get(message.spaceId);
|
||||
if (lastRead && message.createdAt <= lastRead) continue;
|
||||
unread += 1;
|
||||
}
|
||||
return unread;
|
||||
}
|
||||
Reference in new issue
Block a user