feat(05-01): dashboard grid, clock widget, widget registry, store, and tests
- Install react-grid-layout@2.2.3 and react-resizable - Create widget-registry.tsx with all 4 widget types, WIDGET_CONSTRAINTS, WidgetProps - Create dashboard-api.ts with fetch/save layout and widget CRUD functions - Create dashboard-store.ts (Zustand, NO persist — D-05) with edit mode and auto-save on exit - Create DashboardGrid with react-grid-layout v2 Responsive, ResizeObserver width - Create ClockWidget using Intl.DateTimeFormat (no manual UTC offsets) - Create WidgetWrapper with drag handle and delete button in edit mode - Create EditModeToggle (pencil/checkmark), WidgetCatalogModal (2x2 grid) - Rewrite portal page.tsx as dashboard with grid, edit toggle, widget catalog - Add ResizeObserver polyfill in test setup, CSS mock support in vitest config - All 5 tests green (dashboard grid + clock widget) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Dashboard API client functions.
|
||||
* All calls use credentials: 'include' for cookie-based auth.
|
||||
*/
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
export async function fetchLayout(): Promise<Record<string, unknown>> {
|
||||
const res = await fetch(`${API_URL}/dashboard/layout`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch layout');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function saveLayout(
|
||||
layouts: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
const res = await fetch(`${API_URL}/dashboard/layout`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ layouts }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to save layout');
|
||||
}
|
||||
|
||||
export async function fetchWidgets(): Promise<
|
||||
Array<{ id: string; widgetType: string; config: Record<string, unknown> }>
|
||||
> {
|
||||
const res = await fetch(`${API_URL}/dashboard/widgets`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch widgets');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function addWidget(
|
||||
widgetType: string,
|
||||
): Promise<{ id: string; widgetType: string; config: Record<string, unknown> }> {
|
||||
const res = await fetch(`${API_URL}/dashboard/widgets`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ widgetType }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to add widget');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function removeWidget(id: string): Promise<void> {
|
||||
const res = await fetch(`${API_URL}/dashboard/widgets/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to remove widget');
|
||||
}
|
||||
|
||||
export async function updateWidgetConfig(
|
||||
id: string,
|
||||
config: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
const res = await fetch(`${API_URL}/dashboard/widgets/${id}/config`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ config }),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to update widget config');
|
||||
}
|
||||
Reference in New Issue
Block a user