Skip to content

Commit

Permalink
[ui] Move column selector to tabletoolbar and change it to combobox t…
Browse files Browse the repository at this point in the history
…o handle more columns
  • Loading branch information
hhssb committed Dec 16, 2024
1 parent 16353f4 commit ba517a3
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 87 deletions.
34 changes: 34 additions & 0 deletions app/src/app/search/components/column-selector-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";
import { Button } from "@/components/ui/button";
import { Settings2 } from "lucide-react";
import { useTableColumns } from "../table-columns";
import { ColumnSelector } from "./column-selector";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export const ColumnSelectorButton = () => {
const { columns, toggleColumn, profiles, setProfile } = useTableColumns();
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="secondary"
className="hidden lg:flex items-center ml-auto space-x-2 h-9 p-2"
>
<Settings2 size={17} />
<span>Column Settings</span>
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-[300px] p-0">
<ColumnSelector
columns={columns}
onToggleColumn={toggleColumn}
profiles={profiles}
setProfile={setProfile}
/>
</PopoverContent>
</Popover>
);
};
135 changes: 82 additions & 53 deletions app/src/app/search/components/column-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,100 @@
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuCheckboxItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Settings2 } from "lucide-react";
import { Check } from "lucide-react";
import { ColumnProfile, TableColumn } from "../search.d";
import { isEqual } from "moderndash";

import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
interface ColumnSelectorProps {
columns: TableColumn[];
onToggleColumn: (column: TableColumn) => void;
profiles: Record<ColumnProfile, TableColumn[]>;
setProfile: (profile: ColumnProfile) => void;
}

export function ColumnSelector({
columns,
onToggleColumn,
profiles,
setProfile,
}: ColumnSelectorProps) {
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<Settings2 className="h-4 w-4" />
<span className="sr-only">Column Settings</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[200px]">
<DropdownMenuLabel>Toggle Columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{columns.map((column) => (
<DropdownMenuCheckboxItem
key={`column-selector-${column.code}${column.type === "Adaptable" ? "-" + column.stat_code : ""}`}
checked={column.type == "Always" ? true : column.visible}
onCheckedChange={() => onToggleColumn(column)}
disabled={column.type == "Always" ? true : false}
<Command>
<CommandInput placeholder="Columns" />
<CommandList>
<CommandGroup heading="Toggle Columns">
{columns.map((column) => (
<CommandItem
key={`column-selector-${column.code}${column.type === "Adaptable" ? "-" + column.stat_code : ""}`}
value={column.label}
onSelect={() => onToggleColumn(column)}
disabled={column.type == "Always" ? true : false}
className="space-x-2"
>
<Check
size={14}
className={
column.type === "Always" || column.visible
? "opacity-100"
: "opacity-0"
}
/>
<span>{column.label}</span>
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Profiles">
<CommandItem
onSelect={() => setProfile("Brief")}
value="Brief"
className="space-x-2"
>
<Check
size={14}
className={
profiles && isEqual(columns, profiles["Brief"])
? "opacity-100"
: "opacity-0"
}
/>
<span>Brief</span>
</CommandItem>
<CommandItem
onSelect={() => setProfile("Regular")}
value="Regular"
className="space-x-2"
>
<Check
size={14}
className={
profiles && isEqual(columns, profiles["Regular"])
? "opacity-100"
: "opacity-0"
}
/>
<span>Regular</span>
</CommandItem>
<CommandItem
onSelect={() => setProfile("Detailed")}
value="Detailed"
className="space-x-2"
>
{column.label}
</DropdownMenuCheckboxItem>
))}
<DropdownMenuSeparator />
<DropdownMenuLabel>Profiles</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={profiles && isEqual(columns, profiles["Brief"])}
onCheckedChange={() => setProfile("Brief")}
>
Brief
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={profiles && isEqual(columns, profiles["Regular"])}
onCheckedChange={() => setProfile("Regular")}
>
Regular
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={profiles && isEqual(columns, profiles["Detailed"])}
onCheckedChange={() => setProfile("Detailed")}
>
Detailed
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
<Check
size={14}
className={
profiles && isEqual(columns, profiles["Detailed"])
? "opacity-100"
: "opacity-0"
}
/>
<span>Detailed</span>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
);
}
14 changes: 3 additions & 11 deletions app/src/app/search/components/statistical-unit-table-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useBaseData } from "@/app/BaseDataClient";
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useTableColumns } from "../table-columns";
import { ColumnSelector } from "./column-selector";

interface StatisticalUnitTableHeaderProps {
regionLevel: number;
Expand All @@ -18,7 +17,8 @@ export function StatisticalUnitTableHeader({
maxRegionLevel,
}: StatisticalUnitTableHeaderProps) {
const { statDefinitions, externalIdentTypes } = useBaseData();
const { columns, visibleColumns, toggleColumn, profiles, setProfile, headerRowSuffix, headerCellSuffix } = useTableColumns();
const { visibleColumns, headerRowSuffix, headerCellSuffix } =
useTableColumns();

return (
<TableHeader className="bg-gray-50">
Expand Down Expand Up @@ -134,15 +134,7 @@ export function StatisticalUnitTableHeader({
);
}
})}
<TableHead className="py-2 p-1 text-right hidden lg:table-cell" key="header-column-selector">
<ColumnSelector
columns={columns}
onToggleColumn={toggleColumn}
profiles={profiles}
setProfile={setProfile}
/>
</TableHead>
<TableHead className="lg:hidden" />
<TableHead />
</TableRow>
</TableHeader>
);
Expand Down
18 changes: 11 additions & 7 deletions app/src/app/search/components/table-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import { ResetFilterButton } from "@/app/search/components/reset-filter-button";
import { FilterWrapper } from "./filter-wrapper";
import { IURLSearchParamsDict } from "@/lib/url-search-params-dict";
import DataSourceFilter from "../filters/data-source/data-source-filter";
import { ColumnSelectorButton } from "./column-selector-button";

export default function TableToolbar({ initialUrlSearchParamsDict }: IURLSearchParamsDict) {
export default function TableToolbar({
initialUrlSearchParamsDict,
}: IURLSearchParamsDict) {
return (
<div className="flex flex-wrap items-center p-1 lg:p-0 [&>*]:mb-2 [&>*]:mx-1 w-full">
<div className="flex flex-wrap items-center gap-2 mb-4 p-1 lg:p-0 w-full">
<FilterWrapper columnCode="name">
<FullTextSearchFilter/>
<ExternalIdentFilter/>
Expand All @@ -32,20 +35,21 @@ export default function TableToolbar({ initialUrlSearchParamsDict }: IURLSearchP
<RegionFilter/>
</Suspense>
</FilterWrapper>
<LegalFormFilter/>
<LegalFormFilter />
<FilterWrapper columnCode="activity">
<Suspense fallback={<FilterSkeleton title="Activity Category" />}>
<ActivityCategoryFilter/>
<ActivityCategoryFilter />
</Suspense>
</FilterWrapper>
<FilterWrapper columnCode="data_sources">
<Suspense fallback={<FilterSkeleton title="Data Source" />}>
<DataSourceFilter/>
<DataSourceFilter />
</Suspense>
</FilterWrapper>
<StatisticalVariablesFilter/>
<InvalidCodesFilter/>
<StatisticalVariablesFilter />
<InvalidCodesFilter />
<ResetFilterButton />
<ColumnSelectorButton />
</div>
);
}
55 changes: 39 additions & 16 deletions app/src/app/search/table-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,45 +129,68 @@ export function TableColumnsProvider({ children }: { children: ReactNode }) {

const [columns, setColumns] = useState<TableColumns>([]);

// Update columns and keep user preferences
const updateColumnsWithPreferences = useCallback(
(userColumns: AdaptableTableColumn[] = []): TableColumns => {
return available_columns.map((col) => {
if (col.type === "Adaptable") {
const userColumn = userColumns.find(
(userCol) =>
userCol.type === "Adaptable" &&
userCol.code === col.code &&
userCol.stat_code === col.stat_code
);
return { ...col, visible: userColumn?.visible ?? col.visible };
}
return col;
});
},
[available_columns]
);

useEffect(() => {
if (available_columns.length === 0) {
return; // Wait for default columns to be available
const fallbackColumns = updateColumnsWithPreferences();
if (fallbackColumns.length === 0) {
return;
}

// Try loading from localStorage first
try {
const saved = localStorage.getItem(COLUMN_LOCALSTORAGE_NAME);
if (saved) {
try {
const state = JSON.parse(saved);
setColumns(state);
const savedColumns = saved ? JSON.parse(saved) : [];
const currentColumns = updateColumnsWithPreferences(savedColumns);
setColumns(currentColumns);
if (!isEqual(savedColumns, currentColumns)) {
localStorage.setItem(
COLUMN_LOCALSTORAGE_NAME,
JSON.stringify(currentColumns)
);
}
} catch (e) {
console.error("Failed to parse stored columns state:", e);
localStorage.removeItem(COLUMN_LOCALSTORAGE_NAME);
setColumns(available_columns); // Fall back to defaults on error
}
return;
setColumns(fallbackColumns);
}

// Fall back to default columns if no localStorage data
setColumns(available_columns);

// Listen for changes in other tabs
const handleStorageChange = (e: StorageEvent) => {
if (e.key === COLUMN_LOCALSTORAGE_NAME && e.newValue) {
try {
const newState = JSON.parse(e.newValue);
setColumns(newState);
const updatedPreferences = JSON.parse(e.newValue);
const updatedColumns =
updateColumnsWithPreferences(updatedPreferences);
setColumns(updatedColumns);
} catch (e) {
console.error("Failed to parse columns state from storage event:", e);
localStorage.removeItem(COLUMN_LOCALSTORAGE_NAME);
setColumns(available_columns); // Fall back to defaults on error
setColumns(fallbackColumns);
}
}
};

window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, [available_columns]);
}, [updateColumnsWithPreferences]);

const toggleColumn = useCallback(
(column: TableColumn) => {
Expand Down

0 comments on commit ba517a3

Please sign in to comment.