@svgrid/enterprise 2.2.1 → 2.3.1

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.
Files changed (126) hide show
  1. package/README.md +95 -81
  2. package/dist/cdn/svgrid-enterprise.svelte-external.js +26642 -11252
  3. package/dist/designer/assets/GridMenus-BtWVk9Ab.js +7 -0
  4. package/dist/designer/assets/SvGridChartPanel-DWlBO2SD.js +10 -0
  5. package/dist/designer/assets/SvGridChartView-8c8Mb4j8.js +1 -0
  6. package/dist/designer/assets/SvGridChartView-DX9HfkBR.css +1 -0
  7. package/dist/designer/assets/index-DsDgp9Xq.js +78758 -0
  8. package/dist/designer/assets/index-tTY_Dx4P.css +1 -0
  9. package/dist/designer/assets/jszip.min-fkJdmAmj.js +2 -0
  10. package/dist/designer/assets/pdfmake-DeCsnyl9.js +242 -0
  11. package/dist/designer/assets/smart.export-BZlSCE8T.js +35 -0
  12. package/dist/designer/assets/vfs_fonts-eX2NpmfX.js +1 -0
  13. package/dist/designer/index.html +13 -0
  14. package/dist/node/studio.js +15304 -2708
  15. package/package.json +35 -11
  16. package/src/SvAlertRuleEditor.svelte +294 -0
  17. package/src/SvAlertsManager.svelte +210 -0
  18. package/src/SvAlertsPanel.svelte +129 -0
  19. package/src/SvBoard.svelte +3 -1
  20. package/src/SvExpressionEditor.svelte +341 -0
  21. package/src/SvGridAlerts.dom.test.ts +113 -0
  22. package/src/SvGridAlerts.svelte +265 -0
  23. package/src/SvGridBoard.svelte +2358 -0
  24. package/src/SvGridEditPanel.svelte +849 -794
  25. package/src/SvGridScheduler.svelte +5358 -4410
  26. package/src/SvPivotDesigner.svelte +3 -3
  27. package/src/SvRecordDetail.svelte +6 -2
  28. package/src/SvSchedule.svelte +3 -1
  29. package/src/{ai-export-pdf.test.ts → ai-export-pdf.dom.test.ts} +77 -74
  30. package/src/{ai-export-xlsx.test.ts → ai-export-xlsx.dom.test.ts} +90 -87
  31. package/src/{ai-export.test.ts → ai-export.dom.test.ts} +114 -110
  32. package/src/alerts/alert-engine-attach.ts +165 -0
  33. package/src/alerts/alert-engine.test.ts +135 -0
  34. package/src/alerts/alert-engine.ts +260 -0
  35. package/src/alerts/alert-formats.test.ts +87 -0
  36. package/src/alerts/alert-formats.ts +77 -0
  37. package/src/alerts/alert-observer.test.ts +189 -0
  38. package/src/alerts/alert-observer.ts +208 -0
  39. package/src/alerts/alert-scheduler.ts +96 -0
  40. package/src/alerts/alert-storage.test.ts +54 -0
  41. package/src/alerts/alert-storage.ts +116 -0
  42. package/src/alerts/alert-store.svelte.ts +80 -0
  43. package/src/alerts/alert-types.ts +94 -0
  44. package/src/alerts.ts +28 -0
  45. package/src/board.dom.test.ts +941 -0
  46. package/src/board.ts +36 -0
  47. package/src/export-ooxml.ts +4 -0
  48. package/src/export-xls.ts +3 -0
  49. package/src/export.ts +6 -0
  50. package/src/expressions/evaluate.test.ts +111 -0
  51. package/src/expressions/evaluate.ts +237 -0
  52. package/src/expressions/expression-columns.ts +133 -0
  53. package/src/expressions/expression-types.ts +84 -0
  54. package/src/expressions/parse.test.ts +106 -0
  55. package/src/expressions/parse.ts +610 -0
  56. package/src/import.test.ts +1 -1
  57. package/src/import.ts +3 -1
  58. package/src/index.ts +192 -46
  59. package/src/install.ts +12 -2
  60. package/src/pivot-enable.ts +44 -0
  61. package/src/pivot.test.ts +0 -1
  62. package/src/scheduler-assignments.test.ts +97 -0
  63. package/src/scheduler-assignments.ts +134 -0
  64. package/src/scheduler-axis.test.ts +108 -0
  65. package/src/scheduler-axis.ts +238 -0
  66. package/src/scheduler-booking.test.ts +57 -0
  67. package/src/scheduler-booking.ts +63 -0
  68. package/src/scheduler-config.ts +179 -0
  69. package/src/scheduler-dependencies.test.ts +155 -0
  70. package/src/scheduler-dependencies.ts +223 -0
  71. package/src/scheduler-freebusy.test.ts +41 -0
  72. package/src/scheduler-freebusy.ts +36 -0
  73. package/src/scheduler-heatmap.test.ts +39 -0
  74. package/src/scheduler-heatmap.ts +55 -0
  75. package/src/scheduler-resource-tree.test.ts +84 -0
  76. package/src/scheduler-resource-tree.ts +107 -0
  77. package/src/scheduler-slots.test.ts +53 -0
  78. package/src/scheduler-slots.ts +94 -0
  79. package/src/scheduler-summary.test.ts +47 -0
  80. package/src/scheduler-summary.ts +81 -0
  81. package/src/schema-designer.ts +1 -1
  82. package/src/sources/index.ts +1 -1
  83. package/src/sources/introspect-supabase.test.ts +13 -1
  84. package/src/sources/introspect-supabase.ts +20 -0
  85. package/src/studio/copilot-core.test.ts +45 -0
  86. package/src/studio/copilot-core.ts +65 -0
  87. package/src/studio/deploy-cli.test.ts +56 -0
  88. package/src/studio/deploy-cli.ts +100 -0
  89. package/src/studio/emit-project.test.ts +800 -24
  90. package/src/studio/emit-project.ts +1307 -135
  91. package/src/studio/emit-schema.test.ts +17 -0
  92. package/src/studio/emit-schema.ts +182 -41
  93. package/src/studio/index.ts +59 -1
  94. package/src/studio/init-flow.test.ts +239 -0
  95. package/src/studio/init-flow.ts +358 -0
  96. package/src/studio/introspect-openapi.test.ts +84 -0
  97. package/src/studio/introspect-openapi.ts +252 -0
  98. package/src/studio/project.test.ts +57 -1
  99. package/src/studio/project.ts +302 -8
  100. package/src/studio/samples/crm.ts +282 -258
  101. package/src/studio/samples/datasets.test.ts +84 -0
  102. package/src/studio/samples/datasets.ts +340 -0
  103. package/src/studio/samples/fleet.ts +213 -186
  104. package/src/studio/samples/insurance.ts +223 -195
  105. package/src/studio/samples/inventory.ts +213 -182
  106. package/src/studio/samples/live-data.test.ts +99 -98
  107. package/src/studio/samples/live-data.ts +8 -10
  108. package/src/studio/samples/projects.ts +212 -190
  109. package/src/studio/samples/samples.test.ts +268 -216
  110. package/src/studio/samples/shared.ts +22 -145
  111. package/src/studio/samples/starter.ts +251 -0
  112. package/src/studio/samples/support.ts +209 -184
  113. package/src/studio/screen-suites.test.ts +226 -0
  114. package/src/studio/screen-suites.ts +445 -0
  115. package/src/studio/ui-components-surface.test.ts +185 -0
  116. package/src/studio/ui-components.generated.ts +7154 -0
  117. package/src/studio/ui-components.ts +273 -3
  118. package/src/sveltekit/index.ts +1 -0
  119. package/src/sveltekit/sql-source.test.ts +13 -0
  120. package/src/sveltekit/sql-source.ts +12 -7
  121. package/src/sveltekit/transport-scope.test.ts +125 -0
  122. package/src/sveltekit/transport.ts +76 -2
  123. package/src/upgrade-prompt.ts +2 -2
  124. package/src/watermark.ts +2 -2
  125. package/src/ai.test.ts +0 -522
  126. 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
- const drivers: EntitySchema = {
6
- name: 'drivers',
7
- label: 'Driver',
8
- idField: 'id',
9
- fields: [
10
- { field: 'id', type: 'text', primaryKey: true, readonly: true },
11
- { field: 'name', type: 'text', required: true, minLength: 2 },
12
- { field: 'phone', type: 'text', input: { editorType: 'phone', help: 'Mobile contact number' } },
13
- { field: 'email', type: 'text', format: 'email', required: true, input: { placeholder: 'name@fleetly.com' } },
14
- { field: 'license', type: 'text', label: 'License', input: { editorType: 'mask', mask: 'AA-######', help: 'State code + 6 digits' } },
15
- { field: 'rating', type: 'number', label: 'Safety rating', min: 1, max: 5, defaultValue: 3, input: { editorType: 'rating', help: '1-5 safety score' } },
16
- { field: 'hireDate', type: 'date', label: 'Hire date' },
17
- ],
18
- }
19
-
20
- const vehicles: EntitySchema = {
21
- name: 'vehicles',
22
- label: 'Vehicle',
23
- idField: 'id',
24
- fields: [
25
- { field: 'id', type: 'text', primaryKey: true, readonly: true },
26
- { field: 'name', type: 'text', required: true, minLength: 2 },
27
- { field: 'vin', type: 'text', label: 'VIN', input: { editorType: 'mask', mask: '*****************', help: '17-character vehicle identification number' } },
28
- { field: 'plate', type: 'text', label: 'Plate', input: { editorType: 'mask', mask: 'AAA-####', help: 'Fleet plate, e.g. FLT-1201' } },
29
- { field: 'type', type: 'enum', required: true, defaultValue: 'van', options: [
30
- { value: 'van', label: 'Van', color: '#6366f1' },
31
- { value: 'truck', label: 'Truck', color: '#10b981' },
32
- { value: 'car', label: 'Car', color: '#f59e0b' },
33
- { value: 'bike', label: 'Bike', color: '#0ea5e9' },
34
- ] },
35
- { field: 'year', type: 'number', label: 'Year', min: 1990, max: 2030 },
36
- { field: 'mileage', type: 'number', label: 'Mileage', min: 0, input: { help: 'Odometer reading' } },
37
- { field: 'fuelLevel', type: 'number', label: 'Fuel level (%)', min: 0, max: 100, defaultValue: 100, input: { editorType: 'slider', help: 'Current tank level' } },
38
- { field: 'status', type: 'enum', required: true, defaultValue: 'available', options: [
39
- { value: 'available', label: 'Available', color: '#10b981' },
40
- { value: 'on_trip', label: 'On trip', color: '#0ea5e9' },
41
- { value: 'maintenance', label: 'Maintenance', color: '#ef4444' },
42
- ] },
43
- { field: 'color', type: 'text', label: 'Livery color', input: { editorType: 'color', help: 'Fleet livery / map color' } },
44
- ],
45
- }
46
-
47
- const trips: EntitySchema = {
48
- name: 'trips',
49
- label: 'Trip',
50
- idField: 'id',
51
- fields: [
52
- { field: 'id', type: 'text', primaryKey: true, readonly: true },
53
- { field: 'vehicleId', type: 'relation', label: 'Vehicle', required: true, relation: { entity: 'vehicles', foreignKey: 'vehicleId', labelField: 'name' } },
54
- { field: 'driverId', type: 'relation', label: 'Driver', required: true, relation: { entity: 'drivers', foreignKey: 'driverId', labelField: 'name' } },
55
- { field: 'status', type: 'enum', required: true, defaultValue: 'planned', options: [
56
- { value: 'planned', label: 'Planned', color: '#94a3b8' },
57
- { value: 'ongoing', label: 'Ongoing', color: '#0ea5e9' },
58
- { value: 'done', label: 'Done', color: '#10b981' },
59
- { value: 'cancelled', label: 'Cancelled', color: '#ef4444' },
60
- ] },
61
- { field: 'distanceKm', type: 'number', label: 'Distance (km)', min: 0, input: { help: 'Route distance in kilometers' } },
62
- { field: 'cost', type: 'number', label: 'Cost ($)', min: 0 },
63
- // Derived: cost efficiency per kilometer (display-only, never stored).
64
- { field: 'costPerKm', type: 'number', label: 'Cost / km ($)', readonly: true, formula: 'cost / distanceKm' },
65
- { field: 'startAt', type: 'datetime', label: 'Start', input: { editorType: 'datetime', help: 'Scheduled departure time' } },
66
- ],
67
- }
68
-
69
- const seed = {
70
- drivers: [
71
- { id: 'dr1', name: 'Miguel Santos', phone: '+1 (415) 555-0201', email: 'miguel@fleetly.com', license: 'CA-482910', rating: 5, hireDate: '2021-03-15' },
72
- { id: 'dr2', name: 'Aisha Khan', phone: '+1 (312) 555-0202', email: 'aisha@fleetly.com', license: 'IL-118273', rating: 4, hireDate: '2022-07-01' },
73
- { id: 'dr3', name: 'Tom Becker', phone: '+1 (206) 555-0203', email: 'tom@fleetly.com', license: 'WA-905641', rating: 4, hireDate: '2020-11-20' },
74
- { id: 'dr4', name: 'Lena Novak', phone: '+1 (720) 555-0204', email: 'lena@fleetly.com', license: 'CO-337882', rating: 3, hireDate: '2023-02-10' },
75
- { id: 'dr5', name: 'Raj Mehta', phone: '+1 (646) 555-0205', email: 'raj@fleetly.com', license: 'NY-556201', rating: 5, hireDate: '2019-09-05' },
76
- { id: 'dr6', name: 'Sofia Rossi', phone: '+1 (305) 555-0206', email: 'sofia@fleetly.com', license: 'FL-774519', rating: 4, hireDate: '2024-01-22' },
77
- ],
78
- vehicles: [
79
- { id: 've1', name: 'Sprinter 1', vin: '1HGCM82633A004352', plate: 'FLT-1201', type: 'van', year: 2021, mileage: 82000, fuelLevel: 80, status: 'available', color: '#0284c7' },
80
- { id: 've2', name: 'Hauler 2', vin: '2FMDK48C97BB12345', plate: 'FLT-1202', type: 'truck', year: 2019, mileage: 154000, fuelLevel: 45, status: 'on_trip', color: '#dc2626' },
81
- { id: 've3', name: 'Sedan 3', vin: '3VWFE21C04M001234', plate: 'FLT-1203', type: 'car', year: 2022, mileage: 41000, fuelLevel: 95, status: 'available', color: '#0f172a' },
82
- { id: 've4', name: 'Courier 4', vin: '5UXWX7C5XG0R12345', plate: 'FLT-1204', type: 'bike', year: 2023, mileage: 12000, fuelLevel: 60, status: 'maintenance', color: '#f59e0b' },
83
- { id: 've5', name: 'Hauler 5', vin: '1FTFW1ET5DFA12345', plate: 'FLT-1205', type: 'truck', year: 2020, mileage: 132000, fuelLevel: 30, status: 'on_trip', color: '#10b981' },
84
- { id: 've6', name: 'Sprinter 6', vin: '1GNEK13ZX3R123456', plate: 'FLT-1206', type: 'van', year: 2021, mileage: 76000, fuelLevel: 70, status: 'available', color: '#6366f1' },
85
- ],
86
- trips: [
87
- { id: 'tp1', vehicleId: 've1', driverId: 'dr1', status: 'done', distanceKm: 120, cost: 180, startAt: '2026-07-08T08:30:00' },
88
- { id: 'tp2', vehicleId: 've2', driverId: 'dr2', status: 'ongoing', distanceKm: 340, cost: 520, startAt: '2026-07-12T06:15:00' },
89
- { id: 'tp3', vehicleId: 've3', driverId: 'dr3', status: 'planned', distanceKm: 60, cost: 90, startAt: '2026-07-15T09:00:00' },
90
- { id: 'tp4', vehicleId: 've4', driverId: 'dr4', status: 'cancelled', distanceKm: 15, cost: 25, startAt: '2026-07-09T14:45:00' },
91
- { id: 'tp5', vehicleId: 've5', driverId: 'dr5', status: 'ongoing', distanceKm: 410, cost: 640, startAt: '2026-07-13T05:30:00' },
92
- { id: 'tp6', vehicleId: 've1', driverId: 'dr6', status: 'done', distanceKm: 95, cost: 140, startAt: '2026-07-11T11:20:00' },
93
- { id: 'tp7', vehicleId: 've2', driverId: 'dr1', status: 'done', distanceKm: 275, cost: 430, startAt: '2026-07-10T07:00:00' },
94
- ],
95
- }
96
-
97
- // Vehicle grid formatting: type + status pills, plus numeric thresholds (low fuel
98
- // red + bold, healthy fuel green, high-mileage vehicles bold) - data-driven color,
99
- // not just status pills.
100
- const vehicleFormats: FormatRule[] = [
101
- ...statusPills(vehicles, 'status'),
102
- ...statusPills(vehicles, 'type'),
103
- { field: 'fuelLevel', op: 'lt', value: 25, color: '#dc2626', bold: true },
104
- { field: 'fuelLevel', op: 'gte', value: 75, color: '#16a34a' },
105
- { field: 'mileage', op: 'gte', value: 150000, bold: true },
106
- ]
107
-
108
- // Trip grid formatting: status pills, plus expensive trips bold and long-haul
109
- // distances highlighted.
110
- const tripFormats: FormatRule[] = [
111
- ...statusPills(trips, 'status'),
112
- { field: 'cost', op: 'gte', value: 500, bold: true },
113
- { field: 'distanceKm', op: 'gte', value: 300, color: '#0284c7', bold: true },
114
- ]
115
-
116
- export const fleet: SampleApp = {
117
- id: 'fleet',
118
- name: 'Fleet',
119
- description: 'Vehicles, drivers and trips - 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).',
120
- emoji: '\u{1F69A}',
121
- accent: '#38bdf8',
122
- build: () => {
123
- const driverRows = pad(drivers, seed.drivers, 12)
124
- const vehicleRows = pad(vehicles, seed.vehicles, 14)
125
- const tripRows = pad(trips, seed.trips, 30, { vehicleId: ids(vehicleRows), driverId: ids(driverRows) })
126
- return project({
127
- title: 'Fleetly',
128
- brand: 'Fleetly',
129
- accent: '#38bdf8',
130
- preset: 'carbon',
131
- mode: 'dark',
132
- footer: '',
133
- entities: [drivers, vehicles, trips],
134
- seed: { drivers: driverRows, vehicles: vehicleRows, trips: tripRows },
135
- screens: [
136
- // Fleet dashboard: fleet-size + mileage KPIs, a fuel-level gauge (avg tank
137
- // 0-100), mileage-by-type + status breakdowns, a type x status pivot, a
138
- // tabbed view, and a status-pill vehicle grid with inline edit actions.
139
- dashScreen(vehicles, { id: 'overview', title: 'Fleet', order: 0 }, [
140
- { kpi: 'Vehicles', reduce: 'count', span: 1 },
141
- { kpi: 'Total mileage', measure: 'mileage', reduce: 'sum', format: 'compact', span: 1 },
142
- { kpi: 'Avg mileage', measure: 'mileage', reduce: 'avg', format: 'compact', span: 1 },
143
- { gauge: 'Avg fuel level', measure: 'fuelLevel', reduce: 'avg', min: 0, max: 100, unit: '%', span: 1 },
144
- { chart: 'type', measure: 'mileage', reduce: 'sum', type: 'bar', span: 2 },
145
- { chart: 'status', reduce: 'count', type: 'pie', span: 1 },
146
- { pivot: { rows: ['type'], cols: ['status'], aggregate: 'count' }, span: 3 },
147
- { tabs: [
148
- { label: 'Mileage by type', tiles: [{ chart: 'type', measure: 'mileage', reduce: 'sum', type: 'bar' }] },
149
- { label: 'Vehicles by status', tiles: [{ chart: 'status', reduce: 'count', type: 'bar' }] },
150
- ], span: 3 },
151
- { grid: true, format: vehicleFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
152
- ]),
153
- // Trips dashboard: distance + cost KPIs (both trending over start time via a
154
- // sparkline), an avg-distance gauge, a cost-over-time area trend, a status
155
- // pie, a tabbed breakdown, a faceted filter, and a status-pill trip grid.
156
- dashScreen(trips, { id: 'trips-dashboard', title: 'Trips', order: 1 }, [
157
- { kpi: 'Trips', reduce: 'count', span: 1 },
158
- { kpi: 'Total distance', measure: 'distanceKm', reduce: 'sum', format: 'compact', trendField: 'startAt', trendReduce: 'sum', span: 1 },
159
- { kpi: 'Total cost', measure: 'cost', reduce: 'sum', format: 'currency', trendField: 'startAt', trendReduce: 'sum', span: 1 },
160
- { kpi: 'Avg cost', measure: 'cost', reduce: 'avg', format: 'currency', span: 1 },
161
- { gauge: 'Avg distance (km)', measure: 'distanceKm', reduce: 'avg', min: 0, max: 500, unit: 'km', span: 1 },
162
- { chart: 'startAt', measure: 'cost', reduce: 'sum', type: 'area', span: 2 },
163
- { chart: 'status', reduce: 'count', type: 'pie', span: 1 },
164
- { tabs: [
165
- { label: 'Cost by status', tiles: [{ chart: 'status', measure: 'cost', reduce: 'sum', type: 'bar' }] },
166
- { label: 'Distance by status', tiles: [{ chart: 'status', measure: 'distanceKm', reduce: 'sum', type: 'bar' }] },
167
- ], span: 3 },
168
- { filter: ['status', 'vehicleId', 'driverId'], span: 3 },
169
- { grid: true, format: tripFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
170
- ]),
171
- // Resource scheduler: a column per vehicle, trips placed by time + tinted by status,
172
- // drag-to-reassign a vehicle + a detail drawer (week view). Was a month calendar.
173
- schedulerScreen(trips, { id: 'dispatch', title: 'Dispatch', order: 2 }, { startField: 'startAt', titleField: 'driverId', colorField: 'status', resourceField: 'vehicleId', initialView: 'week' }),
174
- // Vehicle detail: asset header (status pill) + mileage / fuel tiles with a
175
- // trip-history timeline (each trip titled by its driver, colored by status).
176
- detailScreen(vehicles, { id: 'vehicle-detail', title: 'Vehicle detail', order: 3 }, {
177
- titleField: 'name', subtitleField: 'plate', statusField: 'status', metricFields: ['mileage', 'fuelLevel'],
178
- related: [{ entity: 'trips', foreignKey: 'vehicleId', label: 'Trips', titleField: 'driverId', subtitleField: 'distanceKm', dateField: 'startAt', statusField: 'status' }],
179
- }),
180
- screen(vehicles, 'master-detail', { id: 'vehicles', title: 'Vehicles', order: 4, child: trips, foreignKey: 'vehicleId', linkScreen: 'vehicle-detail' }),
181
- formScreen(trips, { id: 'trip-forms', title: 'Trip forms', order: 5 }, ['vehicleId', 'driverId', 'status'], { format: tripFormats, summaries: true, rowActions: [{ kind: 'edit' }] }, ['status', 'vehicleId', 'driverId']),
182
- formScreen(drivers, { id: 'drivers', title: 'Drivers', order: 6 }, ['name', 'phone', 'rating'], { summaries: true }, ['rating']),
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
+ }