diff --git a/frontend/src/app/(portal)/reviewer/applications/components/ApplicationsClient.tsx b/frontend/src/app/(portal)/reviewer/applications/components/ApplicationsClient.tsx
index 6eaf199..2f4508f 100644
--- a/frontend/src/app/(portal)/reviewer/applications/components/ApplicationsClient.tsx
+++ b/frontend/src/app/(portal)/reviewer/applications/components/ApplicationsClient.tsx
@@ -2,8 +2,7 @@
import { PageContainer } from '@/components/PageContainer'
import { useMemo, useState } from 'react'
-import { Loader2, Search, List, Columns } from 'lucide-react'
-import { Button } from '@/components/ui/button'
+import { Search, List, Columns } from 'lucide-react'
import {
Select,
SelectContent,
@@ -26,7 +25,7 @@ import { pickDefaultCycleId, useCycles } from '@/lib/queries/cycles'
import { useQuestionsByCycleRoles } from '@/lib/queries/questions'
import { useCurrentUser } from '@/lib/queries/users'
import { ROLE_COLUMNS, ROLE_LABEL } from '@/lib/roles'
-import { ORDERED_STAGES, stageLabel } from './constants'
+import { BulkActionBar } from './BulkActionBar'
import {
AVAILABILITY_DAY_OPTIONS,
availabilityOptionsFor,
@@ -218,6 +217,14 @@ export function ApplicationsClient() {
})
}
+ // Dropping the selection also drops the bar's transient state, so a failure
+ // notice from a previous batch can't linger into the next one.
+ function clearSelection() {
+ setSelectedIds(new Set())
+ setBulkStage('')
+ setBulkFailed(0)
+ }
+
async function applyBulkStage() {
if (!bulkStage || selectedIds.size === 0) return
setApplyingBulk(true)
@@ -352,49 +359,6 @@ export function ApplicationsClient() {
- {view === 'table' && isChief && (
-
-
- {selectedIds.size} selected
-
- {bulkFailed > 0 && (
-
- {bulkFailed} update{bulkFailed === 1 ? '' : 's'} failed
-
- )}
-
-
-
-
-
- )}
-
{view === 'table' ? (
0 ? (
+
+ ) : undefined
+ }
/>
) : (
void
+ onApply: () => void
+ onClear: () => void
+ applying: boolean
+ failedCount: number
+}) {
+ return (
+
+
+ {selectedCount} selected
+
+
+ {failedCount > 0 && (
+
+ {failedCount} update{failedCount === 1 ? '' : 's'} failed
+
+ )}
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/app/(portal)/reviewer/applications/components/TableView.tsx b/frontend/src/app/(portal)/reviewer/applications/components/TableView.tsx
index b4b2e31..e1eafd0 100644
--- a/frontend/src/app/(portal)/reviewer/applications/components/TableView.tsx
+++ b/frontend/src/app/(portal)/reviewer/applications/components/TableView.tsx
@@ -26,6 +26,7 @@ export function TableView({
onSelectApplication,
filters,
onFilterChange,
+ bulkBar,
}: {
applicants: ApplicantApplication[]
allApplicants: ApplicantApplication[]
@@ -48,6 +49,9 @@ export function TableView({
filter: AnswerFilter | null,
action: 'add' | 'remove'
) => void
+ // Rendered in the filter row's place while a selection is active. Owned by
+ // the parent, which holds the selection and the bulk mutation.
+ bulkBar?: React.ReactNode
}) {
const countByStage = (stage: ApplicationStage | 'all') =>
stage === 'all'
@@ -72,11 +76,13 @@ export function TableView({
return (
-
+ {bulkBar ?? (
+
+ )}
{FILTER_STAGES.map(({ label, value }) => (