Files
pn-new-crm/src/components/shared/column-picker.tsx
Matt 19622985b5 fix(layout): mobile UX cleanup + interest-stage legend popover
Mobile UX:
- Hide ColumnPicker on `< sm` viewports (cards, no columns to toggle).
- Hide kanban toggle in interest list on mobile and snap viewMode back
  to 'table' if the persisted choice was 'board'.
- Drop dead "Inbox" link from the More-sheet (email/IMAP feature is
  deferred per sidebar.tsx note).
- Repoint Notifications nav from `/notifications` (no page.tsx — 404)
  to `/notifications/preferences` and re-label as "Notification
  preferences" (the bell stays the surface for actual notifications).
- Hide Website Analytics on both desktop sidebar and mobile More-sheet
  when Umami isn't configured for the port (`useUmamiActive()`).

Interests:
- New `<StageLegend>` popover button in the filter row decodes the
  card stripe colours to pipeline stage names, kept in sync with
  `STAGE_DOT` automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 04:11:01 +02:00

137 lines
4.2 KiB
TypeScript

'use client';
import { Columns3, Check, Bookmark } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
export interface ColumnPickerOption {
/** Stable ID matching the column's `id` field in the table. */
id: string;
/** Human-readable label shown in the dropdown menu. */
label: string;
/**
* When true, the column can't be toggled off (e.g. Name + actions).
* It still appears in the menu but with a disabled checkmark.
*/
alwaysVisible?: boolean;
}
/**
* Dropdown menu for toggling table column visibility. Lives next to the
* filter bar — single source of truth for which columns the current
* user wants to see in this table. Persistence is handled by the
* parent (typically via `useTablePreferences`).
*/
export function ColumnPicker({
columns,
hidden,
onChange,
onSaveView,
}: {
columns: ColumnPickerOption[];
hidden: string[];
onChange: (hidden: string[]) => void;
/**
* Optional callback. When provided, a "Save current view" item is
* appended to the menu — folds the save-view affordance into the
* column picker instead of a separate top-level button.
*/
onSaveView?: () => void;
}) {
const hiddenSet = new Set(hidden);
function toggle(id: string) {
const next = new Set(hiddenSet);
if (next.has(id)) next.delete(id);
else next.add(id);
onChange(Array.from(next));
}
function showAll() {
onChange([]);
}
// The "All visible" affordance is only useful when something is
// hidden — a no-op button is noise.
const canShowAll = hidden.some((id) =>
columns.some((col) => col.id === id && !col.alwaysVisible),
);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
{/* Hide entirely on mobile — small viewports render the list as
cards (no columns to pick). The desktop table view shows
this trigger from the `sm:` breakpoint up. */}
<Button variant="outline" size="sm" className="hidden sm:inline-flex gap-1.5 h-8">
<Columns3 className="h-3.5 w-3.5" />
<span>Columns</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel className="text-xs text-muted-foreground">
Show / hide columns
</DropdownMenuLabel>
<DropdownMenuSeparator />
{columns.map((col) => {
const isVisible = !hiddenSet.has(col.id);
return (
<DropdownMenuItem
key={col.id}
disabled={col.alwaysVisible}
onSelect={(e) => {
// Keep the menu open while toggling so the user can
// flip multiple columns in one pass.
e.preventDefault();
if (col.alwaysVisible) return;
toggle(col.id);
}}
className="flex items-center gap-2"
>
<span
aria-hidden
className={`flex h-4 w-4 items-center justify-center rounded-sm border ${
isVisible ? 'bg-primary border-primary text-primary-foreground' : 'border-border'
}`}
>
{isVisible && <Check className="h-3 w-3" />}
</span>
<span className="flex-1">{col.label}</span>
{col.alwaysVisible && (
<span className="text-[10px] uppercase tracking-wide text-muted-foreground">
always
</span>
)}
</DropdownMenuItem>
);
})}
{canShowAll && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={showAll} className="text-xs text-muted-foreground">
Show all columns
</DropdownMenuItem>
</>
)}
{onSaveView && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onSaveView}>
<Bookmark className="mr-2 h-3.5 w-3.5" />
Save current view
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
}