diff --git a/frontend/bun.lock b/frontend/bun.lock
index c4ba6fb..3819a1a 100644
--- a/frontend/bun.lock
+++ b/frontend/bun.lock
@@ -12,7 +12,7 @@
},
"devDependencies": {
"@originjs/vite-plugin-federation": "^1.3.6",
- "@paca-ai/plugin-sdk-react": "^0.3.0",
+ "@paca-ai/plugin-sdk-react": "^0.3.3",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^4.3.0",
@@ -124,7 +124,7 @@
"@originjs/vite-plugin-federation": ["@originjs/vite-plugin-federation@1.4.1", "", { "dependencies": { "estree-walker": "^3.0.2", "magic-string": "^0.27.0" } }, "sha512-Uo08jW5pj1t58OUKuZNkmzcfTN2pqeVuAWCCiKf/75/oll4Efq4cHOqSE1FXMlvwZNGDziNdDyBbQ5IANem3CQ=="],
- "@paca-ai/plugin-sdk-react": ["@paca-ai/plugin-sdk-react@0.3.0", "", { "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-vSGwfDXWQ3NbPnwpvxa0JacO8XXsUYsji5gIHGTxMWkYA5KmW4Qnfzx6oKs2sLuVZRGdSXJ07cyCgNDXnXHjcA=="],
+ "@paca-ai/plugin-sdk-react": ["@paca-ai/plugin-sdk-react@0.3.3", "", { "peerDependencies": { "@tanstack/react-query": ">=5.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-YpXq7IbD9Ke11FZLmBMfldsRZ+alzckEMCb+moh8si9KvjX5rLJgiNIpQNWJ90TcpokpT/E4/eVCYy3ViV9zFA=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
diff --git a/frontend/package.json b/frontend/package.json
index 22cbdf2..fe29054 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,7 +17,7 @@
},
"devDependencies": {
"@originjs/vite-plugin-federation": "^1.3.6",
- "@paca-ai/plugin-sdk-react": "^0.3.0",
+ "@paca-ai/plugin-sdk-react": "^0.3.3",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"typescript": "^5.8.3",
diff --git a/frontend/src/TimeTrackingPage.tsx b/frontend/src/TimeTrackingPage.tsx
index 9eda8b8..741e9a6 100644
--- a/frontend/src/TimeTrackingPage.tsx
+++ b/frontend/src/TimeTrackingPage.tsx
@@ -222,18 +222,21 @@ function TimeTrackingPageInner({ scope }: TimeTrackingPageProps) {
// embedded by the backend (see TimeLogWithContext) — there's no single
// project to resolve these against generically the way project scope does.
- const { data: members = [] } = useQuery({
- queryKey: ["plugin", PLUGIN_ID, "core", "members", projectId ?? ""],
- queryFn: () => api.listMembers(),
+ // The member filter's options are a server-side search over the team (first
+ // page of matches), not the whole team.
+ const [memberSearch, setMemberSearch] = useState("");
+ const { data: memberOptionsPage } = useQuery({
+ queryKey: ["plugin", PLUGIN_ID, "core", "members", projectId ?? "", memberSearch],
+ queryFn: () => api.listMembers({ search: memberSearch, pageSize: 50 }),
enabled: isProject,
+ placeholderData: keepPreviousData,
});
+ const memberOptions = memberOptionsPage?.items ?? [];
const { data: tasks = [] } = useQuery({
queryKey: ["plugin", PLUGIN_ID, "core", "tasks", projectId ?? ""],
queryFn: () => api.listTasks(),
enabled: isProject,
});
- const memberName = (memberId: string) =>
- members.find((m) => m.id === memberId)?.full_name ?? memberId;
const taskTitle = (taskId: string) =>
tasks.find((t) => t.id === taskId)?.title ?? taskId;
@@ -274,6 +277,26 @@ function TimeTrackingPageInner({ scope }: TimeTrackingPageProps) {
placeholderData: keepPreviousData,
});
+ // Names for the members that appear on this page of logs, looked up by id.
+ const logMemberIds = useMemo(
+ () =>
+ isProject
+ ? [...new Set(((logPage?.logs ?? []) as TimeLog[]).map((l) => l.member_id))].sort()
+ : [],
+ [isProject, logPage],
+ );
+ const { data: logMembersPage } = useQuery({
+ queryKey: ["plugin", PLUGIN_ID, "core", "members-by-id", projectId ?? "", logMemberIds.join(",")],
+ queryFn: () => api.listMembers({ ids: logMemberIds, pageSize: 100 }),
+ enabled: isProject && logMemberIds.length > 0,
+ placeholderData: keepPreviousData,
+ });
+ const memberNames = useMemo(
+ () => new Map((logMembersPage?.items ?? []).map((m) => [m.id, m.full_name || m.username])),
+ [logMembersPage],
+ );
+ const memberName = (memberId: string) => memberNames.get(memberId) ?? memberId;
+
const rows: DisplayRow[] = useMemo(() => {
const logs = logPage?.logs ?? [];
if (isProject) {
@@ -299,7 +322,7 @@ function TimeTrackingPageInner({ scope }: TimeTrackingPageProps) {
minutesSpent: l.minutes_spent,
note: l.note,
}));
- }, [logPage, isProject, members, tasks]);
+ }, [logPage, isProject, memberNames, tasks]);
const total = logPage?.total ?? 0;
const totalMinutes = logPage?.total_minutes ?? 0;
@@ -448,6 +471,14 @@ function TimeTrackingPageInner({ scope }: TimeTrackingPageProps) {
>
Member
+ setMemberSearch(e.target.value)}
+ placeholder="Search members"
+ aria-label="Search members"
+ className="min-w-36 rounded-lg bg-muted/40 px-2.5 py-1.5 text-xs"
+ />