feat(live-reporting): select page vs all pages in bulk download

The header tick now selects the current page (its intuitive meaning). When
the whole page is ticked and more filtered rows exist, a Gmail-style banner
offers "Select all N across pages" (and "Clear selection" once everything
is selected) — so users can grab a page-sized chunk without selecting the
entire project.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-08 16:04:53 +00:00
parent 34dd3fe7ae
commit 7a7f7e157e
2 changed files with 69 additions and 7 deletions

View file

@ -178,7 +178,8 @@ export default function DocumentBulkDownload({
{selectedIds.size} selected
</span>
<span className="text-xs text-gray-500">
Tick the properties to include, or use the header box to select all.
Tick properties, or use the header box to select the page (then select
all across pages).
</span>
{overCap && (
<span className="text-xs font-medium text-red-600">

View file

@ -119,21 +119,23 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
enableSorting: false,
enableGlobalFilter: false,
header: ({ table }) => {
const ids = table
.getFilteredRowModel()
// Header ticks the current page; the banner below offers all pages.
const pageIds = table
.getRowModel()
.rows.map((r) => r.original.landlordPropertyId)
.filter((id): id is string => !!id);
const allSelected = ids.length > 0 && ids.every((id) => selection.selectedIds.has(id));
const someSelected = ids.some((id) => selection.selectedIds.has(id));
const allSelected =
pageIds.length > 0 && pageIds.every((id) => selection.selectedIds.has(id));
const someSelected = pageIds.some((id) => selection.selectedIds.has(id));
return (
<input
type="checkbox"
aria-label="Select all filtered properties"
aria-label="Select all properties on this page"
checked={allSelected}
ref={(el) => {
if (el) el.indeterminate = someSelected && !allSelected;
}}
onChange={() => selection.onSetMany(ids, !allSelected)}
onChange={() => selection.onSetMany(pageIds, !allSelected)}
className="h-4 w-4 accent-brandblue align-middle"
/>
);
@ -217,6 +219,31 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
const currentPage = table.getState().pagination.pageIndex + 1;
const totalFiltered = table.getFilteredRowModel().rows.length;
// "Select all across pages" banner: shown when the whole current page is
// ticked and there are more filtered rows than fit on it (Gmail-style).
const pageSelectableIds = selection
? table
.getRowModel()
.rows.map((r) => r.original.landlordPropertyId)
.filter((id): id is string => !!id)
: [];
const filteredSelectableIds = selection
? table
.getFilteredRowModel()
.rows.map((r) => r.original.landlordPropertyId)
.filter((id): id is string => !!id)
: [];
const pageFullySelected =
pageSelectableIds.length > 0 &&
pageSelectableIds.every((id) => selection!.selectedIds.has(id));
const allFilteredSelected =
filteredSelectableIds.length > 0 &&
filteredSelectableIds.every((id) => selection!.selectedIds.has(id));
const showSelectAllBanner =
!!selection &&
pageFullySelected &&
filteredSelectableIds.length > pageSelectableIds.length;
const retroAssessmentLabel: Record<RetroAssessmentFilter, string> = {
all: "All retrofit statuses",
none: "No Retrofit Docs",
@ -314,6 +341,40 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
propert{totalFiltered === 1 ? "y" : "ies"}
</p>
{/* Select-all-across-pages banner */}
{showSelectAllBanner && (
<div className="flex flex-wrap items-center gap-2 rounded-lg border border-brandblue/20 bg-brandlightblue/20 px-3.5 py-2 text-[13px] text-brandblue">
{allFilteredSelected ? (
<>
<span>
All <span className="font-semibold">{filteredSelectableIds.length}</span>{" "}
matching propert{filteredSelectableIds.length === 1 ? "y is" : "ies are"}{" "}
selected.
</span>
<button
onClick={() => selection!.onSetMany(filteredSelectableIds, false)}
className="font-semibold underline hover:text-brandblue/80"
>
Clear selection
</button>
</>
) : (
<>
<span>
All <span className="font-semibold">{pageSelectableIds.length}</span> on this
page selected.
</span>
<button
onClick={() => selection!.onSetMany(filteredSelectableIds, true)}
className="font-semibold underline hover:text-brandblue/80"
>
Select all {filteredSelectableIds.length} across pages
</button>
</>
)}
</div>
)}
{/* Table */}
<div className="rounded-xl border border-gray-200 overflow-hidden shadow-sm">
<div className="overflow-x-auto">