mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
tweaking layout
This commit is contained in:
parent
8c9cceef77
commit
910198e847
4 changed files with 252 additions and 133 deletions
|
|
@ -63,7 +63,7 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<NavigationMenu className="relative">
|
||||
<NavigationMenu className="relative px-4">
|
||||
<NavigationMenuList className="flex-wrap">
|
||||
{navItems.map(({ label, icon: Icon, href, match }) => {
|
||||
const isActive = match(pathname);
|
||||
|
|
@ -73,14 +73,14 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) {
|
|||
<button
|
||||
onClick={() => router.push(href)}
|
||||
className={cn(
|
||||
"relative flex items-center rounded-md text-sm font-medium p-[3px]",
|
||||
"relative flex items-center rounded-md text-xs font-medium p-[3px]",
|
||||
isActive &&
|
||||
"bg-gradient-to-r from-brandblue via-brandbrown to-brandblue"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center rounded-md px-4 py-2",
|
||||
"flex items-center rounded-md px-3 py-1.5",
|
||||
isActive
|
||||
? "bg-white text-brandblue shadow-sm"
|
||||
: "bg-gray-50 text-gray-800 hover:bg-midblue hover:text-gray-100"
|
||||
|
|
@ -107,14 +107,14 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) {
|
|||
<button
|
||||
onClick={() => router.push(href)}
|
||||
className={cn(
|
||||
"relative flex items-center rounded-md text-sm font-medium p-[3px]",
|
||||
"relative flex items-center rounded-md text-xs font-medium p-[3px]",
|
||||
isActive &&
|
||||
"bg-gradient-to-r from-brandblue via-brandbrown to-brandblue"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center rounded-md px-4 py-2",
|
||||
"flex items-center rounded-md px-3 py-1.5",
|
||||
isActive
|
||||
? "bg-white text-brandblue shadow-sm"
|
||||
: "bg-gray-50 text-gray-800 hover:bg-midblue hover:text-gray-100"
|
||||
|
|
|
|||
|
|
@ -31,9 +31,11 @@ export default async function PortfolioLayout(props: {
|
|||
<div className="col-span-12 justify-center bg-gray-50 py-1 px-4 relative">
|
||||
<Toolbar portfolioId={portfolioId} scenarios={scenarios} />
|
||||
</div>
|
||||
<div className="col-span-12">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,29 @@ import { useProperties } from "./useProperties";
|
|||
import DataTable from "./dataTable";
|
||||
import PropertyFilters from "./PropertyFilters";
|
||||
import { FilterGroups } from "@/app/utils/propertyFilters";
|
||||
import { HomeIcon, FunnelIcon, ChevronDownIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
HomeIcon,
|
||||
FunnelIcon,
|
||||
ChevronDownIcon,
|
||||
XMarkIcon,
|
||||
ArrowDownTrayIcon,
|
||||
ViewColumnsIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { columns } from "@/app/portfolio/[slug]/components/propertyTableColumns";
|
||||
import {
|
||||
OPTIONAL_COLUMN_IDS,
|
||||
OPTIONAL_COLUMN_LABELS,
|
||||
} from "@/app/portfolio/[slug]/components/propertyTableColumns";
|
||||
import { VisibilityState, Updater } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/app/shadcn_components/ui/dropdown-menu";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -40,7 +61,7 @@ function EmptyPropertyState() {
|
|||
----------------------------------------- */
|
||||
function LoadingOverlay() {
|
||||
return (
|
||||
<div className="absolute inset-0 z-10 rounded-md bg-white/60 flex items-center justify-center pointer-events-none">
|
||||
<div className="absolute inset-0 z-10 rounded-xl bg-white/60 flex items-center justify-center pointer-events-none">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-gray-200 border-t-gray-700" />
|
||||
<span className="text-xs text-gray-500">Updating…</span>
|
||||
|
|
@ -79,7 +100,6 @@ function QuickFilterDropdown({
|
|||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [draft, setDraft] = useState(committedValue);
|
||||
|
||||
// Sync draft when dropdown opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setDraft(committedValue);
|
||||
|
|
@ -91,11 +111,13 @@ function QuickFilterDropdown({
|
|||
onCommit(draft.trim());
|
||||
}, [draft, onCommit]);
|
||||
|
||||
// Close + commit on outside click
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
function handleMouseDown(e: MouseEvent) {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(e.target as Node)
|
||||
) {
|
||||
commit();
|
||||
}
|
||||
}
|
||||
|
|
@ -110,21 +132,31 @@ function QuickFilterDropdown({
|
|||
<button
|
||||
onClick={onOpen}
|
||||
className={[
|
||||
"flex items-center gap-1.5 h-9 px-3 rounded-md border text-sm transition shrink-0",
|
||||
"flex items-center gap-1.5 h-8 px-3 rounded-lg border text-xs font-semibold transition shrink-0",
|
||||
isActive
|
||||
? "border-gray-800 bg-gray-800 text-white"
|
||||
: "border-gray-300 text-gray-600 hover:bg-gray-50",
|
||||
? "border-brandblue bg-brandblue text-white"
|
||||
: "border-slate-200 bg-white text-primary hover:bg-slate-50",
|
||||
].join(" ")}
|
||||
>
|
||||
<span className="font-medium">{label}</span>
|
||||
<span>{label}</span>
|
||||
{isActive ? (
|
||||
<>
|
||||
<span className="opacity-75 max-w-[120px] truncate">: {committedValue}</span>
|
||||
<span className="opacity-75 max-w-[120px] truncate">
|
||||
: {committedValue}
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e) => { e.stopPropagation(); onClear(); }}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") { e.stopPropagation(); onClear(); } }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClear();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
onClear();
|
||||
}
|
||||
}}
|
||||
className="ml-0.5 rounded-full hover:opacity-75"
|
||||
>
|
||||
<XMarkIcon className="h-3.5 w-3.5" />
|
||||
|
|
@ -136,21 +168,24 @@ function QuickFilterDropdown({
|
|||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute left-0 top-full mt-1 z-30 bg-white border border-gray-200 rounded-md shadow-md p-2 flex gap-1.5 items-center">
|
||||
<div className="absolute left-0 top-full mt-1 z-30 bg-white border border-slate-200 rounded-lg shadow-md p-2 flex gap-1.5 items-center">
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`h-8 rounded border border-gray-300 px-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-black/10 ${inputWidth}`}
|
||||
className={`h-8 rounded-lg border border-slate-200 px-2.5 text-xs focus:outline-none focus:ring-2 focus:ring-brandblue/20 focus:border-brandblue ${inputWidth}`}
|
||||
placeholder={placeholder}
|
||||
value={draft}
|
||||
onChange={(e) => 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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={commit}
|
||||
className="h-8 px-2.5 rounded bg-gray-800 text-white text-sm hover:bg-gray-700 transition whitespace-nowrap"
|
||||
className="h-8 px-2.5 rounded-lg bg-brandblue text-white text-xs font-semibold hover:opacity-90 transition whitespace-nowrap"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
|
|
@ -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<QuickFilterKey | null>(null);
|
||||
|
||||
// Advanced filter groups from the sidebar
|
||||
const [filterGroups, setFilterGroups] = useState<FilterGroups>([]);
|
||||
|
||||
// Column visibility — lifted up from DataTable
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
() => {
|
||||
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<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="px-4 py-3">
|
||||
<div className="py-4">
|
||||
{/* Action bar */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
{/* Left: results count */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs font-black uppercase tracking-wider text-primary">
|
||||
Results:
|
||||
</span>
|
||||
{isLoading ? (
|
||||
<span className="text-xs text-slate-400">Loading…</span>
|
||||
) : (
|
||||
<span className="text-xs text-slate-500">
|
||||
Showing{" "}
|
||||
<span className="font-bold text-primary">
|
||||
{filteredCount.toLocaleString()}
|
||||
</span>{" "}
|
||||
of{" "}
|
||||
<span className="font-bold text-primary">
|
||||
{totalCount.toLocaleString()}
|
||||
</span>{" "}
|
||||
properties
|
||||
</span>
|
||||
)}
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={clearAll}
|
||||
className="text-xs text-slate-400 hover:text-primary underline"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: action buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Filters toggle */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen((o) => !o)}
|
||||
className={[
|
||||
"flex items-center gap-1.5 h-8 px-3 rounded-lg border text-xs font-semibold transition shrink-0",
|
||||
sidebarOpen
|
||||
? "bg-primary text-white border-primary"
|
||||
: "border-slate-200 bg-white text-primary hover:bg-slate-50",
|
||||
].join(" ")}
|
||||
title={sidebarOpen ? "Hide filters" : "Show filters"}
|
||||
>
|
||||
<FunnelIcon className="h-3.5 w-3.5" />
|
||||
Filters
|
||||
</button>
|
||||
|
||||
{/* Edit Columns dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="flex items-center gap-1.5 h-8 px-3 rounded-lg border border-slate-200 bg-white text-xs font-semibold text-primary hover:bg-slate-50 transition">
|
||||
<ViewColumnsIcon className="h-3.5 w-3.5" />
|
||||
Edit Columns
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuLabel className="text-xs">
|
||||
Optional Columns
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{OPTIONAL_COLUMN_IDS.map((colId) => {
|
||||
const isVisible = columnVisibility[colId] !== false;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={colId}
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
setColumnVisibility((prev) => ({
|
||||
...prev,
|
||||
[colId]: !isVisible,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isVisible}
|
||||
readOnly
|
||||
className="h-3.5 w-3.5 accent-black"
|
||||
/>
|
||||
<span className="text-xs">
|
||||
{OPTIONAL_COLUMN_LABELS[colId]}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Export (stub) */}
|
||||
<button className="flex items-center gap-1.5 h-8 px-3 rounded-lg border border-slate-200 bg-slate-100 text-xs font-semibold text-primary hover:bg-slate-200 transition">
|
||||
<ArrowDownTrayIcon className="h-3.5 w-3.5" />
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick filters row */}
|
||||
<div className="flex items-center gap-2 mb-3 flex-wrap">
|
||||
<button
|
||||
onClick={() => setSidebarOpen((o) => !o)}
|
||||
className="flex items-center gap-1.5 h-9 px-3 rounded-md border border-gray-300 text-sm text-gray-600 hover:bg-gray-50 transition shrink-0"
|
||||
title={sidebarOpen ? "Hide filters" : "Show filters"}
|
||||
>
|
||||
<FunnelIcon className="h-4 w-4" />
|
||||
{sidebarOpen ? "Hide filters" : "Filters"}
|
||||
</button>
|
||||
|
||||
<QuickFilterDropdown
|
||||
label="Address"
|
||||
placeholder="Contains…"
|
||||
committedValue={committedAddress}
|
||||
isOpen={openFilter === "address"}
|
||||
onOpen={() => 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 && (
|
||||
<button
|
||||
onClick={clearAll}
|
||||
className="h-9 px-3 text-sm text-gray-500 hover:text-gray-700 underline"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body: sidebar + table */}
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex items-start">
|
||||
{/* Collapsible filter sidebar */}
|
||||
<div
|
||||
className={[
|
||||
"shrink-0 overflow-hidden transition-all duration-300 ease-in-out",
|
||||
"bg-white rounded-md",
|
||||
"shrink-0 overflow-hidden transition-all duration-300 ease-in-out rounded-xl mr-2",
|
||||
sidebarOpen
|
||||
? "w-72 opacity-100 border border-gray-200"
|
||||
? "w-72 opacity-100 border border-slate-100 bg-slate-50"
|
||||
: "w-0 opacity-0",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="w-72">
|
||||
<p className="px-3 pt-3 pb-0 text-xs font-semibold text-gray-500 uppercase tracking-wide">
|
||||
Filters
|
||||
<p className="px-4 pt-4 pb-0 text-[9px] font-black text-primary uppercase tracking-widest">
|
||||
Curate Selection
|
||||
</p>
|
||||
<PropertyFilters
|
||||
filterGroups={filterGroups}
|
||||
|
|
@ -318,19 +483,23 @@ export default function PropertyTable({
|
|||
</div>
|
||||
|
||||
{/* Table area */}
|
||||
<div className="flex-1 min-w-0 bg-white rounded-md border border-gray-200 relative">
|
||||
<div className="flex-1 min-w-0 bg-white rounded-xl border border-slate-100 shadow-sm relative">
|
||||
{isFetching && !isLoading && <LoadingOverlay />}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="p-6 text-gray-400">Loading properties…</div>
|
||||
<div className="p-6 text-slate-400 text-sm">
|
||||
Loading properties…
|
||||
</div>
|
||||
) : isError ? (
|
||||
<div className="p-6 text-red-500">Failed to load properties.</div>
|
||||
<div className="p-6 text-red-500 text-sm">
|
||||
Failed to load properties.
|
||||
</div>
|
||||
) : data.length === 0 && hasActiveFilters ? (
|
||||
<div className="p-10 text-center text-gray-500">
|
||||
<p>No properties match your filters.</p>
|
||||
<div className="p-10 text-center text-slate-500">
|
||||
<p className="text-sm">No properties match your filters.</p>
|
||||
<button
|
||||
onClick={clearAll}
|
||||
className="mt-3 text-sm text-black underline"
|
||||
className="mt-3 text-xs text-primary underline"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
|
|
@ -342,6 +511,12 @@ export default function PropertyTable({
|
|||
data={data}
|
||||
columns={columns}
|
||||
onDeleteProperty={(id) => setDeletePropertyId(id)}
|
||||
columnVisibility={columnVisibility}
|
||||
onColumnVisibilityChange={
|
||||
setColumnVisibility as (
|
||||
updater: Updater<VisibilityState>,
|
||||
) => void
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<TData> {
|
|||
columns: ColumnDef<TData, any>[];
|
||||
data: TData[];
|
||||
onDeleteProperty?: (propertyId: number) => void;
|
||||
columnVisibility: VisibilityState;
|
||||
onColumnVisibilityChange: (updater: Updater<VisibilityState>) => void;
|
||||
}
|
||||
|
||||
export default function DataTable<TData extends Record<string, any>>({
|
||||
data,
|
||||
columns,
|
||||
onDeleteProperty,
|
||||
columnVisibility,
|
||||
onColumnVisibilityChange,
|
||||
}: DataTableProps<TData>) {
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
|
|
@ -69,15 +61,6 @@ export default function DataTable<TData extends Record<string, any>>({
|
|||
pageIndex: 0,
|
||||
pageSize: 7,
|
||||
});
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||
() => {
|
||||
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<TData extends Record<string, any>>({
|
|||
onColumnFiltersChange: setColumnFilters,
|
||||
onGlobalFilterChange: setGlobalFilter,
|
||||
onPaginationChange: setPagination,
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onColumnVisibilityChange,
|
||||
|
||||
globalFilterFn: fuzzyFilter,
|
||||
|
||||
|
|
@ -110,48 +93,7 @@ export default function DataTable<TData extends Record<string, any>>({
|
|||
});
|
||||
|
||||
return (
|
||||
<div className="rounded-md">
|
||||
{/* Edit Columns toolbar */}
|
||||
<div className="flex justify-end px-4 py-2 border-b">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="text-xs gap-1.5">
|
||||
Edit Columns
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuLabel className="text-xs">
|
||||
Optional Columns
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{OPTIONAL_COLUMN_IDS.map((colId) => {
|
||||
const col = table.getColumn(colId);
|
||||
if (!col) return null;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={colId}
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
col.toggleVisibility();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={col.getIsVisible()}
|
||||
readOnly
|
||||
className="h-3.5 w-3.5 accent-black"
|
||||
/>
|
||||
<span className="text-xs">
|
||||
{OPTIONAL_COLUMN_LABELS[colId]}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue