Added LDAP config and auth

This commit is contained in:
2026-07-08 15:05:50 -07:00
parent 95dbd26ca8
commit 5447479d1b
13 changed files with 736 additions and 214 deletions
+132 -80
View File
@@ -1,94 +1,146 @@
import { Box, Text, Flex, IconButton } from "@chakra-ui/react";
import { Box, Text, Flex, IconButton, Spinner } from "@chakra-ui/react";
import { X } from "lucide-react";
import { Responsive, WidthProvider } from "react-grid-layout";
import { useDashboardStore } from "@/store/useDashboardStore.jsx";
import { Suspense } from 'react';
// 2. Import registry!
import { WIDGET_REGISTRY } from '@/constants/widgetRegistry.jsx';
// React-Grid-Layout requires this wrapper to automatically calculate screen width
const ResponsiveGridLayout = WidthProvider(Responsive);
export default function MyDashboard() {
// 1. ☁️ Connect to the Cloud (Zustand)
const widgets = useDashboardStore(state => state.widgets);
const isEditing = useDashboardStore(state => state.isEditing);
const updateLayoutPositions = useDashboardStore(state => state.updateLayoutPositions);
const removeWidget = useDashboardStore(state => state.removeWidget);
// 1. ☁️ Connect to the Cloud (Zustand)
const widgets = useDashboardStore(state => state.widgets);
const isEditing = useDashboardStore(state => state.isEditing);
const updateLayoutPositions = useDashboardStore(state => state.updateLayoutPositions);
const removeWidget = useDashboardStore(state => state.removeWidget);
// 2. 🛠️ Format the data for RGL
// RGL expects a very specific array format for its layout prop
const rglLayout = widgets.map(w => ({
i: w.id,
x: w.x,
y: w.y,
w: w.w,
h: w.h,
isResizable: false // 🚫 The magic lock! No resizing allowed globally.
}));
// 2. 🛠️ Format the data for RGL
// RGL expects a very specific array format for its layout prop
const rglLayout = widgets.map(w => ({
i: w.id,
x: w.x,
y: w.y,
w: w.w,
h: w.h,
isResizable: false // 🚫 The magic lock! No resizing allowed globally.
}));
// 3. THE COMPONENT MAPPER
const renderWidget = (widget) => {
const registryEntry = WIDGET_REGISTRY[widget.type];
if (registryEntry && registryEntry.component) {
const Component = registryEntry.component;
return (
// The "Frame" that holds both the Title and the Chart
<Flex direction="column" height="100%" width="100%" bg="gray.800">
{/* 1. THE TITLE BAR (Dynamically pulled from registry!) */}
<Flex
justify="space-between"
align="center"
px={3}
py={2}
// borderBottom="1px solid"
// borderColor="gray.700"
>
<Text color="gray.100" fontWeight="semibold" fontSize="md" isTruncated>
{registryEntry.name}
</Text>
{/* (Optional) This is exactly where the ellipsis/settings icon will go later! */}
<Box w="20px" h="20px" />
</Flex>
{/* 2. THE CHART CANVAS */}
<Box flex="1" overflow="hidden" position="relative" p={1}>
<Component />
</Box>
return (
<Box width="100%" height="100%">
<ResponsiveGridLayout
className="layout"
layouts={{ lg: rglLayout }} // We feed it the formatted layout map
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} // Our 12-column master grid
rowHeight={30}
isDraggable={isEditing} // Only allows dragging when your Settings tools toggle this
isResizable={false} // Double lock-down
onLayoutChange={(newLayout) => updateLayoutPositions(newLayout)} // Fires when user drops a widget
compactType="vertical" // Auto-packs widgets to the top
>
{/* 3. 🎨 Paint the Widgets */}
{widgets.map(widget => (
<Box
key={widget.id}
bg="white"
borderRadius="md"
boxShadow={isEditing ? "outline" : "sm"} // Visual cue when editing
border={isEditing ? "2px dashed gray" : "1px solid"}
borderColor={isEditing ? "gray.400" : "gray.200"}
overflow="hidden"
cursor={isEditing ? "grab" : "default"}
position="relative" // 3. REQUIRED so the absolute button stays inside this box
>
{/* 4. THE DELETE BUTTON */}
{isEditing && (
<IconButton
aria-label="Remove widget"
position="absolute"
top={2}
right={2}
size="xs"
bg="white"
color="gray.400"
border="1px solid"
borderColor="gray.200"
_hover={{ bg: "red.50", color: "red.500", borderColor: "red.200" }}
zIndex={2}
onClick={() => removeWidget(widget.id)}
onMouseDown={(e) => e.stopPropagation()} // 🚫 Stops RGL from grabbing the widget
onTouchStart={(e) => e.stopPropagation()} // 🚫 Same protection for mobile touches
>
<X size={14} />
</IconButton>
)}
{/* 🚧 Temporary Placeholder!
Eventually, we will swap this block out for a dynamic component loader
that looks at widget.type and loads the correct graph or table.
*/}
<Flex width="100%" height="100%" align="center" justify="center" direction="column">
<Text fontWeight="bold" color="gray.600">{widget.type}</Text>
<Text fontSize="sm" color="gray.400">Size: {widget.size}</Text>
</Flex>
</Box>
))}
</ResponsiveGridLayout>
);
}
{/* Empty State Helper */}
{widgets.length === 0 && (
<Flex width="100%" height="200px" align="center" justify="center">
<Text color="gray.400">Your dashboard is empty. Click Settings to add widgets.</Text>
// Fallback if someone asks for a widget that doesn't exist
return (
<Flex width="100%" height="100%" align="center" justify="center" direction="column" bg="gray.800">
<Text fontWeight="bold" color="red.400">Error</Text>
<Text fontSize="sm" color="gray.400">Widget '{widget.type}' not found.</Text>
</Flex>
)}
</Box>
);
);
};
return (
<Box width="100%" height="100%">
<ResponsiveGridLayout
className="layout"
layouts={{ lg: rglLayout }} // We feed it the formatted layout map
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} // Our 12-column master grid
rowHeight={30}
isDraggable={isEditing} // Only allows dragging when your Settings tools toggle this
isResizable={false} // Double lock-down
onLayoutChange={(newLayout) => updateLayoutPositions(newLayout)} // Fires when user drops a widget
compactType="vertical" // Auto-packs widgets to the top
>
{/* 3. 🎨 Paint the Widgets */}
{widgets.map(widget => (
<Box
key={widget.id}
bg="white"
borderRadius="md"
boxShadow={isEditing ? "outline" : "sm"} // Visual cue when editing
border={isEditing ? "2px dashed gray" : "1px solid"}
borderColor={isEditing ? "gray.400" : "gray.200"}
overflow="hidden"
cursor={isEditing ? "grab" : "default"}
position="relative" // 3. REQUIRED so the absolute button stays inside this box
>
{/* 4. THE DELETE BUTTON */}
{isEditing && (
<IconButton
aria-label="Remove widget"
position="absolute"
top={2}
right={2}
size="xs"
bg="white"
color="gray.400"
border="1px solid"
borderColor="gray.200"
_hover={{ bg: "red.50", color: "red.500", borderColor: "red.200" }}
zIndex={2}
onClick={() => removeWidget(widget.id)}
onMouseDown={(e) => e.stopPropagation()} // 🚫 Stops RGL from grabbing the widget
onTouchStart={(e) => e.stopPropagation()} // 🚫 Same protection for mobile touches
>
<X size={14} />
</IconButton>
)}
{/* 4. SUSPENSE BOUNDARY wrapping the render function */}
<Suspense fallback={
<Flex w="100%" h="100%" align="center" justify="center" bg="gray.800">
<Spinner size="md" color="blue.400" thickness="3px" />
</Flex>
}>
{renderWidget(widget)}
</Suspense>
</Box>
))}
</ResponsiveGridLayout>
{/* Empty State Helper */}
{widgets.length === 0 && (
<Flex width="100%" height="200px" align="center" justify="center">
<Text color="gray.400">Your dashboard is empty. Click Settings to add widgets.</Text>
</Flex>
)}
</Box>
);
}