2026-06-09 14:06:01 -07:00
|
|
|
import { create } from 'zustand';
|
|
|
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
|
import { WIDGET_REGISTRY } from '../constants/widgetRegistry.jsx';
|
|
|
|
|
|
|
|
|
|
export const useDashboardStore = create((set, get) => ({
|
|
|
|
|
// ==========================================
|
|
|
|
|
// 1. STATE (The Data)
|
|
|
|
|
// ==========================================
|
|
|
|
|
isEditing: false,
|
|
|
|
|
widgets: [], // The master array of what is currently on the screen
|
|
|
|
|
backupWidgets: [], // Holds the layout snapshot for the "Cancel" button
|
|
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
// 2. ACTIONS (The Logic)
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
|
|
enterEditMode: () => set({
|
|
|
|
|
isEditing: true,
|
|
|
|
|
backupWidgets: get().widgets // 📸 Take a snapshot of current positions
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
cancelEdit: () => set({
|
|
|
|
|
isEditing: false,
|
|
|
|
|
widgets: get().backupWidgets // ⏪ Restore snapshot, instantly undoing any drags
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
saveEdit: async () => {
|
|
|
|
|
const payload = get().widgets;
|
|
|
|
|
|
|
|
|
|
// 💾 This is perfectly formatted for your backend DB right now
|
|
|
|
|
console.log("Saving dashboard to API:", JSON.stringify(payload, null, 2));
|
|
|
|
|
|
|
|
|
|
// Example: await fetch('/api/user/dashboard', { method: 'POST', body: JSON.stringify(payload) });
|
|
|
|
|
|
|
|
|
|
set({ isEditing: false, backupWidgets: [] });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
addWidget: (widgetId, sizeKey) => {
|
|
|
|
|
// Look up the exact rules for this widget from your constant
|
|
|
|
|
const registryEntry = WIDGET_REGISTRY[widgetId];
|
|
|
|
|
|
|
|
|
|
if (!registryEntry || !registryEntry.allowedSizes[sizeKey]) {
|
|
|
|
|
console.error(`Invalid widget ID or Size: ${widgetId} - ${sizeKey}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dimensions = registryEntry.allowedSizes[sizeKey];
|
2026-06-16 09:48:18 -07:00
|
|
|
const widgets = get().widgets;
|
2026-06-09 14:06:01 -07:00
|
|
|
|
2026-06-16 09:48:18 -07:00
|
|
|
// Default to spawning on the far left on a new row
|
|
|
|
|
let startX = 0;
|
|
|
|
|
let startY = Infinity;
|
|
|
|
|
|
|
|
|
|
if (widgets.length > 0) {
|
|
|
|
|
// 1. Find the lowest 'y' value (the start of the bottom row)
|
|
|
|
|
const bottomRowY = Math.max(...widgets.map(w => w.y));
|
|
|
|
|
|
|
|
|
|
// 2. Grab all the widgets that are sitting on that exact row
|
|
|
|
|
const bottomRowWidgets = widgets.filter(w => w.y === bottomRowY);
|
|
|
|
|
|
|
|
|
|
// 3. Find the right-most edge of those widgets (x position + width)
|
|
|
|
|
const rightmostEdge = Math.max(...bottomRowWidgets.map(w => w.x + w.w));
|
|
|
|
|
|
|
|
|
|
// 4. If the right-most edge PLUS the new widget's width fits in 12 columns...
|
|
|
|
|
if (rightmostEdge + dimensions.w <= 12) {
|
|
|
|
|
startX = rightmostEdge; // ...tuck it right next to the last widget!
|
|
|
|
|
startY = bottomRowY; // ...and keep it on the same row.
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-09 14:06:01 -07:00
|
|
|
const newWidget = {
|
|
|
|
|
id: uuid(),
|
|
|
|
|
type: widgetId,
|
|
|
|
|
size: sizeKey,
|
2026-06-16 09:48:18 -07:00
|
|
|
x: startX,
|
|
|
|
|
y: startY,
|
2026-06-09 14:06:01 -07:00
|
|
|
w: dimensions.w,
|
|
|
|
|
h: dimensions.h
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
set((state) => ({ widgets: [...state.widgets, newWidget] }));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
removeWidget: (id) => set((state) => ({
|
|
|
|
|
widgets: state.widgets.filter(w => w.id !== id)
|
|
|
|
|
})),
|
|
|
|
|
|
|
|
|
|
// Called automatically by React-Grid-Layout when the user lets go of the mouse
|
|
|
|
|
updateLayoutPositions: (rglLayout) => {
|
|
|
|
|
set((state) => ({
|
|
|
|
|
widgets: state.widgets.map(widget => {
|
|
|
|
|
// Find where RGL says this widget currently is
|
|
|
|
|
const gridItem = rglLayout.find(l => l.i === widget.id);
|
|
|
|
|
|
|
|
|
|
// Only update X and Y. We completely ignore W and H so users can't resize!
|
|
|
|
|
return gridItem ? { ...widget, x: gridItem.x, y: gridItem.y } : widget;
|
|
|
|
|
})
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}));
|