feat(dashboard): expand grid to 16 columns with auto-migration for saved 12-col layouts
This commit is contained in:
@@ -162,7 +162,10 @@ export function normalizeDashboardLayout(input: unknown): DashboardLayoutConfig
|
||||
return createDefaultDashboardLayout();
|
||||
}
|
||||
|
||||
const gridCols = clamp(Math.max(1, toInt(input.gridCols) ?? DASHBOARD_GRID_COLUMNS), 1, 24);
|
||||
const savedGridCols = clamp(Math.max(1, toInt(input.gridCols) ?? DASHBOARD_GRID_COLUMNS), 1, 24);
|
||||
// Migrate layouts saved with 12 columns to the current 16-column grid.
|
||||
const needsColumnMigration = savedGridCols === 12 && DASHBOARD_GRID_COLUMNS === 16;
|
||||
const gridCols = needsColumnMigration ? DASHBOARD_GRID_COLUMNS : savedGridCols;
|
||||
const rawWidgets = Array.isArray(input.widgets) ? input.widgets : [];
|
||||
const widgets: DashboardWidgetInstance[] = [];
|
||||
const seenIds = new Set<string>();
|
||||
@@ -184,14 +187,20 @@ export function normalizeDashboardLayout(input: unknown): DashboardLayoutConfig
|
||||
}
|
||||
seenIds.add(id);
|
||||
|
||||
// Scale x and w when migrating from a 12-column to 16-column layout.
|
||||
const scaleCol = (v: number) => Math.round(v * 16 / 12);
|
||||
const rawX = typeof rawWidget.x === "number" ? (needsColumnMigration ? scaleCol(rawWidget.x) : rawWidget.x) : undefined;
|
||||
const rawW = typeof rawWidget.w === "number" ? (needsColumnMigration ? scaleCol(rawWidget.w) : rawWidget.w) : undefined;
|
||||
const rawMinW = typeof rawWidget.minW === "number" ? (needsColumnMigration ? scaleCol(rawWidget.minW) : rawWidget.minW) : undefined;
|
||||
|
||||
const widgetOptions: Parameters<typeof createDashboardWidget<typeof type>>[1] = {
|
||||
id,
|
||||
...(typeof rawWidget.title === "string" ? { title: rawWidget.title } : {}),
|
||||
...(typeof rawWidget.x === "number" ? { x: rawWidget.x } : {}),
|
||||
...(rawX !== undefined ? { x: rawX } : {}),
|
||||
...(typeof rawWidget.y === "number" ? { y: rawWidget.y } : {}),
|
||||
...(typeof rawWidget.w === "number" ? { w: rawWidget.w } : {}),
|
||||
...(rawW !== undefined ? { w: rawW } : {}),
|
||||
...(typeof rawWidget.h === "number" ? { h: rawWidget.h } : {}),
|
||||
...(typeof rawWidget.minW === "number" ? { minW: rawWidget.minW } : {}),
|
||||
...(rawMinW !== undefined ? { minW: rawMinW } : {}),
|
||||
...(typeof rawWidget.minH === "number" ? { minH: rawWidget.minH } : {}),
|
||||
...(rawWidget.config !== undefined ? { config: rawWidget.config } : {}),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ProjectStatus } from "./enums.js";
|
||||
|
||||
export const DASHBOARD_LAYOUT_VERSION = 2;
|
||||
export const DASHBOARD_GRID_COLUMNS = 12;
|
||||
export const DASHBOARD_GRID_COLUMNS = 16;
|
||||
|
||||
export interface DashboardWidgetSize {
|
||||
w: number;
|
||||
@@ -118,8 +118,8 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Overview Stats",
|
||||
description: "Key metrics: total resources, active projects, allocations, budget utilization",
|
||||
icon: "📊",
|
||||
defaultSize: { w: 12, h: 3 },
|
||||
minSize: { w: 6, h: 2 },
|
||||
defaultSize: { w: 16, h: 3 },
|
||||
minSize: { w: 8, h: 2 },
|
||||
defaultConfig: { showDetails: false },
|
||||
},
|
||||
{
|
||||
@@ -127,8 +127,8 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Resource Table",
|
||||
description: "Filterable list of EIDs with utilization and chargeability",
|
||||
icon: "👥",
|
||||
defaultSize: { w: 8, h: 6 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultSize: { w: 10, h: 6 },
|
||||
minSize: { w: 5, h: 4 },
|
||||
defaultConfig: { showDetails: false },
|
||||
},
|
||||
{
|
||||
@@ -136,8 +136,8 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Project Overview",
|
||||
description: "Projects with costs, person days, and timeline",
|
||||
icon: "📋",
|
||||
defaultSize: { w: 8, h: 6 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultSize: { w: 10, h: 6 },
|
||||
minSize: { w: 5, h: 4 },
|
||||
defaultConfig: { showDetails: false },
|
||||
},
|
||||
{
|
||||
@@ -145,8 +145,8 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Peak Times",
|
||||
description: "Booked hours vs capacity over time",
|
||||
icon: "📈",
|
||||
defaultSize: { w: 8, h: 5 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultSize: { w: 10, h: 5 },
|
||||
minSize: { w: 5, h: 4 },
|
||||
defaultConfig: {
|
||||
showDetails: false,
|
||||
granularity: "month",
|
||||
@@ -158,7 +158,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Demand View",
|
||||
description: "Staffing demand vs supply by project, person, or chapter",
|
||||
icon: "🔍",
|
||||
defaultSize: { w: 6, h: 5 },
|
||||
defaultSize: { w: 8, h: 5 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultConfig: {
|
||||
showDetails: false,
|
||||
@@ -170,7 +170,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Top Value Resources",
|
||||
description: "Leaderboard of resources ranked by price/quality value score",
|
||||
icon: "★",
|
||||
defaultSize: { w: 6, h: 5 },
|
||||
defaultSize: { w: 8, h: 5 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultConfig: {
|
||||
showDetails: false,
|
||||
@@ -182,7 +182,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Chargeability Overview",
|
||||
description: "Top-list and watchlist by actual chargeability this month",
|
||||
icon: "⚡",
|
||||
defaultSize: { w: 6, h: 8 },
|
||||
defaultSize: { w: 8, h: 8 },
|
||||
minSize: { w: 4, h: 6 },
|
||||
defaultConfig: {
|
||||
showDetails: false,
|
||||
@@ -195,7 +195,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "My Projects",
|
||||
description: "Quick access to your favorite and responsible projects",
|
||||
icon: "⭐",
|
||||
defaultSize: { w: 6, h: 6 },
|
||||
defaultSize: { w: 8, h: 6 },
|
||||
minSize: { w: 4, h: 3 },
|
||||
defaultConfig: {
|
||||
showDetails: false,
|
||||
@@ -208,7 +208,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Budget Forecast",
|
||||
description: "Budget burn rate and projected exhaustion per active project",
|
||||
icon: "💰",
|
||||
defaultSize: { w: 6, h: 5 },
|
||||
defaultSize: { w: 8, h: 5 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultConfig: { showDetails: false },
|
||||
},
|
||||
@@ -217,7 +217,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Skill Gap Analysis",
|
||||
description: "Top skill shortages: open demand vs available supply",
|
||||
icon: "🎯",
|
||||
defaultSize: { w: 6, h: 5 },
|
||||
defaultSize: { w: 8, h: 5 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultConfig: { showDetails: false },
|
||||
},
|
||||
@@ -226,7 +226,7 @@ export const DASHBOARD_WIDGET_CATALOG = [
|
||||
label: "Project Health",
|
||||
description: "Composite health score per project: budget, staffing, timeline",
|
||||
icon: "🏥",
|
||||
defaultSize: { w: 6, h: 5 },
|
||||
defaultSize: { w: 8, h: 5 },
|
||||
minSize: { w: 4, h: 4 },
|
||||
defaultConfig: { showDetails: false },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user