@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,186 +1,213 @@
|
|
|
1
|
-
import type { EntitySchema } from '../../schema.js'
|
|
2
|
-
import type { FormatRule } from '../project.js'
|
|
3
|
-
import { screen, formScreen, schedulerScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ field: '
|
|
26
|
-
{ field: '
|
|
27
|
-
{ field: '
|
|
28
|
-
{ field: '
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{ field: '
|
|
38
|
-
{ field: '
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{ field: '
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{ field: '
|
|
65
|
-
{ field: '
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ id: '
|
|
84
|
-
{ id: '
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{ id: '
|
|
88
|
-
{ id: '
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{ id: '
|
|
92
|
-
{ id: '
|
|
93
|
-
{ id: '
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
}
|
|
1
|
+
import type { EntitySchema } from '../../schema.js'
|
|
2
|
+
import type { FormatRule } from '../project.js'
|
|
3
|
+
import { screen, formScreen, schedulerScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
4
|
+
|
|
5
|
+
// Industrial control-console chrome (the look of a LabVIEW / SystemLink operations
|
|
6
|
+
// dashboard): a near-black rail, uppercase monospace nav labels, a cyan LED-style
|
|
7
|
+
// active indicator, and a faint measurement grid over the content.
|
|
8
|
+
const CONTROL_CSS = `.sv-app.theme-control .sv-app__side { background: #0a0e14; border-right: 1px solid #1b2430; }
|
|
9
|
+
.sv-app.theme-control .sv-app__brandtext { color: #e6f6ff; font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; text-transform: uppercase; letter-spacing: 0.08em; font-size: 14px; }
|
|
10
|
+
.sv-app.theme-control .sv-app__link { color: #9fb3c8; font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; border-radius: 4px; padding: 7px 12px; }
|
|
11
|
+
.sv-app.theme-control .sv-app__link:hover { background: rgba(34, 211, 238, 0.08); color: #e6f6ff; }
|
|
12
|
+
.sv-app.theme-control .sv-app__link.is-active { background: rgba(34, 211, 238, 0.14); color: #67e8f9; box-shadow: inset 3px 0 0 #22d3ee; }
|
|
13
|
+
.sv-app.theme-control .sv-app__collapse, .sv-app.theme-control .sv-app__foot { color: #64798f; }
|
|
14
|
+
.sv-app.theme-control .sv-app__toolbar { border-bottom: 1px solid #1b2430; }
|
|
15
|
+
.sv-app.theme-control .sv-app__content { background-image: linear-gradient(rgba(34, 211, 238, 0.045) 1px, transparent 1px), linear-gradient(90deg, rgba(34, 211, 238, 0.045) 1px, transparent 1px); background-size: 28px 28px; }`
|
|
16
|
+
|
|
17
|
+
const drivers: EntitySchema = {
|
|
18
|
+
name: 'drivers',
|
|
19
|
+
label: 'Driver',
|
|
20
|
+
idField: 'id',
|
|
21
|
+
fields: [
|
|
22
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
23
|
+
{ field: 'name', type: 'text', required: true, minLength: 2 },
|
|
24
|
+
{ field: 'phone', type: 'text', input: { editorType: 'phone', help: 'Mobile contact number' } },
|
|
25
|
+
{ field: 'email', type: 'text', format: 'email', required: true, input: { placeholder: 'name@fleetly.com' } },
|
|
26
|
+
{ field: 'license', type: 'text', label: 'License', input: { editorType: 'mask', mask: 'AA-######', help: 'State code + 6 digits' } },
|
|
27
|
+
{ field: 'rating', type: 'number', label: 'Safety rating', min: 1, max: 5, defaultValue: 3, input: { editorType: 'rating', help: '1-5 safety score' } },
|
|
28
|
+
{ field: 'hireDate', type: 'date', label: 'Hire date' },
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const vehicles: EntitySchema = {
|
|
33
|
+
name: 'vehicles',
|
|
34
|
+
label: 'Vehicle',
|
|
35
|
+
idField: 'id',
|
|
36
|
+
fields: [
|
|
37
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
38
|
+
{ field: 'name', type: 'text', required: true, minLength: 2 },
|
|
39
|
+
{ field: 'vin', type: 'text', label: 'VIN', input: { editorType: 'mask', mask: '*****************', help: '17-character vehicle identification number' } },
|
|
40
|
+
{ field: 'plate', type: 'text', label: 'Plate', input: { editorType: 'mask', mask: 'AAA-####', help: 'Fleet plate, e.g. FLT-1201' } },
|
|
41
|
+
{ field: 'type', type: 'enum', required: true, defaultValue: 'van', options: [
|
|
42
|
+
{ value: 'van', label: 'Van', color: '#6366f1' },
|
|
43
|
+
{ value: 'truck', label: 'Truck', color: '#10b981' },
|
|
44
|
+
{ value: 'car', label: 'Car', color: '#f59e0b' },
|
|
45
|
+
{ value: 'bike', label: 'Bike', color: '#0ea5e9' },
|
|
46
|
+
] },
|
|
47
|
+
{ field: 'year', type: 'number', label: 'Year', min: 1990, max: 2030 },
|
|
48
|
+
{ field: 'mileage', type: 'number', label: 'Mileage', min: 0, input: { help: 'Odometer reading' } },
|
|
49
|
+
{ field: 'fuelLevel', type: 'number', label: 'Fuel level (%)', min: 0, max: 100, defaultValue: 100, input: { editorType: 'slider', help: 'Current tank level' } },
|
|
50
|
+
{ field: 'status', type: 'enum', required: true, defaultValue: 'available', options: [
|
|
51
|
+
{ value: 'available', label: 'Available', color: '#10b981' },
|
|
52
|
+
{ value: 'on_trip', label: 'On trip', color: '#0ea5e9' },
|
|
53
|
+
{ value: 'maintenance', label: 'Maintenance', color: '#ef4444' },
|
|
54
|
+
] },
|
|
55
|
+
{ field: 'color', type: 'text', label: 'Livery color', input: { editorType: 'color', help: 'Fleet livery / map color' } },
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const trips: EntitySchema = {
|
|
60
|
+
name: 'trips',
|
|
61
|
+
label: 'Trip',
|
|
62
|
+
idField: 'id',
|
|
63
|
+
fields: [
|
|
64
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
65
|
+
{ field: 'vehicleId', type: 'relation', label: 'Vehicle', required: true, relation: { entity: 'vehicles', foreignKey: 'vehicleId', labelField: 'name' } },
|
|
66
|
+
{ field: 'driverId', type: 'relation', label: 'Driver', required: true, relation: { entity: 'drivers', foreignKey: 'driverId', labelField: 'name' } },
|
|
67
|
+
{ field: 'status', type: 'enum', required: true, defaultValue: 'planned', options: [
|
|
68
|
+
{ value: 'planned', label: 'Planned', color: '#94a3b8' },
|
|
69
|
+
{ value: 'ongoing', label: 'Ongoing', color: '#0ea5e9' },
|
|
70
|
+
{ value: 'done', label: 'Done', color: '#10b981' },
|
|
71
|
+
{ value: 'cancelled', label: 'Cancelled', color: '#ef4444' },
|
|
72
|
+
] },
|
|
73
|
+
{ field: 'distanceKm', type: 'number', label: 'Distance (km)', min: 0, input: { help: 'Route distance in kilometers' } },
|
|
74
|
+
{ field: 'cost', type: 'number', label: 'Cost ($)', min: 0 },
|
|
75
|
+
// Derived: cost efficiency per kilometer (display-only, never stored).
|
|
76
|
+
{ field: 'costPerKm', type: 'number', label: 'Cost / km ($)', readonly: true, formula: 'cost / distanceKm' },
|
|
77
|
+
{ field: 'startAt', type: 'datetime', label: 'Start', input: { editorType: 'datetime', help: 'Scheduled departure time' } },
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const seed = {
|
|
82
|
+
drivers: [
|
|
83
|
+
{ id: 'dr1', name: 'Miguel Santos', phone: '+1 (415) 555-0201', email: 'miguel@fleetly.com', license: 'CA-482910', rating: 5, hireDate: '2021-03-15' },
|
|
84
|
+
{ id: 'dr2', name: 'Aisha Khan', phone: '+1 (312) 555-0202', email: 'aisha@fleetly.com', license: 'IL-118273', rating: 4, hireDate: '2022-07-01' },
|
|
85
|
+
{ id: 'dr3', name: 'Tom Becker', phone: '+1 (206) 555-0203', email: 'tom@fleetly.com', license: 'WA-905641', rating: 4, hireDate: '2020-11-20' },
|
|
86
|
+
{ id: 'dr4', name: 'Lena Novak', phone: '+1 (720) 555-0204', email: 'lena@fleetly.com', license: 'CO-337882', rating: 3, hireDate: '2023-02-10' },
|
|
87
|
+
{ id: 'dr5', name: 'Raj Mehta', phone: '+1 (646) 555-0205', email: 'raj@fleetly.com', license: 'NY-556201', rating: 5, hireDate: '2019-09-05' },
|
|
88
|
+
{ id: 'dr6', name: 'Sofia Rossi', phone: '+1 (305) 555-0206', email: 'sofia@fleetly.com', license: 'FL-774519', rating: 4, hireDate: '2024-01-22' },
|
|
89
|
+
],
|
|
90
|
+
vehicles: [
|
|
91
|
+
{ id: 've1', name: 'Sprinter 1', vin: '1HGCM82633A004352', plate: 'FLT-1201', type: 'van', year: 2021, mileage: 82000, fuelLevel: 80, status: 'available', color: '#0284c7' },
|
|
92
|
+
{ id: 've2', name: 'Hauler 2', vin: '2FMDK48C97BB12345', plate: 'FLT-1202', type: 'truck', year: 2019, mileage: 154000, fuelLevel: 45, status: 'on_trip', color: '#dc2626' },
|
|
93
|
+
{ id: 've3', name: 'Sedan 3', vin: '3VWFE21C04M001234', plate: 'FLT-1203', type: 'car', year: 2022, mileage: 41000, fuelLevel: 95, status: 'available', color: '#0f172a' },
|
|
94
|
+
{ id: 've4', name: 'Courier 4', vin: '5UXWX7C5XG0R12345', plate: 'FLT-1204', type: 'bike', year: 2023, mileage: 12000, fuelLevel: 60, status: 'maintenance', color: '#f59e0b' },
|
|
95
|
+
{ id: 've5', name: 'Hauler 5', vin: '1FTFW1ET5DFA12345', plate: 'FLT-1205', type: 'truck', year: 2020, mileage: 132000, fuelLevel: 30, status: 'on_trip', color: '#10b981' },
|
|
96
|
+
{ id: 've6', name: 'Sprinter 6', vin: '1GNEK13ZX3R123456', plate: 'FLT-1206', type: 'van', year: 2021, mileage: 76000, fuelLevel: 70, status: 'available', color: '#6366f1' },
|
|
97
|
+
],
|
|
98
|
+
trips: [
|
|
99
|
+
{ id: 'tp1', vehicleId: 've1', driverId: 'dr1', status: 'done', distanceKm: 120, cost: 180, startAt: '2026-07-08T08:30:00' },
|
|
100
|
+
{ id: 'tp2', vehicleId: 've2', driverId: 'dr2', status: 'ongoing', distanceKm: 340, cost: 520, startAt: '2026-07-12T06:15:00' },
|
|
101
|
+
{ id: 'tp3', vehicleId: 've3', driverId: 'dr3', status: 'planned', distanceKm: 60, cost: 90, startAt: '2026-07-15T09:00:00' },
|
|
102
|
+
{ id: 'tp4', vehicleId: 've4', driverId: 'dr4', status: 'cancelled', distanceKm: 15, cost: 25, startAt: '2026-07-09T14:45:00' },
|
|
103
|
+
{ id: 'tp5', vehicleId: 've5', driverId: 'dr5', status: 'ongoing', distanceKm: 410, cost: 640, startAt: '2026-07-13T05:30:00' },
|
|
104
|
+
{ id: 'tp6', vehicleId: 've1', driverId: 'dr6', status: 'done', distanceKm: 95, cost: 140, startAt: '2026-07-11T11:20:00' },
|
|
105
|
+
{ id: 'tp7', vehicleId: 've2', driverId: 'dr1', status: 'done', distanceKm: 275, cost: 430, startAt: '2026-07-10T07:00:00' },
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Vehicle grid formatting: type + status pills, plus numeric thresholds (low fuel
|
|
110
|
+
// red + bold, healthy fuel green, high-mileage vehicles bold) - data-driven color,
|
|
111
|
+
// not just status pills.
|
|
112
|
+
const vehicleFormats: FormatRule[] = [
|
|
113
|
+
...statusPills(vehicles, 'status'),
|
|
114
|
+
...statusPills(vehicles, 'type'),
|
|
115
|
+
{ field: 'fuelLevel', op: 'lt', value: 25, color: '#dc2626', bold: true },
|
|
116
|
+
{ field: 'fuelLevel', op: 'gte', value: 75, color: '#16a34a' },
|
|
117
|
+
{ field: 'mileage', op: 'gte', value: 150000, bold: true },
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
// Trip grid formatting: status pills, plus expensive trips bold and long-haul
|
|
121
|
+
// distances highlighted.
|
|
122
|
+
const tripFormats: FormatRule[] = [
|
|
123
|
+
...statusPills(trips, 'status'),
|
|
124
|
+
{ field: 'cost', op: 'gte', value: 500, bold: true },
|
|
125
|
+
{ field: 'distanceKm', op: 'gte', value: 300, color: '#0284c7', bold: true },
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
export const fleet: SampleApp = {
|
|
129
|
+
id: 'fleet',
|
|
130
|
+
name: 'Fleet',
|
|
131
|
+
description: 'Vehicles, drivers and trips - a LabVIEW / SystemLink-style operations console behind a sign-in, with fleet + trips dashboards (mileage/cost KPIs with a startAt sparkline, a fuel-level gauge, a type x status pivot, a cost-over-time area trend, tabbed breakdowns), filtered status-pill grids with row actions, master/detail, and rich edit forms (phone, license/VIN/plate masks, safety rating, fuel slider, livery color, datetime).',
|
|
132
|
+
emoji: '\u{1F69A}',
|
|
133
|
+
accent: '#38bdf8',
|
|
134
|
+
build: () => {
|
|
135
|
+
const driverRows = pad(drivers, seed.drivers, 12)
|
|
136
|
+
const vehicleRows = pad(vehicles, seed.vehicles, 14)
|
|
137
|
+
const tripRows = pad(trips, seed.trips, 30, { vehicleId: ids(vehicleRows), driverId: ids(driverRows) })
|
|
138
|
+
return project({
|
|
139
|
+
title: 'Fleetly',
|
|
140
|
+
brand: 'Fleet Ops',
|
|
141
|
+
accent: '#22d3ee',
|
|
142
|
+
preset: 'carbon',
|
|
143
|
+
mode: 'dark',
|
|
144
|
+
footer: '',
|
|
145
|
+
// A LabVIEW / SystemLink-style operations console: dark control-room chrome.
|
|
146
|
+
appClass: 'theme-control',
|
|
147
|
+
customCss: CONTROL_CSS,
|
|
148
|
+
// Sign-in with two roles. Operators get the dispatch floor (and cannot delete);
|
|
149
|
+
// the Trips cost dashboard and the driver roster are manager-only, so they drop
|
|
150
|
+
// out of an operator's nav entirely.
|
|
151
|
+
auth: { enabled: true, protect: true },
|
|
152
|
+
access: {
|
|
153
|
+
enabled: true,
|
|
154
|
+
defaultRole: 'operator',
|
|
155
|
+
roles: [
|
|
156
|
+
{ role: 'manager', screens: '*', actions: '*' },
|
|
157
|
+
{ role: 'operator', screens: ['overview', 'dispatch', 'vehicle-detail', 'vehicles', 'trip-forms'], actions: ['create', 'update'] },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
entities: [drivers, vehicles, trips],
|
|
161
|
+
seed: { drivers: driverRows, vehicles: vehicleRows, trips: tripRows },
|
|
162
|
+
screens: [
|
|
163
|
+
// Fleet dashboard: fleet-size + mileage KPIs, a fuel-level gauge (avg tank
|
|
164
|
+
// 0-100), mileage-by-type + status breakdowns, a type x status pivot, a
|
|
165
|
+
// tabbed view, and a status-pill vehicle grid with inline edit actions.
|
|
166
|
+
dashScreen(vehicles, { id: 'overview', title: 'Fleet', order: 0 }, [
|
|
167
|
+
{ kpi: 'Vehicles', reduce: 'count', span: 1 },
|
|
168
|
+
{ kpi: 'Total mileage', measure: 'mileage', reduce: 'sum', format: 'compact', span: 1 },
|
|
169
|
+
{ kpi: 'Avg mileage', measure: 'mileage', reduce: 'avg', format: 'compact', span: 1 },
|
|
170
|
+
{ gauge: 'Avg fuel level', measure: 'fuelLevel', reduce: 'avg', min: 0, max: 100, unit: '%', span: 1 },
|
|
171
|
+
{ chart: 'type', measure: 'mileage', reduce: 'sum', type: 'bar', span: 2 },
|
|
172
|
+
{ chart: 'status', reduce: 'count', type: 'pie', span: 1 },
|
|
173
|
+
{ pivot: { rows: ['type'], cols: ['status'], aggregate: 'count' }, span: 3 },
|
|
174
|
+
{ tabs: [
|
|
175
|
+
{ label: 'Mileage by type', tiles: [{ chart: 'type', measure: 'mileage', reduce: 'sum', type: 'bar' }] },
|
|
176
|
+
{ label: 'Vehicles by status', tiles: [{ chart: 'status', reduce: 'count', type: 'bar' }] },
|
|
177
|
+
], span: 3 },
|
|
178
|
+
{ grid: true, format: vehicleFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
179
|
+
]),
|
|
180
|
+
// Trips dashboard: distance + cost KPIs (both trending over start time via a
|
|
181
|
+
// sparkline), an avg-distance gauge, a cost-over-time area trend, a status
|
|
182
|
+
// pie, a tabbed breakdown, a faceted filter, and a status-pill trip grid.
|
|
183
|
+
dashScreen(trips, { id: 'trips-dashboard', title: 'Trips', order: 1 }, [
|
|
184
|
+
{ kpi: 'Trips', reduce: 'count', span: 1 },
|
|
185
|
+
{ kpi: 'Total distance', measure: 'distanceKm', reduce: 'sum', format: 'compact', trendField: 'startAt', trendReduce: 'sum', span: 1 },
|
|
186
|
+
{ kpi: 'Total cost', measure: 'cost', reduce: 'sum', format: 'currency', trendField: 'startAt', trendReduce: 'sum', span: 1 },
|
|
187
|
+
{ kpi: 'Avg cost', measure: 'cost', reduce: 'avg', format: 'currency', span: 1 },
|
|
188
|
+
{ gauge: 'Avg distance (km)', measure: 'distanceKm', reduce: 'avg', min: 0, max: 500, unit: 'km', span: 1 },
|
|
189
|
+
{ chart: 'startAt', measure: 'cost', reduce: 'sum', type: 'area', span: 2 },
|
|
190
|
+
{ chart: 'status', reduce: 'count', type: 'pie', span: 1 },
|
|
191
|
+
{ tabs: [
|
|
192
|
+
{ label: 'Cost by status', tiles: [{ chart: 'status', measure: 'cost', reduce: 'sum', type: 'bar' }] },
|
|
193
|
+
{ label: 'Distance by status', tiles: [{ chart: 'status', measure: 'distanceKm', reduce: 'sum', type: 'bar' }] },
|
|
194
|
+
], span: 3 },
|
|
195
|
+
{ filter: ['status', 'vehicleId', 'driverId'], span: 3 },
|
|
196
|
+
{ grid: true, format: tripFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
197
|
+
]),
|
|
198
|
+
// Resource scheduler: a column per vehicle, trips placed by time + tinted by status,
|
|
199
|
+
// drag-to-reassign a vehicle + a detail drawer (week view). Was a month calendar.
|
|
200
|
+
schedulerScreen(trips, { id: 'dispatch', title: 'Dispatch', order: 2 }, { startField: 'startAt', titleField: 'driverId', colorField: 'status', resourceField: 'vehicleId', initialView: 'week' }),
|
|
201
|
+
// Vehicle detail: asset header (status pill) + mileage / fuel tiles with a
|
|
202
|
+
// trip-history timeline (each trip titled by its driver, colored by status).
|
|
203
|
+
detailScreen(vehicles, { id: 'vehicle-detail', title: 'Vehicle detail', order: 3 }, {
|
|
204
|
+
titleField: 'name', subtitleField: 'plate', statusField: 'status', metricFields: ['mileage', 'fuelLevel'],
|
|
205
|
+
related: [{ entity: 'trips', foreignKey: 'vehicleId', label: 'Trips', titleField: 'driverId', subtitleField: 'distanceKm', dateField: 'startAt', statusField: 'status' }],
|
|
206
|
+
}),
|
|
207
|
+
screen(vehicles, 'master-detail', { id: 'vehicles', title: 'Vehicles', order: 4, child: trips, foreignKey: 'vehicleId', linkScreen: 'vehicle-detail' }),
|
|
208
|
+
formScreen(trips, { id: 'trip-forms', title: 'Trip forms', order: 5 }, ['vehicleId', 'driverId', 'status'], { format: tripFormats, summaries: true, rowActions: [{ kind: 'edit' }] }, ['status', 'vehicleId', 'driverId']),
|
|
209
|
+
formScreen(drivers, { id: 'drivers', title: 'Drivers', order: 6 }, ['name', 'phone', 'rating'], { summaries: true }, ['rating']),
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
},
|
|
213
|
+
}
|