@svgrid/enterprise 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -81
- package/dist/cdn/svgrid-enterprise.svelte-external.js +22479 -10466
- package/dist/node/studio.js +11590 -2907
- package/package.json +31 -10
- package/src/SvAlertRuleEditor.svelte +294 -0
- package/src/SvAlertsManager.svelte +210 -0
- package/src/SvAlertsPanel.svelte +129 -0
- package/src/SvBoard.svelte +3 -1
- package/src/SvExpressionEditor.svelte +341 -0
- package/src/SvGridAlerts.dom.test.ts +113 -0
- package/src/SvGridAlerts.svelte +265 -0
- package/src/SvGridBoard.svelte +2355 -0
- package/src/SvGridEditPanel.svelte +849 -794
- package/src/SvGridScheduler.svelte +5334 -4410
- package/src/SvPivotDesigner.svelte +3 -3
- package/src/SvRecordDetail.svelte +6 -2
- package/src/SvSchedule.svelte +3 -1
- package/src/{ai-export-pdf.test.ts → ai-export-pdf.dom.test.ts} +4 -1
- package/src/{ai-export-xlsx.test.ts → ai-export-xlsx.dom.test.ts} +4 -1
- package/src/{ai-export.test.ts → ai-export.dom.test.ts} +5 -1
- package/src/alerts/alert-engine-attach.ts +165 -0
- package/src/alerts/alert-engine.test.ts +135 -0
- package/src/alerts/alert-engine.ts +260 -0
- package/src/alerts/alert-formats.test.ts +87 -0
- package/src/alerts/alert-formats.ts +77 -0
- package/src/alerts/alert-observer.test.ts +189 -0
- package/src/alerts/alert-observer.ts +208 -0
- package/src/alerts/alert-scheduler.ts +96 -0
- package/src/alerts/alert-storage.test.ts +54 -0
- package/src/alerts/alert-storage.ts +116 -0
- package/src/alerts/alert-store.svelte.ts +80 -0
- package/src/alerts/alert-types.ts +94 -0
- package/src/alerts.ts +28 -0
- package/src/board.dom.test.ts +941 -0
- package/src/board.ts +36 -0
- package/src/export.ts +13 -1
- package/src/expressions/evaluate.test.ts +111 -0
- package/src/expressions/evaluate.ts +228 -0
- package/src/expressions/expression-columns.ts +133 -0
- package/src/expressions/expression-types.ts +84 -0
- package/src/expressions/parse.test.ts +106 -0
- package/src/expressions/parse.ts +610 -0
- package/src/import.ts +2 -0
- package/src/index.ts +180 -46
- package/src/install.ts +12 -2
- package/src/pivot-enable.ts +44 -0
- package/src/scheduler-assignments.test.ts +97 -0
- package/src/scheduler-assignments.ts +134 -0
- package/src/scheduler-axis.test.ts +108 -0
- package/src/scheduler-axis.ts +238 -0
- package/src/scheduler-booking.test.ts +57 -0
- package/src/scheduler-booking.ts +63 -0
- package/src/scheduler-config.ts +179 -0
- package/src/scheduler-dependencies.test.ts +155 -0
- package/src/scheduler-dependencies.ts +223 -0
- package/src/scheduler-freebusy.test.ts +41 -0
- package/src/scheduler-freebusy.ts +36 -0
- package/src/scheduler-heatmap.test.ts +39 -0
- package/src/scheduler-heatmap.ts +55 -0
- package/src/scheduler-resource-tree.test.ts +84 -0
- package/src/scheduler-resource-tree.ts +107 -0
- package/src/scheduler-slots.test.ts +53 -0
- package/src/scheduler-slots.ts +94 -0
- package/src/scheduler-summary.test.ts +47 -0
- package/src/scheduler-summary.ts +81 -0
- package/src/sources/index.ts +1 -1
- package/src/sources/introspect-supabase.test.ts +13 -1
- package/src/sources/introspect-supabase.ts +20 -0
- package/src/studio/copilot-core.test.ts +45 -0
- package/src/studio/copilot-core.ts +65 -0
- package/src/studio/deploy-cli.test.ts +56 -0
- package/src/studio/deploy-cli.ts +100 -0
- package/src/studio/emit-project.test.ts +503 -18
- package/src/studio/emit-project.ts +829 -111
- package/src/studio/emit-schema.test.ts +17 -0
- package/src/studio/emit-schema.ts +166 -35
- package/src/studio/index.ts +25 -1
- package/src/studio/introspect-openapi.test.ts +84 -0
- package/src/studio/introspect-openapi.ts +252 -0
- package/src/studio/project.test.ts +57 -0
- package/src/studio/project.ts +193 -6
- package/src/studio/samples/crm.ts +282 -258
- package/src/studio/samples/fleet.ts +213 -186
- package/src/studio/samples/insurance.ts +223 -195
- package/src/studio/samples/inventory.ts +213 -182
- package/src/studio/samples/live-data.test.ts +99 -98
- package/src/studio/samples/live-data.ts +8 -10
- package/src/studio/samples/projects.ts +212 -190
- package/src/studio/samples/samples.test.ts +52 -0
- package/src/studio/samples/shared.ts +26 -6
- package/src/studio/samples/starter.ts +251 -0
- package/src/studio/samples/support.ts +209 -184
- package/src/studio/ui-components-surface.test.ts +185 -0
- package/src/studio/ui-components.generated.ts +5617 -0
- package/src/studio/ui-components.ts +641 -472
- package/src/sveltekit/sql-source.test.ts +13 -0
- package/src/sveltekit/sql-source.ts +11 -6
- package/src/upgrade-prompt.ts +2 -2
- package/src/watermark.ts +2 -2
- package/src/ai.test.ts +0 -522
- package/src/ai.ts +0 -1388
|
@@ -1,258 +1,282 @@
|
|
|
1
|
-
import type { EntitySchema } from '../../schema.js'
|
|
2
|
-
import type { FormatRule } from '../project.js'
|
|
3
|
-
import { screen, formScreen, boardScreen, schedulerScreen, workspaceScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{ field: '
|
|
30
|
-
{ field: '
|
|
31
|
-
{ field: '
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ field: '
|
|
41
|
-
{ field: '
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{ field: '
|
|
57
|
-
{ field: '
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{ field: '
|
|
67
|
-
{ field: '
|
|
68
|
-
|
|
69
|
-
{ field: '
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{ field: '
|
|
81
|
-
{ field: '
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{ field: '
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{ value: '
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{ field: '
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{ id: '
|
|
126
|
-
{ id: '
|
|
127
|
-
{ id: '
|
|
128
|
-
{ id: '
|
|
129
|
-
{ id: '
|
|
130
|
-
{ id: '
|
|
131
|
-
],
|
|
132
|
-
|
|
133
|
-
{ id: '
|
|
134
|
-
{ id: '
|
|
135
|
-
{ id: '
|
|
136
|
-
{ id: '
|
|
137
|
-
{ id: '
|
|
138
|
-
{ id: '
|
|
139
|
-
{ id: '
|
|
140
|
-
{ id: '
|
|
141
|
-
],
|
|
142
|
-
|
|
143
|
-
{ id: '
|
|
144
|
-
{ id: '
|
|
145
|
-
{ id: '
|
|
146
|
-
{ id: '
|
|
147
|
-
{ id: '
|
|
148
|
-
{ id: '
|
|
149
|
-
{ id: '
|
|
150
|
-
{ id: '
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{ kpi: '
|
|
242
|
-
{
|
|
243
|
-
{
|
|
244
|
-
{ chart: '
|
|
245
|
-
{
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
1
|
+
import type { EntitySchema } from '../../schema.js'
|
|
2
|
+
import type { FormatRule } from '../project.js'
|
|
3
|
+
import { screen, formScreen, boardScreen, schedulerScreen, workspaceScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
4
|
+
|
|
5
|
+
// Salesforce Lightning-style chrome: a gradient blue brand rail, white rounded-pill
|
|
6
|
+
// nav with a solid-white active state, and a Lightning-blue rule under the toolbar.
|
|
7
|
+
const LIGHTNING_CSS = `.sv-app.theme-lightning .sv-app__side { background: linear-gradient(180deg, #032d60, #0176d3); border-right: 0; }
|
|
8
|
+
.sv-app.theme-lightning .sv-app__brandtext { color: #fff; font-weight: 800; letter-spacing: -0.01em; }
|
|
9
|
+
.sv-app.theme-lightning .sv-app__link { color: #d9edff; font-weight: 600; border-radius: 9999px; padding: 8px 14px; }
|
|
10
|
+
.sv-app.theme-lightning .sv-app__link:hover { background: rgba(255, 255, 255, 0.14); color: #fff; }
|
|
11
|
+
.sv-app.theme-lightning .sv-app__link.is-active { background: #fff; color: #032d60; }
|
|
12
|
+
.sv-app.theme-lightning .sv-app__collapse, .sv-app.theme-lightning .sv-app__foot { color: rgba(255, 255, 255, 0.72); }
|
|
13
|
+
.sv-app.theme-lightning .sv-app__toolbar { border-bottom: 2px solid #0176d3; }`
|
|
14
|
+
|
|
15
|
+
// A fixed sales-rep pool, shared by deals + activities so leaderboards, pivots
|
|
16
|
+
// and owner pills stay clean (padded rows cycle these instead of random names).
|
|
17
|
+
const OWNERS = [
|
|
18
|
+
{ value: 'Sam Rivera', label: 'Sam Rivera', color: '#6366f1' },
|
|
19
|
+
{ value: 'Jamie Chen', label: 'Jamie Chen', color: '#10b981' },
|
|
20
|
+
{ value: 'Priya Patel', label: 'Priya Patel', color: '#f59e0b' },
|
|
21
|
+
{ value: 'Dana Lee', label: 'Dana Lee', color: '#ec4899' },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const companies: EntitySchema = {
|
|
25
|
+
name: 'companies',
|
|
26
|
+
label: 'Company',
|
|
27
|
+
idField: 'id',
|
|
28
|
+
fields: [
|
|
29
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
30
|
+
{ field: 'name', type: 'text', required: true, minLength: 2 },
|
|
31
|
+
{ field: 'industry', type: 'enum', required: true, options: [
|
|
32
|
+
{ value: 'saas', label: 'SaaS', color: '#6366f1' },
|
|
33
|
+
{ value: 'fintech', label: 'Fintech', color: '#10b981' },
|
|
34
|
+
{ value: 'retail', label: 'Retail', color: '#f59e0b' },
|
|
35
|
+
{ value: 'health', label: 'Health', color: '#ef4444' },
|
|
36
|
+
] },
|
|
37
|
+
{ field: 'country', type: 'text', label: 'Country', input: { editorType: 'country', help: 'Headquarters country' } },
|
|
38
|
+
{ field: 'website', type: 'text', format: 'url', input: { placeholder: 'https://…' } },
|
|
39
|
+
{ field: 'employees', type: 'number', label: 'Employees', min: 0 },
|
|
40
|
+
{ field: 'health', type: 'number', label: 'Account health', min: 1, max: 5, defaultValue: 3, input: { editorType: 'rating', help: '1-5 relationship score' } },
|
|
41
|
+
{ field: 'active', type: 'boolean', label: 'Active', defaultValue: true },
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const contacts: EntitySchema = {
|
|
46
|
+
name: 'contacts',
|
|
47
|
+
label: 'Contact',
|
|
48
|
+
idField: 'id',
|
|
49
|
+
fields: [
|
|
50
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
51
|
+
{ field: 'name', type: 'text', required: true, minLength: 2 },
|
|
52
|
+
{ field: 'email', type: 'text', format: 'email', required: true },
|
|
53
|
+
{ field: 'phone', type: 'text', input: { editorType: 'phone' } },
|
|
54
|
+
{ field: 'title', type: 'text', label: 'Job title' },
|
|
55
|
+
{ field: 'companyId', type: 'relation', label: 'Company', relation: { entity: 'companies', foreignKey: 'companyId', labelField: 'name' } },
|
|
56
|
+
{ field: 'tags', type: 'text', label: 'Segments', input: { editorType: 'chips', help: 'Interests / segments' } },
|
|
57
|
+
{ field: 'active', type: 'boolean', defaultValue: true },
|
|
58
|
+
],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const deals: EntitySchema = {
|
|
62
|
+
name: 'deals',
|
|
63
|
+
label: 'Deal',
|
|
64
|
+
idField: 'id',
|
|
65
|
+
fields: [
|
|
66
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
67
|
+
{ field: 'title', type: 'text', required: true, minLength: 2 },
|
|
68
|
+
{ field: 'companyId', type: 'relation', label: 'Company', relation: { entity: 'companies', foreignKey: 'companyId', labelField: 'name' } },
|
|
69
|
+
{ field: 'stage', type: 'enum', required: true, options: [
|
|
70
|
+
{ value: 'lead', label: 'Lead', color: '#94a3b8' },
|
|
71
|
+
{ value: 'qualified', label: 'Qualified', color: '#6366f1' },
|
|
72
|
+
{ value: 'proposal', label: 'Proposal', color: '#f59e0b' },
|
|
73
|
+
{ value: 'won', label: 'Won', color: '#10b981' },
|
|
74
|
+
{ value: 'lost', label: 'Lost', color: '#ef4444' },
|
|
75
|
+
] },
|
|
76
|
+
{ field: 'value', type: 'number', label: 'Value ($)', min: 0 },
|
|
77
|
+
{ field: 'probability', type: 'number', label: 'Probability (%)', min: 0, max: 100, defaultValue: 50, input: { editorType: 'slider', help: 'Win likelihood' } },
|
|
78
|
+
// Derived: expected value weighted by win probability (display-only).
|
|
79
|
+
{ field: 'weighted', type: 'number', label: 'Weighted ($)', readonly: true, formula: 'value * probability / 100' },
|
|
80
|
+
{ field: 'owner', type: 'enum', label: 'Owner', options: OWNERS },
|
|
81
|
+
{ field: 'closeDate', type: 'date', label: 'Close date' },
|
|
82
|
+
],
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const activities: EntitySchema = {
|
|
86
|
+
name: 'activities',
|
|
87
|
+
label: 'Activity',
|
|
88
|
+
idField: 'id',
|
|
89
|
+
fields: [
|
|
90
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
91
|
+
{ field: 'subject', type: 'text', required: true, minLength: 3 },
|
|
92
|
+
{ field: 'type', type: 'enum', required: true, options: [
|
|
93
|
+
{ value: 'call', label: 'Call', color: '#6366f1' },
|
|
94
|
+
{ value: 'email', label: 'Email', color: '#10b981' },
|
|
95
|
+
{ value: 'meeting', label: 'Meeting', color: '#f59e0b' },
|
|
96
|
+
{ value: 'task', label: 'Task', color: '#8b5cf6' },
|
|
97
|
+
] },
|
|
98
|
+
{ field: 'dealId', type: 'relation', label: 'Deal', relation: { entity: 'deals', foreignKey: 'dealId', labelField: 'title' } },
|
|
99
|
+
{ field: 'owner', type: 'enum', label: 'Owner', options: OWNERS },
|
|
100
|
+
{ field: 'priority', type: 'enum', defaultValue: 'medium', options: [
|
|
101
|
+
{ value: 'low', label: 'Low', color: '#94a3b8' },
|
|
102
|
+
{ value: 'medium', label: 'Medium', color: '#6366f1' },
|
|
103
|
+
{ value: 'high', label: 'High', color: '#ef4444' },
|
|
104
|
+
] },
|
|
105
|
+
{ field: 'dueDate', type: 'date', label: 'Due date' },
|
|
106
|
+
{ field: 'durationMins', type: 'number', label: 'Duration (min)', min: 0 },
|
|
107
|
+
{ field: 'completed', type: 'boolean', label: 'Done', defaultValue: false },
|
|
108
|
+
{ field: 'notes', type: 'text', label: 'Notes' },
|
|
109
|
+
],
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Deal grid formatting: stage + owner pills, plus numeric thresholds (weak deals
|
|
113
|
+
// red, strong deals green + bold, big-ticket values bold) - data-driven color, not
|
|
114
|
+
// just status pills.
|
|
115
|
+
const dealFormats: FormatRule[] = [
|
|
116
|
+
...statusPills(deals, 'stage'),
|
|
117
|
+
...statusPills(deals, 'owner'),
|
|
118
|
+
{ field: 'probability', op: 'lt', value: 30, color: '#dc2626' },
|
|
119
|
+
{ field: 'probability', op: 'gte', value: 70, color: '#16a34a', bold: true },
|
|
120
|
+
{ field: 'value', op: 'gte', value: 100000, bold: true },
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
const seed = {
|
|
124
|
+
companies: [
|
|
125
|
+
{ id: 'co1', name: 'Globex', industry: 'saas', country: 'United States', website: 'https://globex.com', employees: 320, health: 4, active: true },
|
|
126
|
+
{ id: 'co2', name: 'Initech', industry: 'fintech', country: 'United Kingdom', website: 'https://initech.co', employees: 140, health: 3, active: true },
|
|
127
|
+
{ id: 'co3', name: 'Umbrella', industry: 'health', country: 'Germany', website: 'https://umbrella.de', employees: 900, health: 5, active: true },
|
|
128
|
+
{ id: 'co4', name: 'Hooli', industry: 'saas', country: 'United States', website: 'https://hooli.com', employees: 2100, health: 2, active: false },
|
|
129
|
+
{ id: 'co5', name: 'Soylent', industry: 'retail', country: 'Canada', website: 'https://soylent.ca', employees: 75, health: 3, active: true },
|
|
130
|
+
{ id: 'co6', name: 'Stark Industries', industry: 'fintech', country: 'United States', website: 'https://stark.com', employees: 5400, health: 5, active: true },
|
|
131
|
+
],
|
|
132
|
+
contacts: [
|
|
133
|
+
{ id: 'ct1', name: 'Ada Lovelace', email: 'ada@globex.com', phone: '+1 (415) 555-0101', title: 'CTO', companyId: 'co1', tags: ['Champion', 'Technical'], active: true },
|
|
134
|
+
{ id: 'ct2', name: 'Alan Turing', email: 'alan@initech.co', phone: '+44 20 7946 0102', title: 'Head of Data', companyId: 'co2', tags: ['Economic buyer'], active: true },
|
|
135
|
+
{ id: 'ct3', name: 'Grace Hopper', email: 'grace@umbrella.de', phone: '+49 30 5550103', title: 'VP Engineering', companyId: 'co3', tags: ['Champion'], active: true },
|
|
136
|
+
{ id: 'ct4', name: 'Linus Torvalds', email: 'linus@hooli.com', phone: '+1 (650) 555-0104', title: 'Architect', companyId: 'co4', tags: ['Technical', 'Blocker'], active: false },
|
|
137
|
+
{ id: 'ct5', name: 'Barbara Liskov', email: 'barbara@soylent.ca', phone: '+1 (416) 555-0105', title: 'Founder', companyId: 'co5', tags: ['Decision maker'], active: true },
|
|
138
|
+
{ id: 'ct6', name: 'Katherine Johnson', email: 'kj@stark.com', phone: '+1 (212) 555-0106', title: 'Analyst', companyId: 'co6', tags: ['Influencer'], active: true },
|
|
139
|
+
{ id: 'ct7', name: 'Donald Knuth', email: 'don@globex.com', phone: '+1 (415) 555-0107', title: 'Advisor', companyId: 'co1', tags: ['Influencer', 'Technical'], active: true },
|
|
140
|
+
{ id: 'ct8', name: 'Margaret Hamilton', email: 'mh@stark.com', phone: '+1 (212) 555-0108', title: 'Director', companyId: 'co6', tags: ['Decision maker'], active: true },
|
|
141
|
+
],
|
|
142
|
+
deals: [
|
|
143
|
+
{ id: 'dl1', title: 'Globex platform rollout', companyId: 'co1', stage: 'proposal', value: 48000, probability: 60, owner: 'Sam Rivera', closeDate: '2026-08-15' },
|
|
144
|
+
{ id: 'dl2', title: 'Initech data pipeline', companyId: 'co2', stage: 'won', value: 26000, probability: 100, owner: 'Jamie Chen', closeDate: '2026-06-30' },
|
|
145
|
+
{ id: 'dl3', title: 'Umbrella compliance suite', companyId: 'co3', stage: 'qualified', value: 91000, probability: 40, owner: 'Sam Rivera', closeDate: '2026-09-20' },
|
|
146
|
+
{ id: 'dl4', title: 'Hooli migration', companyId: 'co4', stage: 'lost', value: 15000, probability: 0, owner: 'Priya Patel', closeDate: '2026-05-10' },
|
|
147
|
+
{ id: 'dl5', title: 'Soylent storefront', companyId: 'co5', stage: 'lead', value: 8000, probability: 20, owner: 'Jamie Chen', closeDate: '2026-10-01' },
|
|
148
|
+
{ id: 'dl6', title: 'Stark analytics', companyId: 'co6', stage: 'proposal', value: 132000, probability: 65, owner: 'Priya Patel', closeDate: '2026-08-28' },
|
|
149
|
+
{ id: 'dl7', title: 'Globex add-on seats', companyId: 'co1', stage: 'won', value: 12000, probability: 100, owner: 'Sam Rivera', closeDate: '2026-07-05' },
|
|
150
|
+
{ id: 'dl8', title: 'Stark expansion', companyId: 'co6', stage: 'qualified', value: 54000, probability: 45, owner: 'Jamie Chen', closeDate: '2026-09-12' },
|
|
151
|
+
],
|
|
152
|
+
activities: [
|
|
153
|
+
{ id: 'ac1', subject: 'Discovery call with Ada', type: 'call', dealId: 'dl1', owner: 'Sam Rivera', priority: 'high', dueDate: '2026-07-16', durationMins: 30, completed: true, notes: 'Mapped the rollout scope.' },
|
|
154
|
+
{ id: 'ac2', subject: 'Send pricing proposal', type: 'email', dealId: 'dl1', owner: 'Sam Rivera', priority: 'high', dueDate: '2026-07-18', durationMins: 15, completed: false, notes: 'Awaiting legal sign-off.' },
|
|
155
|
+
{ id: 'ac3', subject: 'Data pipeline demo', type: 'meeting', dealId: 'dl2', owner: 'Jamie Chen', priority: 'medium', dueDate: '2026-07-12', durationMins: 45, completed: true, notes: 'Great fit, moving to close.' },
|
|
156
|
+
{ id: 'ac4', subject: 'Compliance review follow-up', type: 'task', dealId: 'dl3', owner: 'Sam Rivera', priority: 'medium', dueDate: '2026-07-20', durationMins: 20, completed: false, notes: 'Collect SOC2 docs.' },
|
|
157
|
+
{ id: 'ac5', subject: 'Re-engage Hooli champion', type: 'call', dealId: 'dl4', owner: 'Priya Patel', priority: 'low', dueDate: '2026-07-22', durationMins: 25, completed: false, notes: 'Deal went cold - revisit Q4.' },
|
|
158
|
+
{ id: 'ac6', subject: 'Storefront scoping', type: 'meeting', dealId: 'dl5', owner: 'Jamie Chen', priority: 'medium', dueDate: '2026-07-19', durationMins: 60, completed: false, notes: 'Needs design mockups.' },
|
|
159
|
+
{ id: 'ac7', subject: 'Analytics exec briefing', type: 'meeting', dealId: 'dl6', owner: 'Priya Patel', priority: 'high', dueDate: '2026-07-24', durationMins: 40, completed: false, notes: 'CFO joining the call.' },
|
|
160
|
+
{ id: 'ac8', subject: 'Renewal paperwork', type: 'task', dealId: 'dl7', owner: 'Sam Rivera', priority: 'low', dueDate: '2026-07-14', durationMins: 10, completed: true, notes: 'Signed and filed.' },
|
|
161
|
+
{ id: 'ac9', subject: 'Expansion seats quote', type: 'email', dealId: 'dl8', owner: 'Jamie Chen', priority: 'medium', dueDate: '2026-07-21', durationMins: 15, completed: false, notes: 'Quote 40 extra seats.' },
|
|
162
|
+
{ id: 'ac10', subject: 'Quarterly business review', type: 'meeting', dealId: 'dl6', owner: 'Priya Patel', priority: 'high', dueDate: '2026-07-28', durationMins: 90, completed: false, notes: 'Prep the QBR deck.' },
|
|
163
|
+
],
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const crm: SampleApp = {
|
|
167
|
+
id: 'crm',
|
|
168
|
+
name: 'CRM',
|
|
169
|
+
description: 'Companies, contacts, deals and activities - a Salesforce Lightning-style CRM behind a sign-in, with sales + forecast + activity dashboards (KPI sparklines, target gauge, value-over-time trend, pivot, tabs), filtered status-pill grids with drill-through and row actions, master/detail, and rich edit forms.',
|
|
170
|
+
emoji: '\u{1F91D}',
|
|
171
|
+
accent: '#0176d3',
|
|
172
|
+
build: () => {
|
|
173
|
+
const companyRows = pad(companies, seed.companies, 12)
|
|
174
|
+
const pool = ids(companyRows)
|
|
175
|
+
const contactRows = pad(contacts, seed.contacts, 22, { companyId: pool })
|
|
176
|
+
const dealRows = pad(deals, seed.deals, 30, { companyId: pool })
|
|
177
|
+
const dealPool = ids(dealRows)
|
|
178
|
+
const activityRows = pad(activities, seed.activities, 24, { dealId: dealPool })
|
|
179
|
+
return project({
|
|
180
|
+
title: 'Acme CRM',
|
|
181
|
+
brand: 'Acme CRM',
|
|
182
|
+
accent: '#0176d3',
|
|
183
|
+
preset: 'salesforce',
|
|
184
|
+
footer: '© Acme Inc.',
|
|
185
|
+
// Salesforce Lightning look: a deep-blue brand rail with white pill nav, and a
|
|
186
|
+
// Lightning-blue accent bar under the app toolbar.
|
|
187
|
+
appClass: 'theme-lightning',
|
|
188
|
+
customCss: LIGHTNING_CSS,
|
|
189
|
+
// A sign-in wall with two sales roles (reps can create + edit, not delete).
|
|
190
|
+
auth: { enabled: true, protect: true },
|
|
191
|
+
access: {
|
|
192
|
+
enabled: true,
|
|
193
|
+
defaultRole: 'rep',
|
|
194
|
+
roles: [
|
|
195
|
+
{ role: 'admin', screens: '*', actions: '*' },
|
|
196
|
+
{ role: 'rep', screens: '*', actions: ['create', 'update'] },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
entities: [companies, contacts, deals, activities],
|
|
200
|
+
seed: { companies: companyRows, contacts: contactRows, deals: dealRows, activities: activityRows },
|
|
201
|
+
screens: [
|
|
202
|
+
// Sales dashboard: KPI cards (a pipeline-value card with a sparkline over
|
|
203
|
+
// close dates + a $400k target), a target gauge, a stage funnel, a pie, an
|
|
204
|
+
// owner-leaderboard, a tabbed breakdown, and a status-pill deal grid.
|
|
205
|
+
dashScreen(deals, { id: 'overview', title: 'Overview', order: 0 }, [
|
|
206
|
+
{ kpi: 'Pipeline value', measure: 'value', reduce: 'sum', format: 'currency', trendField: 'closeDate', trendReduce: 'sum', target: 400000, span: 1 },
|
|
207
|
+
{ kpi: 'Open deals', reduce: 'count', span: 1 },
|
|
208
|
+
{ kpi: 'Avg deal size', measure: 'value', reduce: 'avg', format: 'currency', span: 1 },
|
|
209
|
+
{ gauge: 'Pipeline vs $500k target', measure: 'value', reduce: 'sum', min: 0, max: 500000, unit: '$', span: 1 },
|
|
210
|
+
{ chart: 'stage', measure: 'value', reduce: 'sum', type: 'bar', span: 2 },
|
|
211
|
+
{ chart: 'stage', reduce: 'count', type: 'pie', span: 1 },
|
|
212
|
+
{ chart: 'owner', measure: 'value', reduce: 'sum', type: 'bar', span: 2 },
|
|
213
|
+
{ tabs: [
|
|
214
|
+
{ label: 'Value by stage', tiles: [{ chart: 'stage', measure: 'value', reduce: 'sum', type: 'bar' }] },
|
|
215
|
+
{ label: 'Deals by owner', tiles: [{ chart: 'owner', reduce: 'count', type: 'bar' }] },
|
|
216
|
+
], span: 3 },
|
|
217
|
+
{ grid: true, format: dealFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
218
|
+
]),
|
|
219
|
+
// Pipeline board: deals as draggable cards in stage columns (drag a card to
|
|
220
|
+
// change its stage). The signature "real app" view.
|
|
221
|
+
boardScreen(deals, { id: 'pipeline', title: 'Pipeline', order: 1 }, { groupBy: 'stage', titleField: 'title', badgeField: 'value', subtitleField: 'owner', filter: ['owner', 'companyId'], openScreen: 'deal-360' }),
|
|
222
|
+
// Deal desk: a docking-manager console (split panes) - filter deals on the
|
|
223
|
+
// left, the pipeline grid centre, and the selected deal's record on the right.
|
|
224
|
+
// Resize the panes; click a row to load it. Showcases screen layout: 'split'.
|
|
225
|
+
workspaceScreen(deals, { id: 'deal-desk', title: 'Deal desk', order: 2 }, { filter: ['stage', 'owner'], mode: 'split' }),
|
|
226
|
+
// Deal 360: a full record page - header (title + company + stage pill), value /
|
|
227
|
+
// probability metric tiles, an Overview of deal + account fields, and an
|
|
228
|
+
// Activities timeline of the tasks logged against the deal.
|
|
229
|
+
detailScreen(deals, { id: 'deal-360', title: 'Deal 360', order: 3 }, {
|
|
230
|
+
titleField: 'title', subtitleField: 'companyId', statusField: 'stage',
|
|
231
|
+
metricFields: ['value', 'probability'],
|
|
232
|
+
sections: [
|
|
233
|
+
{ label: 'Deal', fields: ['owner', 'closeDate'] },
|
|
234
|
+
{ label: 'Account', fields: ['companyId'] },
|
|
235
|
+
],
|
|
236
|
+
related: [{ entity: 'activities', foreignKey: 'dealId', label: 'Activities', titleField: 'subject', subtitleField: 'owner', dateField: 'dueDate', statusField: 'priority' }],
|
|
237
|
+
}),
|
|
238
|
+
// Forecast: value KPIs, a value-over-time area trend, and a pivot of value by
|
|
239
|
+
// owner (rows) x stage (columns).
|
|
240
|
+
dashScreen(deals, { id: 'forecast', title: 'Forecast', order: 4 }, [
|
|
241
|
+
{ kpi: 'Pipeline value', measure: 'value', reduce: 'sum', format: 'currency', span: 1 },
|
|
242
|
+
{ kpi: 'Avg deal size', measure: 'value', reduce: 'avg', format: 'currency', span: 1 },
|
|
243
|
+
{ kpi: 'Largest deal', measure: 'value', reduce: 'max', format: 'currency', span: 1 },
|
|
244
|
+
{ chart: 'closeDate', measure: 'value', reduce: 'sum', type: 'area', span: 3 },
|
|
245
|
+
{ pivot: { rows: ['owner'], cols: ['stage'], measure: 'value', aggregate: 'sum' }, span: 3 },
|
|
246
|
+
]),
|
|
247
|
+
// Accounts dashboard: headcount KPIs (compact-formatted), an account-health
|
|
248
|
+
// gauge (avg rating 0-5), industry breakdowns, and a company grid that drills
|
|
249
|
+
// into the account's deals.
|
|
250
|
+
dashScreen(companies, { id: 'accounts', title: 'Accounts', order: 5 }, [
|
|
251
|
+
{ kpi: 'Companies', reduce: 'count', span: 1 },
|
|
252
|
+
{ kpi: 'Total headcount', measure: 'employees', reduce: 'sum', format: 'compact', span: 1 },
|
|
253
|
+
{ kpi: 'Avg headcount', measure: 'employees', reduce: 'avg', span: 1 },
|
|
254
|
+
{ gauge: 'Avg account health', measure: 'health', reduce: 'avg', min: 0, max: 5, span: 1 },
|
|
255
|
+
{ chart: 'industry', measure: 'employees', reduce: 'sum', type: 'bar', span: 2 },
|
|
256
|
+
{ chart: 'industry', reduce: 'count', type: 'pie', span: 1 },
|
|
257
|
+
{ filter: ['industry', 'country', 'active'], span: 3 },
|
|
258
|
+
{ grid: true, format: statusPills(companies, 'industry'), summaries: true, rowLink: { screen: 'deals', sourceField: 'id', targetField: 'companyId' }, span: 3 },
|
|
259
|
+
]),
|
|
260
|
+
// Activity board: task KPIs, a duration gauge, type/owner breakdowns, a filter,
|
|
261
|
+
// and a grid with type + priority pills and inline edit actions.
|
|
262
|
+
dashScreen(activities, { id: 'activities', title: 'Activities', order: 6 }, [
|
|
263
|
+
{ kpi: 'Activities', reduce: 'count', span: 1 },
|
|
264
|
+
{ kpi: 'Logged minutes', measure: 'durationMins', reduce: 'sum', format: 'compact', span: 1 },
|
|
265
|
+
{ kpi: 'Avg duration', measure: 'durationMins', reduce: 'avg', span: 1 },
|
|
266
|
+
{ gauge: 'Avg duration (min)', measure: 'durationMins', reduce: 'avg', min: 0, max: 120, unit: 'm', span: 1 },
|
|
267
|
+
{ chart: 'type', reduce: 'count', type: 'pie', span: 1 },
|
|
268
|
+
{ chart: 'owner', reduce: 'count', type: 'bar', span: 2 },
|
|
269
|
+
{ filter: ['type', 'priority', 'owner', 'completed'], span: 3 },
|
|
270
|
+
{ grid: true, format: [...statusPills(activities, 'type'), ...statusPills(activities, 'priority'), ...statusPills(activities, 'owner')], summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
271
|
+
]),
|
|
272
|
+
// Activity schedule: the activities grid rendered as a resource scheduler -
|
|
273
|
+
// a column per owner (rep), tasks placed by due date and tinted by type, with
|
|
274
|
+
// drag-to-reschedule + a detail drawer. Showcases the grid's scheduler view.
|
|
275
|
+
schedulerScreen(activities, { id: 'activity-cal', title: 'Schedule', order: 7 }, { startField: 'dueDate', titleField: 'subject', colorField: 'type', resourceField: 'owner', initialView: 'week' }),
|
|
276
|
+
screen(companies, 'master-detail', { id: 'companies', title: 'Companies', order: 8, child: contacts, foreignKey: 'companyId' }),
|
|
277
|
+
formScreen(contacts, { id: 'contacts', title: 'Contacts', order: 9 }, undefined, {}, ['companyId', 'tags', 'active']),
|
|
278
|
+
formScreen(deals, { id: 'deals', title: 'Deals', order: 10 }, undefined, { format: dealFormats, summaries: true, rowActions: [{ kind: 'edit' }], rowLink: { screen: 'deal-360', sourceField: 'id', targetField: 'id' } }, ['stage', 'owner', 'companyId']),
|
|
279
|
+
],
|
|
280
|
+
})
|
|
281
|
+
},
|
|
282
|
+
}
|