);
}
@@ -78,22 +69,29 @@ export function ReviewDeskPage() {
useDocumentTitle("Inbox");
const [searchParams] = useSearchParams();
const [search, setSearch, q] = useDebouncedSearchParam();
- const {
- items: list,
- moreArchived,
- query: items,
- } = useReviewItemsData({ includeArchived: true, q });
+ const { items: list, moreArchived, query: items } = useReviewItemsData({ q });
const showBlocked = !q;
const blocked = useBlockedAutomationsData(showBlocked);
const blockedAutomations = showBlocked
? (blocked.data?.automations ?? [])
: [];
const groups = useMemo(() => inboxGroups(list), [list]);
+ // A search lists every match in one list. Later pages add open items, so
+ // keep open matches before archived ones.
+ const results = useMemo(
+ () =>
+ q
+ ? [
+ ...list.filter((item) => item.status === "open"),
+ ...list.filter((item) => item.status !== "open"),
+ ]
+ : undefined,
+ [list, q],
+ );
const open = [...groups.needsYou, ...groups.fyi];
- // Archived items stay out of the way until a search or the reader asks.
const [collapsed, setCollapsed] = useState>(
- () => new Set(["archived"]),
+ () => new Set(),
);
const toggle = (key: InboxGroupKey) =>
setCollapsed((current) => {
@@ -102,16 +100,6 @@ export function ReviewDeskPage() {
else next.add(key);
return next;
});
- const shownCollapsed = useMemo(() => {
- if (!q || !collapsed.has("archived")) return collapsed;
- const next = new Set(collapsed);
- next.delete("archived");
- return next;
- }, [collapsed, q]);
- const showArchived =
- groups.archived.length > 0 && shownCollapsed.has("archived")
- ? () => toggle("archived")
- : undefined;
const itemParam = searchParams.get("item") ?? undefined;
// Wide screens select the item that waited longest. Small screens show
@@ -125,12 +113,11 @@ export function ReviewDeskPage() {
// Read source titles only for the rows on screen and the selected item.
const sourceIds = useMemo(() => {
- const shown = [
- ...(shownCollapsed.has("needs-you") ? [] : groups.needsYou),
- ...(shownCollapsed.has("fyi") ? [] : groups.fyi),
- ...(shownCollapsed.has("archived") ? [] : groups.archived),
- ...(selected ? [selected] : []),
+ const rows = results ?? [
+ ...(collapsed.has("needs-you") ? [] : groups.needsYou),
+ ...(collapsed.has("fyi") ? [] : groups.fyi),
];
+ const shown = selected ? [...rows, selected] : rows;
return [
...new Set(
shown
@@ -138,7 +125,7 @@ export function ReviewDeskPage() {
.map((item) => item.addedByConversationId),
),
];
- }, [groups, selected, shownCollapsed]);
+ }, [collapsed, groups, results, selected]);
const sourceTitles = useLinkedConversationTitles(sourceIds);
const sourceTitle = (item: ReviewItemPayload) =>
sourceTitles[sourceIds.indexOf(item.addedByConversationId)];
@@ -162,9 +149,8 @@ export function ReviewDeskPage() {
)}
>
@@ -179,9 +165,9 @@ export function ReviewDeskPage() {
Could not load more items. Try again.
) : null}
- {moreArchived && !shownCollapsed.has("archived") ? (
+ {moreArchived ? (
- Search to find older archived items.
+ Narrow the search to find older archived items.
) : null}
{loaded && q && list.length === 0 ? (
@@ -189,12 +175,7 @@ export function ReviewDeskPage() {
No items match "{q}".
) : null}
- {empty ? (
-
- ) : null}
+ {empty ? : null}
>
}
fyi={groups.fyi}
@@ -202,6 +183,7 @@ export function ReviewDeskPage() {
needsYou={groups.needsYou}
onSearchChange={setSearch}
onToggle={toggle}
+ results={results}
search={search}
searchParams={listParams}
selectedId={selected?.id}
@@ -250,7 +232,7 @@ export function ReviewDeskPage() {
) : direct.error ? (
Review item not found.
) : empty ? (
-
+
) : null}
diff --git a/packages/junior-dashboard/src/client/review-desk/ReviewDeskSidebar.tsx b/packages/junior-dashboard/src/client/review-desk/ReviewDeskSidebar.tsx
index 4e3bbad68d..488787fc3c 100644
--- a/packages/junior-dashboard/src/client/review-desk/ReviewDeskSidebar.tsx
+++ b/packages/junior-dashboard/src/client/review-desk/ReviewDeskSidebar.tsx
@@ -12,10 +12,10 @@ import { ConversationSectionHeading } from "../conversations/ConversationSection
import { automationPath, formatRelativeTime } from "../format";
import { pathWithSearch } from "../searchParams";
import { cn } from "../styles";
-import { reviewItemPath } from "./reviewItemFormat";
+import { reviewItemPath, reviewItemStatusLabel } from "./reviewItemFormat";
/** A sidebar group key. The page owns which groups are collapsed. */
-export type InboxGroupKey = "archived" | "blocked" | "fyi" | "needs-you";
+export type InboxGroupKey = "blocked" | "fyi" | "needs-you";
type Dot = "accent" | "muted" | "warning" | "closed";
@@ -120,11 +120,11 @@ function InboxRowsLoading() {
}
/**
- * List the inbox: open items that need the viewer, notices, blocked
- * Automations, and archived items, each in a group that collapses.
+ * List the inbox: open items that need the viewer, notices, and blocked
+ * Automations, each in a group that collapses. A search shows one list of
+ * open and archived matches instead.
*/
export function ReviewDeskSidebar(props: {
- archived: ReviewItemPayload[];
blocked: AutomationSummary[];
collapsed: ReadonlySet;
/** Shown under the groups, such as an empty state or load more. */
@@ -135,6 +135,8 @@ export function ReviewDeskSidebar(props: {
needsYou: ReviewItemPayload[];
onSearchChange(value: string): void;
onToggle(key: InboxGroupKey): void;
+ /** Search matches in API order. Set while a search is active. */
+ results?: ReviewItemPayload[];
search: string;
searchParams: URLSearchParams;
selectedId?: string;
@@ -153,16 +155,19 @@ export function ReviewDeskSidebar(props: {
];
const firstKey = (index: number) =>
shown.slice(0, index).every((count) => count === 0);
- const itemRow = (item: ReviewItemPayload, dot: Dot) => {
+ const itemRow = (item: ReviewItemPayload) => {
+ const open = item.status === "open";
const source = item.automationId ? "Automation" : props.sourceTitle(item);
- const age = formatRelativeTime(
- item.status === "open" ? item.createdAt : item.closedAt,
- );
+ const meta = [
+ open ? undefined : reviewItemStatusLabel(item),
+ source,
+ formatRelativeTime(open ? item.createdAt : item.closedAt),
+ ];
return (
) : props.loading ? (
+ ) : props.results ? (
+ <>
+ {props.results.length > 0 ? (
+
+ {props.results.map(itemRow)}
+
+ ) : null}
+ {props.footer}
+ >
) : (
<>
{groups.map((group, index) => (
@@ -213,9 +230,7 @@ export function ReviewDeskSidebar(props: {
label={group.label}
onToggle={() => props.onToggle(group.key)}
>
- {group.items.map((item) =>
- itemRow(item, item.kind === "action" ? "accent" : "muted"),
- )}
+ {group.items.map(itemRow)}
))}
))}
- props.onToggle("archived")}
- >
- {props.archived.map((item) => itemRow(item, "closed"))}
-
{props.footer}
>
)}
diff --git a/packages/junior-dashboard/src/client/review-desk/queries.ts b/packages/junior-dashboard/src/client/review-desk/queries.ts
index 315d43a8e9..2e95bafa0f 100644
--- a/packages/junior-dashboard/src/client/review-desk/queries.ts
+++ b/packages/junior-dashboard/src/client/review-desk/queries.ts
@@ -25,19 +25,17 @@ const COUNT_REFRESH_MS = 60_000;
/**
* List the open Review items that the viewer can see, one page at a time.
- * A search or `includeArchived` also finds archived items, with the first
- * page. `items` holds the items of every loaded page, and `query` loads more.
+ * A search also finds archived items, with the first page. `items` holds the
+ * items of every loaded page, and `query` loads more.
*/
export function useReviewItemsData(filters: {
conversationId?: string;
- includeArchived?: boolean;
q?: string;
}) {
const params = new URLSearchParams();
if (filters.conversationId) {
params.set("conversationId", filters.conversationId);
}
- if (filters.includeArchived) params.set("include", "archived");
if (filters.q) params.set("q", filters.q);
const search = params.toString();
const query = useInfiniteQuery({
diff --git a/packages/junior-dashboard/src/client/review-desk/reviewItemFormat.ts b/packages/junior-dashboard/src/client/review-desk/reviewItemFormat.ts
index 6b03fe6f00..e6cb063e83 100644
--- a/packages/junior-dashboard/src/client/review-desk/reviewItemFormat.ts
+++ b/packages/junior-dashboard/src/client/review-desk/reviewItemFormat.ts
@@ -101,9 +101,8 @@ export function reviewItemWaitsForAnswer(item: ReviewItemPayload): boolean {
);
}
-/** The Review items of the inbox, in the groups and order the list shows. */
+/** The open Review items of the inbox, in the groups and order it shows. */
export type InboxGroups = {
- archived: ReviewItemPayload[];
fyi: ReviewItemPayload[];
needsYou: ReviewItemPayload[];
};
@@ -117,13 +116,12 @@ function inboxOrder(a: ReviewItemPayload, b: ReviewItemPayload): number {
}
/**
- * Split items into inbox groups. Open items show oldest first, so the item
- * that waited longest is at the top. Archived items keep the API order.
+ * Split open items into inbox groups, oldest first, so the item that waited
+ * longest is at the top. Archived items are in no group.
*/
export function inboxGroups(items: readonly ReviewItemPayload[]): InboxGroups {
const open = items.filter((item) => item.status === "open");
return {
- archived: items.filter((item) => item.status !== "open"),
fyi: open.filter((item) => item.kind === "notice").sort(inboxOrder),
needsYou: open.filter((item) => item.kind === "action").sort(inboxOrder),
};