refine member message pagination

This commit is contained in:
iliya 2026-04-05 22:07:09 +03:00
parent 0531fc1dbf
commit 9296ce3988
2 changed files with 87 additions and 56 deletions

View file

@ -1,11 +1,8 @@
import { useEffect, useMemo, useState } from 'react';
import { api } from '@renderer/api';
import { useMemo, useState } from 'react';
import { Button } from '@renderer/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import { useMemberStats } from '@renderer/hooks/useMemberStats';
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection';
import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react';
@ -42,8 +39,6 @@ interface MemberDetailDialogProps {
onViewMemberChanges?: (memberName: string, filePath?: string) => void;
}
const MEMBER_MESSAGES_PAGE_SIZE = 200;
export const MemberDetailDialog = ({
open,
member,
@ -71,52 +66,7 @@ export const MemberDetailDialog = ({
() => (member ? messages.filter((m) => m.from === member.name || m.to === member.name) : []),
[messages, member]
);
const [pagedMemberMessages, setPagedMemberMessages] = useState<InboxMessage[] | null>(null);
useEffect(() => {
if (!open || !member) {
setPagedMemberMessages(null);
return;
}
let cancelled = false;
setPagedMemberMessages(null);
void (async () => {
let cursor: string | undefined;
let hasMore = true;
let allMessages: InboxMessage[] = [];
while (!cancelled && hasMore) {
const page = await api.teams.getMessagesPage(teamName, {
beforeTimestamp: cursor,
limit: MEMBER_MESSAGES_PAGE_SIZE,
});
allMessages = mergeTeamMessages(allMessages, page.messages);
hasMore = page.hasMore && page.nextCursor != null;
cursor = page.nextCursor ?? undefined;
}
if (cancelled) return;
setPagedMemberMessages(
allMessages.filter((message) => message.from === member.name || message.to === member.name)
);
})().catch(() => {
if (!cancelled) {
setPagedMemberMessages([]);
}
});
return () => {
cancelled = true;
};
}, [open, teamName, member?.name]);
const memberMessages = useMemo(
() => mergeTeamMessages(seedMemberMessages, pagedMemberMessages ?? []),
[seedMemberMessages, pagedMemberMessages]
);
const memberMessages = seedMemberMessages;
const inProgressTasks = useMemo(
() => memberTasks.filter((t) => t.status === 'in_progress').length,
@ -204,7 +154,11 @@ export const MemberDetailDialog = ({
<MemberTasksTab tasks={memberTasks} onTaskClick={onTaskClick} />
</TabsContent>
<TabsContent value="messages">
<MemberMessagesTab messages={memberMessages} teamName={teamName} />
<MemberMessagesTab
messages={memberMessages}
teamName={teamName}
memberName={member.name}
/>
</TabsContent>
<TabsContent value="stats">
<MemberStatsTab

View file

@ -1,5 +1,8 @@
import { useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '@renderer/api';
import { Button } from '@renderer/components/ui/button';
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
import { ActivityItem } from '../activity/ActivityItem';
@ -9,24 +12,91 @@ import type { InboxMessage } from '@shared/types';
interface MemberMessagesTabProps {
messages: InboxMessage[];
teamName: string;
memberName: string;
onCreateTask?: (subject: string, description: string) => void;
}
const MAX_MESSAGES = 100;
const MEMBER_MESSAGES_PAGE_SIZE = 50;
export const MemberMessagesTab = ({
messages,
teamName,
memberName,
onCreateTask,
}: MemberMessagesTabProps): React.JSX.Element => {
const [pagedMessages, setPagedMessages] = useState<InboxMessage[]>([]);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
let cancelled = false;
setPagedMessages([]);
setNextCursor(null);
setHasMore(false);
setLoading(true);
void (async () => {
try {
const page = await api.teams.getMessagesPage(teamName, { limit: MEMBER_MESSAGES_PAGE_SIZE });
if (cancelled) return;
const memberPageMessages = page.messages.filter(
(message) => message.from === memberName || message.to === memberName
);
setPagedMessages(memberPageMessages);
setNextCursor(page.nextCursor);
setHasMore(page.hasMore);
} catch {
if (!cancelled) {
setPagedMessages([]);
setNextCursor(null);
setHasMore(false);
}
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [teamName, memberName]);
const loadOlderMessages = useCallback(async () => {
if (!nextCursor || loading) return;
setLoading(true);
try {
const page = await api.teams.getMessagesPage(teamName, {
beforeTimestamp: nextCursor,
limit: MEMBER_MESSAGES_PAGE_SIZE,
});
const memberPageMessages = page.messages.filter(
(message) => message.from === memberName || message.to === memberName
);
setPagedMessages((prev) => mergeTeamMessages(prev, memberPageMessages));
setNextCursor(page.nextCursor);
setHasMore(page.hasMore);
} catch {
// best-effort
} finally {
setLoading(false);
}
}, [teamName, memberName, nextCursor, loading]);
const effectiveMessages = useMemo(
() => mergeTeamMessages(messages, pagedMessages),
[messages, pagedMessages]
);
const displayMessages = useMemo(
() =>
filterTeamMessages(messages, {
filterTeamMessages(effectiveMessages, {
timeWindow: null,
filter: { from: new Set(), to: new Set(), showNoise: true },
searchQuery: '',
}).slice(0, MAX_MESSAGES),
[messages]
[effectiveMessages]
);
if (displayMessages.length === 0) {
@ -47,6 +117,13 @@ export const MemberMessagesTab = ({
onCreateTask={onCreateTask}
/>
))}
{hasMore && (
<div className="flex justify-center pt-2">
<Button variant="ghost" size="sm" className="text-xs" disabled={loading} onClick={() => void loadOlderMessages()}>
{loading ? 'Loading...' : 'Load older messages'}
</Button>
</div>
)}
</div>
);
};