diff --git a/src/app/components/portfolio/Toolbar.tsx b/src/app/components/portfolio/Toolbar.tsx index d9ff0df..91c0155 100644 --- a/src/app/components/portfolio/Toolbar.tsx +++ b/src/app/components/portfolio/Toolbar.tsx @@ -63,7 +63,7 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) { return ( <> - + {navItems.map(({ label, icon: Icon, href, match }) => { const isActive = match(pathname); @@ -73,14 +73,14 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) { {isOpen && ( -
+
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") commit(); - if (e.key === "Escape") { setDraft(committedValue); onCommit(committedValue); } + if (e.key === "Escape") { + setDraft(committedValue); + onCommit(committedValue); + } }} /> @@ -168,19 +203,25 @@ export default function PropertyTable({ }: { portfolioId: string; }) { - const [sidebarOpen, setSidebarOpen] = useState(true); + const [sidebarOpen, setSidebarOpen] = useState(false); - // Committed quick filter values (drives the query) const [committedAddress, setCommittedAddress] = useState(""); const [committedPostcode, setCommittedPostcode] = useState(""); const [committedPropertyRef, setCommittedPropertyRef] = useState(""); - - // Which quick filter dropdown is open const [openFilter, setOpenFilter] = useState(null); - - // Advanced filter groups from the sidebar const [filterGroups, setFilterGroups] = useState([]); + // Column visibility — lifted up from DataTable + const [columnVisibility, setColumnVisibility] = useState( + () => { + const init: VisibilityState = {}; + OPTIONAL_COLUMN_IDS.forEach((id) => { + init[id] = false; + }); + return init; + }, + ); + function commitFilter(field: QuickFilterKey, value: string) { if (field === "address") setCommittedAddress(value); if (field === "postcode") setCommittedPostcode(value); @@ -201,17 +242,38 @@ export default function PropertyTable({ if (committedAddress) quick.push({ id: "qa", - conditions: [{ id: "qa-c", field: "address", operator: "contains", value: committedAddress }], + conditions: [ + { + id: "qa-c", + field: "address", + operator: "contains", + value: committedAddress, + }, + ], }); if (committedPostcode) quick.push({ id: "qp", - conditions: [{ id: "qp-c", field: "postcode", operator: "starts_with", value: committedPostcode }], + conditions: [ + { + id: "qp-c", + field: "postcode", + operator: "starts_with", + value: committedPostcode, + }, + ], }); if (committedPropertyRef) quick.push({ id: "qr", - conditions: [{ id: "qr-c", field: "propertyRef", operator: "contains", value: committedPropertyRef }], + conditions: [ + { + id: "qr-c", + field: "propertyRef", + operator: "contains", + value: committedPropertyRef, + }, + ], }); return [...quick, ...filterGroups]; }, [committedAddress, committedPostcode, committedPropertyRef, filterGroups]); @@ -228,6 +290,14 @@ export default function PropertyTable({ filterGroups: allFilterGroups, }); + // Second query for total (no filters) — React Query dedupes when filters are empty + const { data: allData = [] } = useProperties({ + portfolioId, + filterGroups: [], + }); + const totalCount = allData.length; + const filteredCount = data.length; + /* ---------------------------------------- Delete preview state ----------------------------------------- */ @@ -239,26 +309,121 @@ export default function PropertyTable({ const [previewError] = useState(null); return ( -
+
+ {/* Action bar */} +
+ {/* Left: results count */} +
+ + Results: + + {isLoading ? ( + Loading… + ) : ( + + Showing{" "} + + {filteredCount.toLocaleString()} + {" "} + of{" "} + + {totalCount.toLocaleString()} + {" "} + properties + + )} + {hasActiveFilters && ( + + )} +
+ + {/* Right: action buttons */} +
+ {/* Filters toggle */} + + + {/* Edit Columns dropdown */} + + + + + + + Optional Columns + + + {OPTIONAL_COLUMN_IDS.map((colId) => { + const isVisible = columnVisibility[colId] !== false; + return ( + { + e.preventDefault(); + setColumnVisibility((prev) => ({ + ...prev, + [colId]: !isVisible, + })); + }} + > + + + {OPTIONAL_COLUMN_LABELS[colId]} + + + ); + })} + + + + {/* Export (stub) */} + +
+
+ {/* Quick filters row */}
- - setOpenFilter(openFilter === "address" ? null : "address")} + onOpen={() => + setOpenFilter(openFilter === "address" ? null : "address") + } onCommit={(v) => commitFilter("address", v)} - onClear={() => { setCommittedAddress(""); setOpenFilter(null); }} + onClear={() => { + setCommittedAddress(""); + setOpenFilter(null); + }} inputWidth="w-52" /> @@ -267,9 +432,14 @@ export default function PropertyTable({ placeholder="e.g. E17" committedValue={committedPostcode} isOpen={openFilter === "postcode"} - onOpen={() => setOpenFilter(openFilter === "postcode" ? null : "postcode")} + onOpen={() => + setOpenFilter(openFilter === "postcode" ? null : "postcode") + } onCommit={(v) => commitFilter("postcode", v)} - onClear={() => { setCommittedPostcode(""); setOpenFilter(null); }} + onClear={() => { + setCommittedPostcode(""); + setOpenFilter(null); + }} inputWidth="w-32" /> @@ -278,37 +448,32 @@ export default function PropertyTable({ placeholder="Landlord ref…" committedValue={committedPropertyRef} isOpen={openFilter === "propertyRef"} - onOpen={() => setOpenFilter(openFilter === "propertyRef" ? null : "propertyRef")} + onOpen={() => + setOpenFilter(openFilter === "propertyRef" ? null : "propertyRef") + } onCommit={(v) => commitFilter("propertyRef", v)} - onClear={() => { setCommittedPropertyRef(""); setOpenFilter(null); }} + onClear={() => { + setCommittedPropertyRef(""); + setOpenFilter(null); + }} inputWidth="w-40" /> - - {hasActiveFilters && ( - - )}
{/* Body: sidebar + table */} -
+
{/* Collapsible filter sidebar */}
-

- Filters +

+ Curate Selection

{/* Table area */} -
+
{isFetching && !isLoading && } {isLoading ? ( -
Loading properties…
+
+ Loading properties… +
) : isError ? ( -
Failed to load properties.
+
+ Failed to load properties. +
) : data.length === 0 && hasActiveFilters ? ( -
-

No properties match your filters.

+
+

No properties match your filters.

@@ -342,6 +511,12 @@ export default function PropertyTable({ data={data} columns={columns} onDeleteProperty={(id) => setDeletePropertyId(id)} + columnVisibility={columnVisibility} + onColumnVisibilityChange={ + setColumnVisibility as ( + updater: Updater, + ) => void + } /> )}
diff --git a/src/app/portfolio/[slug]/components/dataTable.tsx b/src/app/portfolio/[slug]/components/dataTable.tsx index be90d39..255e0fc 100644 --- a/src/app/portfolio/[slug]/components/dataTable.tsx +++ b/src/app/portfolio/[slug]/components/dataTable.tsx @@ -6,6 +6,7 @@ import { SortingState, PaginationState, VisibilityState, + Updater, flexRender, getCoreRowModel, getFilteredRowModel, @@ -24,23 +25,10 @@ import { TableRow, } from "@/app/shadcn_components/ui/table"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/app/shadcn_components/ui/dropdown-menu"; import { useState } from "react"; import { DataTablePagination } from "./propertyTablePagination"; import { rankItem } from "@tanstack/match-sorter-utils"; -import { Button } from "@/app/shadcn_components/ui/button"; -import { - OPTIONAL_COLUMN_IDS, - OPTIONAL_COLUMN_LABELS, -} from "./propertyTableColumns"; /* ---------------------------------------- Optional fuzzy global filter @@ -55,12 +43,16 @@ interface DataTableProps { columns: ColumnDef[]; data: TData[]; onDeleteProperty?: (propertyId: number) => void; + columnVisibility: VisibilityState; + onColumnVisibilityChange: (updater: Updater) => void; } export default function DataTable>({ data, columns, onDeleteProperty, + columnVisibility, + onColumnVisibilityChange, }: DataTableProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); @@ -69,15 +61,6 @@ export default function DataTable>({ pageIndex: 0, pageSize: 7, }); - const [columnVisibility, setColumnVisibility] = useState( - () => { - const init: VisibilityState = {}; - OPTIONAL_COLUMN_IDS.forEach((id) => { - init[id] = false; - }); - return init; - } - ); const table = useReactTable({ data, @@ -92,7 +75,7 @@ export default function DataTable>({ onColumnFiltersChange: setColumnFilters, onGlobalFilterChange: setGlobalFilter, onPaginationChange: setPagination, - onColumnVisibilityChange: setColumnVisibility, + onColumnVisibilityChange, globalFilterFn: fuzzyFilter, @@ -110,48 +93,7 @@ export default function DataTable>({ }); return ( -
- {/* Edit Columns toolbar */} -
- - - - - - - Optional Columns - - - {OPTIONAL_COLUMN_IDS.map((colId) => { - const col = table.getColumn(colId); - if (!col) return null; - return ( - { - e.preventDefault(); - col.toggleVisibility(); - }} - > - - - {OPTIONAL_COLUMN_LABELS[colId]} - - - ); - })} - - -
- +
{table.getHeaderGroups().map((headerGroup) => (