'use client'; import { useState } from 'react'; import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, type DragEndEvent, } from '@dnd-kit/core'; import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { GripVertical, LayoutGrid } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { Switch } from '@/components/ui/switch'; import { cn } from '@/lib/utils'; import { useDashboardWidgets } from '@/hooks/use-dashboard-widgets'; import type { DashboardWidget } from './widget-registry'; /** * Combined visibility + reorder picker for the dashboard header. Two * sections in one modal: * * 1. "On dashboard" — visible widgets, each row with a drag handle * (reorder via dnd-kit single SortableContext, no buckets); flipping * a switch off moves the row to section 2. * 2. "Hidden" — widgets currently off; flipping a switch on appends to * the bottom of section 1. * * Both visibility toggles and order changes commit optimistically via * `useDashboardWidgets` so the dashboard reflows in the background and * the rep can keep editing. The "Rearrange" button on the header is * gone — order lives here too now, keeping all dashboard layout * controls in one place. */ export function CustomizeWidgetsMenu() { const [open, setOpen] = useState(false); const { allWidgets, visibleWidgets, visibility, setVisible, setAll, setOrder, resetToDefaults, isSaving, } = useDashboardWidgets(); const visibleCount = Object.values(visibility).filter(Boolean).length; const allVisible = visibleCount === allWidgets.length; const allHidden = visibleCount === 0; const matchesDefaults = allWidgets.every((w) => (visibility[w.id] ?? false) === w.defaultVisible); // Hidden = everything not currently rendered. Sorted by registry order // so it reads predictably (newly-added widgets appear at the bottom // until the rep explicitly enables them). const hidden = allWidgets.filter((w) => !visibility[w.id]); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), ); function onDragEnd(event: DragEndEvent) { const { active, over } = event; if (!over || active.id === over.id) return; const ids = visibleWidgets.map((w) => w.id); const oldIndex = ids.indexOf(String(active.id)); const newIndex = ids.indexOf(String(over.id)); if (oldIndex === -1 || newIndex === -1) return; setOrder(arrayMove(ids, oldIndex, newIndex)); } return ( Customize dashboard Drag a visible widget to change its position. Toggle the switch to show or hide. Hidden widgets leave no empty space - the layout reflows to fill the available width. {/* Toggle + reorder list. Capped at ~60vh with internal scroll so the modal doesn't push the action footer off-screen. */}
{visibleWidgets.length > 0 ? (
w.id)} strategy={verticalListSortingStrategy} >
    {visibleWidgets.map((w, idx) => ( setVisible(w.id, checked)} /> ))}
) : null} {hidden.length > 0 ? (
    {hidden.map((w) => ( setVisible(w.id, checked)} /> ))}
) : null}
{visibleCount} of {allWidgets.length} visible
); } function Section({ title, children }: { title: string; children: React.ReactNode }) { return (
{title}
{children}
); } function SortableVisibleRow({ widget, position, disabled, onToggle, }: { widget: DashboardWidget; position: number; disabled: boolean; onToggle: (checked: boolean) => void; }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: widget.id, }); return (
  • {position}
    {widget.label}

    {widget.description}

  • ); } function HiddenRow({ widget, disabled, onToggle, }: { widget: DashboardWidget; disabled: boolean; onToggle: (checked: boolean) => void; }) { return (
  • {widget.label}

    {widget.description}

  • ); }