refine member message pagination
This commit is contained in:
parent
0531fc1dbf
commit
9296ce3988
2 changed files with 87 additions and 56 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue