2026-06-09 14:06:01 -07:00
|
|
|
import { useState, useMemo } from "react";
|
|
|
|
|
import {
|
|
|
|
|
Button,
|
|
|
|
|
IconButton,
|
|
|
|
|
Box,
|
|
|
|
|
Text,
|
|
|
|
|
Flex,
|
|
|
|
|
Grid,
|
|
|
|
|
Dialog,
|
|
|
|
|
Tabs,
|
|
|
|
|
Badge,
|
|
|
|
|
Menu,
|
|
|
|
|
HStack
|
|
|
|
|
} from "@chakra-ui/react";
|
|
|
|
|
import { Settings, Plus, LayoutGrid, Move, Save, X } from 'lucide-react';
|
2026-06-10 14:00:37 -07:00
|
|
|
import { useDashboardStore } from "../../store/useDashboardStore.jsx";
|
|
|
|
|
import { WIDGET_REGISTRY } from "../../constants/widgetRegistry.jsx";
|
2026-06-09 14:06:01 -07:00
|
|
|
|
|
|
|
|
export function SettingsButton() {
|
2026-06-10 14:00:37 -07:00
|
|
|
// 1. ☁Store connections
|
2026-06-09 14:06:01 -07:00
|
|
|
const isEditing = useDashboardStore(state => state.isEditing);
|
|
|
|
|
const enterEditMode = useDashboardStore(state => state.enterEditMode);
|
|
|
|
|
const saveEdit = useDashboardStore(state => state.saveEdit);
|
|
|
|
|
const cancelEdit = useDashboardStore(state => state.cancelEdit);
|
|
|
|
|
const addWidget = useDashboardStore(state => state.addWidget);
|
|
|
|
|
|
2026-06-10 14:00:37 -07:00
|
|
|
// 2. Local state to control the Widget Library popup independently of the dropdown
|
2026-06-09 14:06:01 -07:00
|
|
|
const [isLibraryOpen, setIsLibraryOpen] = useState(false);
|
|
|
|
|
|
2026-06-10 14:00:37 -07:00
|
|
|
// 3. Categorize widgets for the tabs
|
2026-06-09 14:06:01 -07:00
|
|
|
const categorizedWidgets = useMemo(() => {
|
|
|
|
|
return Object.entries(WIDGET_REGISTRY).reduce((acc, [id, widget]) => {
|
|
|
|
|
const cat = widget.category || 'General';
|
|
|
|
|
if (!acc[cat]) acc[cat] = [];
|
|
|
|
|
acc[cat].push({ id, ...widget });
|
|
|
|
|
return acc;
|
|
|
|
|
}, {});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const categories = Object.keys(categorizedWidgets);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* THE STATIC DROPDOWN MENU */}
|
|
|
|
|
<Menu.Root>
|
|
|
|
|
<Menu.Trigger asChild>
|
|
|
|
|
<IconButton
|
|
|
|
|
aria-label="Settings"
|
|
|
|
|
bg="vdap.darkGreen" // Or #1a2e24
|
|
|
|
|
color="white"
|
|
|
|
|
size="sm"
|
|
|
|
|
borderRadius="md"
|
|
|
|
|
_hover={{ bg: "vdap.darkGreenHover" }} // Or #111f18
|
|
|
|
|
>
|
|
|
|
|
<Settings size={18} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Menu.Trigger>
|
|
|
|
|
|
|
|
|
|
<Menu.Content bg="white" boxShadow="lg" borderRadius="md" p={1} zIndex="dropdown">
|
|
|
|
|
|
|
|
|
|
{/* Dynamic Menu Options: What shows up depends on isEditing */}
|
|
|
|
|
{isEditing ? (
|
|
|
|
|
<>
|
|
|
|
|
<Menu.Item onClick={saveEdit} color="green.600" fontWeight="bold" cursor="pointer" _hover={{ bg: "green.50" }}>
|
|
|
|
|
<Save size={16} style={{ marginRight: '8px' }} /> Save Layout
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
<Menu.Item onClick={cancelEdit} color="red.500" cursor="pointer" _hover={{ bg: "red.50" }}>
|
|
|
|
|
<X size={16} style={{ marginRight: '8px' }} /> Discard Changes
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<Menu.Item onClick={enterEditMode} cursor="pointer" _hover={{ bg: "gray.100" }}>
|
|
|
|
|
<Move size={16} style={{ marginRight: '8px' }} /> Edit Layout
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Menu.Separator my={1} borderColor="gray.200" />
|
|
|
|
|
|
|
|
|
|
<Menu.Item onClick={() => setIsLibraryOpen(true)} cursor="pointer" _hover={{ bg: "gray.100" }}>
|
|
|
|
|
<LayoutGrid size={16} style={{ marginRight: '8px' }} /> Widget Library
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
|
|
|
|
</Menu.Content>
|
|
|
|
|
</Menu.Root>
|
|
|
|
|
|
|
|
|
|
{/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */}
|
|
|
|
|
<Dialog.Root open={isLibraryOpen} onOpenChange={(e) => setIsLibraryOpen(e.open)} size="xl" placement="center">
|
|
|
|
|
<Dialog.Backdrop bg="blackAlpha.600" />
|
|
|
|
|
<Dialog.Positioner>
|
|
|
|
|
<Dialog.Content bg="gray.50" borderRadius="xl" overflow="hidden" boxShadow="xl" maxW="800px">
|
|
|
|
|
|
|
|
|
|
<Dialog.Header bg="vdap.darkGreen" color="white" py={4}>
|
|
|
|
|
<Dialog.Title fontSize="xl" fontWeight="bold">Add Dashboard Widgets</Dialog.Title>
|
|
|
|
|
</Dialog.Header>
|
|
|
|
|
|
|
|
|
|
<Dialog.Body p={0}>
|
|
|
|
|
<Tabs.Root defaultValue={categories[0]} variant="enclosed" colorScheme="blue">
|
|
|
|
|
<Tabs.List bg="white" px={4} pt={4} borderBottom="1px solid" borderColor="gray.200">
|
|
|
|
|
{categories.map(cat => (
|
|
|
|
|
<Tabs.Trigger key={cat} value={cat} pb={2}>{cat}</Tabs.Trigger>
|
|
|
|
|
))}
|
|
|
|
|
</Tabs.List>
|
|
|
|
|
|
|
|
|
|
{categories.map(cat => (
|
|
|
|
|
<Tabs.Content key={cat} value={cat} p={6}>
|
|
|
|
|
<Grid templateColumns="repeat(auto-fill, minmax(300px, 1fr))" gap={4}>
|
|
|
|
|
{categorizedWidgets[cat].map(widget => (
|
|
|
|
|
<Flex key={widget.id} direction="column" bg="white" p={4} borderRadius="md" border="1px solid" borderColor="gray.200" boxShadow="sm">
|
|
|
|
|
<Text fontWeight="bold" fontSize="md" mb={1}>{widget.name}</Text>
|
|
|
|
|
<Text fontSize="sm" color="gray.500" mb={4} flex="1">{widget.description}</Text>
|
|
|
|
|
|
|
|
|
|
<Box borderTop="1px solid" borderColor="gray.100" pt={3}>
|
|
|
|
|
<Text fontSize="xs" fontWeight="bold" color="gray.400" textTransform="uppercase" mb={2}>Add to Grid:</Text>
|
|
|
|
|
<HStack gap={2} flexWrap="wrap">
|
|
|
|
|
{Object.keys(widget.allowedSizes).map(sizeKey => (
|
|
|
|
|
<Badge
|
|
|
|
|
key={sizeKey}
|
|
|
|
|
as="button"
|
|
|
|
|
onClick={() => addWidget(widget.id, sizeKey)}
|
|
|
|
|
colorScheme="gray"
|
|
|
|
|
variant="subtle"
|
|
|
|
|
px={3}
|
|
|
|
|
py={1}
|
|
|
|
|
borderRadius="full"
|
|
|
|
|
cursor="pointer"
|
|
|
|
|
_hover={{ bg: "blue.50", color: "blue.600", transform: "scale(1.05)" }}
|
|
|
|
|
transition="all 0.2s"
|
|
|
|
|
>
|
|
|
|
|
<Plus size={12} style={{ display: 'inline', marginRight: '4px' }}/>
|
|
|
|
|
{sizeKey.charAt(0).toUpperCase() + sizeKey.slice(1)}
|
|
|
|
|
</Badge>
|
|
|
|
|
))}
|
|
|
|
|
</HStack>
|
|
|
|
|
</Box>
|
|
|
|
|
</Flex>
|
|
|
|
|
))}
|
|
|
|
|
</Grid>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
))}
|
|
|
|
|
</Tabs.Root>
|
|
|
|
|
</Dialog.Body>
|
|
|
|
|
<Dialog.CloseTrigger position="absolute" top={3} right={3} color="white" />
|
|
|
|
|
</Dialog.Content>
|
|
|
|
|
</Dialog.Positioner>
|
|
|
|
|
</Dialog.Root>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|