Skip to content

Commit c67a278

Browse files
Copilothotlong
andcommitted
feat(types): add DashboardConfig types and Zod validation schemas
- Define DashboardConfig, DashboardWidgetConfig, DashboardColorVariant, DashboardWidgetType in designer.ts - Export from @object-ui/types index - Add DashboardConfigSchema and DashboardWidgetConfigSchema Zod validators - Export from zod/index.zod.ts - Add 16 tests for types and Zod validation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 1245f81 commit c67a278

5 files changed

Lines changed: 397 additions & 0 deletions

File tree

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* Tests for DashboardConfig types and Zod validation schemas.
11+
*/
12+
import { describe, it, expect } from 'vitest';
13+
import type {
14+
DashboardConfig,
15+
DashboardWidgetConfig,
16+
DashboardColorVariant,
17+
DashboardWidgetType,
18+
} from '../designer';
19+
import {
20+
DashboardConfigSchema,
21+
DashboardWidgetConfigSchema,
22+
} from '../zod/index.zod';
23+
24+
describe('DashboardConfig TypeScript Types', () => {
25+
it('should accept a minimal DashboardConfig', () => {
26+
const config: DashboardConfig = {};
27+
expect(config).toBeDefined();
28+
});
29+
30+
it('should accept a full DashboardConfig', () => {
31+
const config: DashboardConfig = {
32+
id: 'dash-1',
33+
title: 'Sales Dashboard',
34+
description: 'Overview of sales pipeline',
35+
columns: 12,
36+
gap: 16,
37+
refreshInterval: 30,
38+
widgets: [
39+
{
40+
id: 'w1',
41+
title: 'Total Revenue',
42+
type: 'metric',
43+
object: 'deals',
44+
valueField: 'amount',
45+
aggregate: 'sum',
46+
colorVariant: 'success',
47+
layout: { x: 0, y: 0, w: 3, h: 2 },
48+
},
49+
],
50+
globalFilters: [['status', '=', 'active']],
51+
dateRange: {
52+
enabled: true,
53+
field: 'created_at',
54+
presets: ['today', 'this_week', 'this_month'],
55+
},
56+
userFilters: [
57+
{ field: 'region', label: 'Region', type: 'select' },
58+
],
59+
showHeader: true,
60+
showFilters: true,
61+
showDateRange: true,
62+
headerActions: [
63+
{ label: 'Export', action: 'export', icon: 'Download', variant: 'outline' },
64+
],
65+
aria: { label: 'Sales dashboard', description: 'Interactive sales overview' },
66+
};
67+
expect(config.widgets).toHaveLength(1);
68+
expect(config.widgets![0].type).toBe('metric');
69+
});
70+
71+
it('should allow DashboardWidgetType values', () => {
72+
const types: DashboardWidgetType[] = [
73+
'metric', 'bar', 'line', 'pie', 'donut', 'area', 'scatter', 'table', 'list', 'custom',
74+
];
75+
expect(types).toHaveLength(10);
76+
});
77+
78+
it('should allow DashboardColorVariant values', () => {
79+
const colors: DashboardColorVariant[] = [
80+
'default', 'blue', 'teal', 'orange', 'purple', 'success', 'warning', 'danger',
81+
];
82+
expect(colors).toHaveLength(8);
83+
});
84+
85+
it('should allow DashboardWidgetConfig with all properties', () => {
86+
const widget: DashboardWidgetConfig = {
87+
id: 'w1',
88+
title: 'Revenue Chart',
89+
description: 'Monthly revenue',
90+
type: 'bar',
91+
object: 'deals',
92+
filter: [['status', '=', 'closed']],
93+
categoryField: 'month',
94+
valueField: 'amount',
95+
aggregate: 'sum',
96+
chartConfig: { stacked: true },
97+
colorVariant: 'blue',
98+
layout: { x: 0, y: 0, w: 6, h: 4 },
99+
actionUrl: '/deals',
100+
};
101+
expect(widget.id).toBe('w1');
102+
});
103+
});
104+
105+
describe('DashboardConfig Zod Validation', () => {
106+
it('should validate a minimal DashboardConfig', () => {
107+
const result = DashboardConfigSchema.safeParse({});
108+
expect(result.success).toBe(true);
109+
});
110+
111+
it('should validate a complete DashboardConfig', () => {
112+
const result = DashboardConfigSchema.safeParse({
113+
id: 'dash-1',
114+
title: 'Sales Dashboard',
115+
description: 'Overview of sales pipeline',
116+
columns: 12,
117+
gap: 16,
118+
refreshInterval: 30,
119+
widgets: [
120+
{
121+
id: 'w1',
122+
title: 'Total Revenue',
123+
type: 'metric',
124+
colorVariant: 'success',
125+
layout: { x: 0, y: 0, w: 3, h: 2 },
126+
},
127+
],
128+
showHeader: true,
129+
showFilters: true,
130+
});
131+
expect(result.success).toBe(true);
132+
});
133+
134+
it('should reject invalid column count', () => {
135+
const result = DashboardConfigSchema.safeParse({ columns: 0 });
136+
expect(result.success).toBe(false);
137+
});
138+
139+
it('should reject negative gap', () => {
140+
const result = DashboardConfigSchema.safeParse({ gap: -1 });
141+
expect(result.success).toBe(false);
142+
});
143+
144+
it('should validate DashboardWidgetConfigSchema', () => {
145+
const result = DashboardWidgetConfigSchema.safeParse({
146+
id: 'w1',
147+
title: 'Revenue',
148+
type: 'bar',
149+
object: 'deals',
150+
colorVariant: 'blue',
151+
layout: { x: 0, y: 0, w: 6, h: 4 },
152+
});
153+
expect(result.success).toBe(true);
154+
});
155+
156+
it('should reject DashboardWidgetConfigSchema without id', () => {
157+
const result = DashboardWidgetConfigSchema.safeParse({
158+
title: 'Revenue',
159+
});
160+
expect(result.success).toBe(false);
161+
});
162+
163+
it('should reject invalid colorVariant', () => {
164+
const result = DashboardWidgetConfigSchema.safeParse({
165+
id: 'w1',
166+
colorVariant: 'invalid-color',
167+
});
168+
expect(result.success).toBe(false);
169+
});
170+
171+
it('should validate dateRange configuration', () => {
172+
const result = DashboardConfigSchema.safeParse({
173+
dateRange: {
174+
enabled: true,
175+
field: 'created_at',
176+
presets: ['today', 'this_week'],
177+
},
178+
});
179+
expect(result.success).toBe(true);
180+
});
181+
182+
it('should validate userFilters configuration', () => {
183+
const result = DashboardConfigSchema.safeParse({
184+
userFilters: [
185+
{ field: 'region', label: 'Region', type: 'select' },
186+
{ field: 'status' },
187+
],
188+
});
189+
expect(result.success).toBe(true);
190+
});
191+
192+
it('should validate headerActions configuration', () => {
193+
const result = DashboardConfigSchema.safeParse({
194+
headerActions: [
195+
{ label: 'Export', action: 'export', icon: 'Download', variant: 'outline' },
196+
{ label: 'Refresh' },
197+
],
198+
});
199+
expect(result.success).toBe(true);
200+
});
201+
202+
it('should validate aria accessibility attributes', () => {
203+
const result = DashboardConfigSchema.safeParse({
204+
aria: { label: 'Sales dashboard', description: 'Interactive overview' },
205+
});
206+
expect(result.success).toBe(true);
207+
});
208+
});

packages/types/src/designer.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,131 @@ export interface UnifiedViewConfig {
550550
[key: string]: any;
551551
}
552552

553+
// ============================================================================
554+
// Dashboard Configuration
555+
// ============================================================================
556+
557+
/** Color variant for dashboard widgets */
558+
export type DashboardColorVariant =
559+
| 'default'
560+
| 'blue'
561+
| 'teal'
562+
| 'orange'
563+
| 'purple'
564+
| 'success'
565+
| 'warning'
566+
| 'danger';
567+
568+
/** Widget visualization type */
569+
export type DashboardWidgetType =
570+
| 'metric'
571+
| 'bar'
572+
| 'line'
573+
| 'pie'
574+
| 'donut'
575+
| 'area'
576+
| 'scatter'
577+
| 'table'
578+
| 'list'
579+
| 'custom';
580+
581+
/** Layout position for a single dashboard widget */
582+
export interface DashboardWidgetConfig {
583+
/** Widget identifier */
584+
id: string;
585+
/** Widget title */
586+
title?: string;
587+
/** Widget description */
588+
description?: string;
589+
/** Visualization type */
590+
type?: DashboardWidgetType;
591+
/** Data source object name */
592+
object?: string;
593+
/** Filter conditions applied to widget data */
594+
filter?: any[];
595+
/** Category / x-axis field */
596+
categoryField?: string;
597+
/** Value / y-axis field */
598+
valueField?: string;
599+
/** Aggregation function (count, sum, avg, min, max) */
600+
aggregate?: string;
601+
/** Chart-specific configuration */
602+
chartConfig?: any;
603+
/** Color variant */
604+
colorVariant?: DashboardColorVariant;
605+
/** Grid layout position */
606+
layout?: { x: number; y: number; w: number; h: number };
607+
/** Clickable action URL */
608+
actionUrl?: string;
609+
}
610+
611+
/**
612+
* Unified data model for dashboard configuration.
613+
*
614+
* Used by the DashboardConfigPanel for create/edit workflows.
615+
* Mirrors the pattern of UnifiedViewConfig for view configuration.
616+
*/
617+
export interface DashboardConfig {
618+
/** Dashboard identifier */
619+
id?: string;
620+
/** Display title */
621+
title?: string;
622+
/** Dashboard description */
623+
description?: string;
624+
/** Number of grid columns (default: 12) */
625+
columns?: number;
626+
/** Grid gap in pixels */
627+
gap?: number;
628+
/** Auto-refresh interval in seconds */
629+
refreshInterval?: number;
630+
/** Dashboard widgets */
631+
widgets?: DashboardWidgetConfig[];
632+
633+
// -- Global filters --------------------------------------------------------
634+
635+
/** Global filter conditions applied across all widgets */
636+
globalFilters?: any[];
637+
/** Date range filter configuration */
638+
dateRange?: {
639+
enabled?: boolean;
640+
field?: string;
641+
presets?: string[];
642+
};
643+
/** User-selectable filter fields */
644+
userFilters?: Array<{
645+
field: string;
646+
label?: string;
647+
type?: string;
648+
}>;
649+
650+
// -- Appearance ------------------------------------------------------------
651+
652+
/** Show dashboard header with title/description */
653+
showHeader?: boolean;
654+
/** Show global filter bar */
655+
showFilters?: boolean;
656+
/** Show date range picker */
657+
showDateRange?: boolean;
658+
/** Action buttons in dashboard header */
659+
headerActions?: Array<{
660+
label: string;
661+
action?: string;
662+
icon?: string;
663+
variant?: string;
664+
}>;
665+
666+
// -- Accessibility ---------------------------------------------------------
667+
668+
/** ARIA properties */
669+
aria?: {
670+
label?: string;
671+
description?: string;
672+
};
673+
674+
/** Catch-all for additional properties */
675+
[key: string]: any;
676+
}
677+
553678
// ============================================================================
554679
// Multi-User Collaborative Editing
555680
// ============================================================================

packages/types/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ export type {
544544
ViewDesignerSchema,
545545
UnifiedViewType,
546546
UnifiedViewConfig,
547+
DashboardColorVariant,
548+
DashboardWidgetType,
549+
DashboardWidgetConfig,
550+
DashboardConfig,
547551
} from './designer';
548552

549553
// ============================================================================

0 commit comments

Comments
 (0)