Skip to content

Commit c6c51b3

Browse files
saagar210claude
andcommitted
refactor(features): extract queue operation handler callbacks
Move handleClaim, handleResolve, handleReopen, handlePriorityChange, and handleQueueKeyDown plus the currentItem derivation and withCurrentItem closure into useQueueOperationHandlers. The hook takes filteredItems + selectedIndex at call time so keyboard handlers always see the current selection. Wave 5.4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 89d4ac2 commit c6c51b3

2 files changed

Lines changed: 207 additions & 133 deletions

File tree

src/features/revamp/screens/QueueCommandCenterPage.tsx

Lines changed: 20 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useMemo, useState } from "react";
1+
import { useEffect, useMemo, useState } from "react";
22
import { Icon } from "../../../components/shared/Icon";
33
import { useToastContext } from "../../../contexts/ToastContext";
44
import { useAnalytics } from "../../../hooks/useAnalytics";
@@ -12,7 +12,6 @@ import { QueueHistoryTemplatesPanel } from "./QueueHistoryTemplatesPanel";
1212
import { resolveRevampFlags } from "../../revamp";
1313
import {
1414
formatQueueTimestamp,
15-
type QueueItem,
1615
type QueuePriority,
1716
type QueueView,
1817
} from "../../inbox/queueModel";
@@ -28,6 +27,7 @@ import {
2827
import { useQueueRenderingState } from "./useQueueRenderingState";
2928
import { useBatchTriageManager } from "./useBatchTriageManager";
3029
import { useDispatchManager } from "./useDispatchManager";
30+
import { useQueueOperationHandlers } from "./useQueueOperationHandlers";
3131
import "../../../styles/revamp/index.css";
3232
import "./queueCommandCenter.css";
3333

@@ -167,6 +167,24 @@ export function QueueCommandCenterPage({
167167
handleCancelDispatch,
168168
} = dispatch;
169169

170+
const operations = useQueueOperationHandlers({
171+
filteredItems,
172+
selectedIndex,
173+
setSelectedIndex,
174+
updateQueueMeta,
175+
operatorName,
176+
onLoadDraft,
177+
logEvent,
178+
});
179+
const {
180+
currentItem,
181+
handleClaim,
182+
handleResolve,
183+
handleReopen,
184+
handlePriorityChange,
185+
handleQueueKeyDown,
186+
} = operations;
187+
170188
useEffect(() => {
171189
loadDrafts(100);
172190
}, [loadDrafts]);
@@ -214,137 +232,6 @@ export function QueueCommandCenterPage({
214232
],
215233
);
216234

217-
const handleClaim = useCallback(
218-
(draftId: string) => {
219-
updateQueueMeta(draftId, { owner: operatorName, state: "in_progress" });
220-
void logEvent("queue_item_claimed", {
221-
draft_id: draftId,
222-
operator: operatorName,
223-
});
224-
},
225-
[logEvent, operatorName, updateQueueMeta],
226-
);
227-
228-
const handleResolve = useCallback(
229-
(draftId: string) => {
230-
updateQueueMeta(draftId, { state: "resolved" });
231-
void logEvent("queue_item_resolved", {
232-
draft_id: draftId,
233-
operator: operatorName,
234-
});
235-
},
236-
[logEvent, operatorName, updateQueueMeta],
237-
);
238-
239-
const handleReopen = useCallback(
240-
(draftId: string) => {
241-
updateQueueMeta(draftId, { state: "open" });
242-
void logEvent("queue_item_reopened", {
243-
draft_id: draftId,
244-
operator: operatorName,
245-
});
246-
},
247-
[logEvent, operatorName, updateQueueMeta],
248-
);
249-
250-
const handlePriorityChange = useCallback(
251-
(draftId: string, priority: QueuePriority) => {
252-
updateQueueMeta(draftId, { priority });
253-
void logEvent("queue_item_priority_changed", {
254-
draft_id: draftId,
255-
operator: operatorName,
256-
priority,
257-
});
258-
},
259-
[logEvent, operatorName, updateQueueMeta],
260-
);
261-
262-
const currentItem = filteredItems[selectedIndex] ?? null;
263-
264-
const withCurrentItem = useCallback(
265-
(handler: (item: QueueItem) => void) => {
266-
if (!currentItem) return;
267-
handler(currentItem);
268-
},
269-
[currentItem],
270-
);
271-
272-
const handleQueueKeyDown = useCallback(
273-
(event: React.KeyboardEvent<HTMLElement>) => {
274-
const target = event.target as HTMLElement;
275-
const isInputElement =
276-
target.tagName === "INPUT" ||
277-
target.tagName === "SELECT" ||
278-
target.tagName === "TEXTAREA";
279-
if (isInputElement) return;
280-
281-
switch (event.key.toLowerCase()) {
282-
case "arrowdown":
283-
case "j":
284-
event.preventDefault();
285-
setSelectedIndex((prev) =>
286-
Math.min(prev + 1, Math.max(filteredItems.length - 1, 0)),
287-
);
288-
break;
289-
case "arrowup":
290-
case "k":
291-
event.preventDefault();
292-
setSelectedIndex((prev) => Math.max(prev - 1, 0));
293-
break;
294-
case "enter":
295-
event.preventDefault();
296-
withCurrentItem((item) => {
297-
onLoadDraft(item.draft);
298-
void logEvent("queue_item_opened", {
299-
draft_id: item.draft.id,
300-
operator: operatorName,
301-
entrypoint: "keyboard",
302-
});
303-
});
304-
break;
305-
case "c":
306-
event.preventDefault();
307-
withCurrentItem((item) => {
308-
if (
309-
item.meta.owner === "unassigned" &&
310-
item.meta.state !== "resolved"
311-
) {
312-
handleClaim(item.draft.id);
313-
}
314-
});
315-
break;
316-
case "x":
317-
event.preventDefault();
318-
withCurrentItem((item) => {
319-
if (item.meta.state !== "resolved") {
320-
handleResolve(item.draft.id);
321-
}
322-
});
323-
break;
324-
case "o":
325-
event.preventDefault();
326-
withCurrentItem((item) => {
327-
if (item.meta.state === "resolved") {
328-
handleReopen(item.draft.id);
329-
}
330-
});
331-
break;
332-
default:
333-
break;
334-
}
335-
},
336-
[
337-
filteredItems.length,
338-
handleClaim,
339-
handleReopen,
340-
handleResolve,
341-
logEvent,
342-
onLoadDraft,
343-
operatorName,
344-
withCurrentItem,
345-
],
346-
);
347-
348235
const listActions = (
349236
<div className="as-queue__count" aria-label="Visible work item count">
350237
{loading ? "Loading…" : `${filteredItems.length} shown`}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { useCallback } from "react";
2+
import type { SavedDraft } from "../../../types/workspace";
3+
import {
4+
type QueueItem,
5+
type QueuePriority,
6+
type QueueState,
7+
} from "../../inbox/queueModel";
8+
9+
interface UseQueueOperationHandlersArgs {
10+
filteredItems: QueueItem[];
11+
selectedIndex: number;
12+
setSelectedIndex: React.Dispatch<React.SetStateAction<number>>;
13+
updateQueueMeta: (
14+
draftId: string,
15+
updates: Partial<{
16+
owner: string;
17+
state: QueueState;
18+
priority: QueuePriority;
19+
}>,
20+
) => void;
21+
operatorName: string;
22+
onLoadDraft: (draft: SavedDraft) => void;
23+
logEvent: (
24+
eventName: string,
25+
properties?: Record<string, unknown>,
26+
) => Promise<unknown> | unknown;
27+
}
28+
29+
export interface UseQueueOperationHandlersResult {
30+
currentItem: QueueItem | null;
31+
handleClaim: (draftId: string) => void;
32+
handleResolve: (draftId: string) => void;
33+
handleReopen: (draftId: string) => void;
34+
handlePriorityChange: (draftId: string, priority: QueuePriority) => void;
35+
handleQueueKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
36+
}
37+
38+
export function useQueueOperationHandlers({
39+
filteredItems,
40+
selectedIndex,
41+
setSelectedIndex,
42+
updateQueueMeta,
43+
operatorName,
44+
onLoadDraft,
45+
logEvent,
46+
}: UseQueueOperationHandlersArgs): UseQueueOperationHandlersResult {
47+
const currentItem = filteredItems[selectedIndex] ?? null;
48+
49+
const withCurrentItem = useCallback(
50+
(handler: (item: QueueItem) => void) => {
51+
if (!currentItem) return;
52+
handler(currentItem);
53+
},
54+
[currentItem],
55+
);
56+
57+
const handleClaim = useCallback(
58+
(draftId: string) => {
59+
updateQueueMeta(draftId, { owner: operatorName, state: "in_progress" });
60+
void logEvent("queue_item_claimed", {
61+
draft_id: draftId,
62+
operator: operatorName,
63+
});
64+
},
65+
[logEvent, operatorName, updateQueueMeta],
66+
);
67+
68+
const handleResolve = useCallback(
69+
(draftId: string) => {
70+
updateQueueMeta(draftId, { state: "resolved" });
71+
void logEvent("queue_item_resolved", {
72+
draft_id: draftId,
73+
operator: operatorName,
74+
});
75+
},
76+
[logEvent, operatorName, updateQueueMeta],
77+
);
78+
79+
const handleReopen = useCallback(
80+
(draftId: string) => {
81+
updateQueueMeta(draftId, { state: "open" });
82+
void logEvent("queue_item_reopened", {
83+
draft_id: draftId,
84+
operator: operatorName,
85+
});
86+
},
87+
[logEvent, operatorName, updateQueueMeta],
88+
);
89+
90+
const handlePriorityChange = useCallback(
91+
(draftId: string, priority: QueuePriority) => {
92+
updateQueueMeta(draftId, { priority });
93+
void logEvent("queue_item_priority_changed", {
94+
draft_id: draftId,
95+
operator: operatorName,
96+
priority,
97+
});
98+
},
99+
[logEvent, operatorName, updateQueueMeta],
100+
);
101+
102+
const handleQueueKeyDown = useCallback(
103+
(event: React.KeyboardEvent<HTMLElement>) => {
104+
const target = event.target as HTMLElement;
105+
const isInputElement =
106+
target.tagName === "INPUT" ||
107+
target.tagName === "SELECT" ||
108+
target.tagName === "TEXTAREA";
109+
if (isInputElement) return;
110+
111+
switch (event.key.toLowerCase()) {
112+
case "arrowdown":
113+
case "j":
114+
event.preventDefault();
115+
setSelectedIndex((prev) =>
116+
Math.min(prev + 1, Math.max(filteredItems.length - 1, 0)),
117+
);
118+
break;
119+
case "arrowup":
120+
case "k":
121+
event.preventDefault();
122+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
123+
break;
124+
case "enter":
125+
event.preventDefault();
126+
withCurrentItem((item) => {
127+
onLoadDraft(item.draft);
128+
void logEvent("queue_item_opened", {
129+
draft_id: item.draft.id,
130+
operator: operatorName,
131+
entrypoint: "keyboard",
132+
});
133+
});
134+
break;
135+
case "c":
136+
event.preventDefault();
137+
withCurrentItem((item) => {
138+
if (
139+
item.meta.owner === "unassigned" &&
140+
item.meta.state !== "resolved"
141+
) {
142+
handleClaim(item.draft.id);
143+
}
144+
});
145+
break;
146+
case "x":
147+
event.preventDefault();
148+
withCurrentItem((item) => {
149+
if (item.meta.state !== "resolved") {
150+
handleResolve(item.draft.id);
151+
}
152+
});
153+
break;
154+
case "o":
155+
event.preventDefault();
156+
withCurrentItem((item) => {
157+
if (item.meta.state === "resolved") {
158+
handleReopen(item.draft.id);
159+
}
160+
});
161+
break;
162+
default:
163+
break;
164+
}
165+
},
166+
[
167+
filteredItems.length,
168+
handleClaim,
169+
handleReopen,
170+
handleResolve,
171+
logEvent,
172+
onLoadDraft,
173+
operatorName,
174+
setSelectedIndex,
175+
withCurrentItem,
176+
],
177+
);
178+
179+
return {
180+
currentItem,
181+
handleClaim,
182+
handleResolve,
183+
handleReopen,
184+
handlePriorityChange,
185+
handleQueueKeyDown,
186+
};
187+
}

0 commit comments

Comments
 (0)