@svgrid/enterprise 1.2.0 → 2.0.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/dist/cdn/svgrid-enterprise.svelte-external.js +26039 -805
- package/dist/designer/assets/index-Dp44bTid.js +939 -0
- package/dist/designer/assets/index-RJp6x8tw.css +1 -0
- package/dist/designer/assets/jszip.min-CjMo-QGg.js +2 -0
- package/dist/designer/index.html +13 -0
- package/dist/node/studio.js +22554 -0
- package/package.json +10 -3
- package/src/SvAuthGate.svelte +115 -0
- package/src/SvBoard.dom.test.ts +67 -0
- package/src/SvBoard.svelte +192 -0
- package/src/SvExportMenu.svelte +553 -0
- package/src/SvFileInput.svelte +113 -0
- package/src/SvGridEditPanel.dom.test.ts +146 -0
- package/src/SvGridEditPanel.svelte +793 -0
- package/src/SvGridMasterDetail.svelte +95 -0
- package/src/SvImportDialog.svelte +1020 -0
- package/src/SvLookupInput.svelte +180 -0
- package/src/SvRecordDetail.dom.test.ts +137 -0
- package/src/SvRecordDetail.svelte +288 -0
- package/src/SvSchedule.dom.test.ts +57 -0
- package/src/SvSchedule.svelte +156 -0
- package/src/SvSchemaChart.svelte +233 -0
- package/src/SvSchemaDashboard.svelte +130 -0
- package/src/ai-export-pdf.test.ts +74 -0
- package/src/ai-export-xlsx.test.ts +87 -0
- package/src/ai-export.test.ts +110 -0
- package/src/ai.ts +1188 -782
- package/src/edit-panel.test.ts +213 -0
- package/src/edit-panel.ts +228 -0
- package/src/export-conditional.test.ts +60 -0
- package/src/export-conditional.ts +94 -0
- package/src/export-ooxml.test.ts +152 -0
- package/src/export-ooxml.ts +485 -0
- package/src/export-pdf.test.ts +138 -0
- package/src/export-pdf.ts +245 -0
- package/src/export-print.test.ts +66 -0
- package/src/export-print.ts +132 -0
- package/src/export-serialize.test.ts +56 -0
- package/src/export-serialize.ts +24 -0
- package/src/export-xls.ts +196 -0
- package/src/export-xlsx-roundtrip.test.ts +120 -0
- package/src/export-xlsx.test.ts +150 -0
- package/src/export.test.ts +349 -0
- package/src/export.ts +1452 -549
- package/src/import-automap.test.ts +131 -0
- package/src/import-hardening.test.ts +158 -0
- package/src/import.ts +1042 -648
- package/src/index.ts +282 -0
- package/src/install.ts +134 -114
- package/src/license-core.test.ts +23 -0
- package/src/license-core.ts +35 -0
- package/src/license.ts +103 -90
- package/src/master-detail.test.ts +61 -0
- package/src/master-detail.ts +42 -0
- package/src/print.ts +107 -127
- package/src/schema-designer.test.ts +121 -0
- package/src/schema-designer.ts +142 -0
- package/src/schema.test.ts +268 -0
- package/src/schema.ts +499 -0
- package/src/smart-shim.ts +107 -105
- package/src/sources/aggregate.test.ts +79 -0
- package/src/sources/aggregate.ts +102 -0
- package/src/sources/auth-supabase.test.ts +80 -0
- package/src/sources/auth-supabase.ts +87 -0
- package/src/sources/dashboard.kpi.test.ts +50 -0
- package/src/sources/dashboard.test.ts +61 -0
- package/src/sources/dashboard.ts +136 -0
- package/src/sources/field-inference.test.ts +60 -0
- package/src/sources/field-inference.ts +63 -0
- package/src/sources/filters.test.ts +58 -0
- package/src/sources/filters.ts +64 -0
- package/src/sources/index.ts +67 -0
- package/src/sources/introspect-supabase.test.ts +100 -0
- package/src/sources/introspect-supabase.ts +119 -0
- package/src/sources/realtime-supabase.test.ts +93 -0
- package/src/sources/realtime-supabase.ts +99 -0
- package/src/sources/relation-lookup.test.ts +99 -0
- package/src/sources/relation-lookup.ts +128 -0
- package/src/sources/rest-adapters.test.ts +95 -0
- package/src/sources/rest-adapters.ts +120 -0
- package/src/sources/rest.test.ts +104 -0
- package/src/sources/rest.ts +129 -0
- package/src/sources/schema-from-columns.test.ts +106 -0
- package/src/sources/schema-from-columns.ts +88 -0
- package/src/sources/supabase.test.ts +110 -0
- package/src/sources/supabase.ts +99 -0
- package/src/sources/with-entity-rules.test.ts +104 -0
- package/src/sources/with-entity-rules.ts +78 -0
- package/src/sources/with-relation-labels.test.ts +75 -0
- package/src/sources/with-relation-labels.ts +73 -0
- package/src/studio/bug-report.test.ts +101 -0
- package/src/studio/bug-report.ts +165 -0
- package/src/studio/cli.test.ts +187 -0
- package/src/studio/cli.ts +112 -0
- package/src/studio/csv.test.ts +90 -0
- package/src/studio/csv.ts +165 -0
- package/src/studio/db-connect-string.test.ts +94 -0
- package/src/studio/db-connect-string.ts +128 -0
- package/src/studio/emit-project.test.ts +923 -0
- package/src/studio/emit-project.ts +1273 -0
- package/src/studio/emit-schema.test.ts +94 -0
- package/src/studio/emit-schema.ts +920 -0
- package/src/studio/index.ts +195 -0
- package/src/studio/introspect-db.test.ts +186 -0
- package/src/studio/introspect-db.ts +312 -0
- package/src/studio/introspect-prisma.test.ts +99 -0
- package/src/studio/introspect-prisma.ts +175 -0
- package/src/studio/introspect.test.ts +172 -0
- package/src/studio/introspect.ts +310 -0
- package/src/studio/pipeline.test.ts +42 -0
- package/src/studio/project-robust.test.ts +157 -0
- package/src/studio/project.test.ts +473 -0
- package/src/studio/project.ts +916 -0
- package/src/studio/sample-data.test.ts +58 -0
- package/src/studio/sample-data.ts +200 -0
- package/src/studio/samples/ats.ts +202 -0
- package/src/studio/samples/clinic.ts +177 -0
- package/src/studio/samples/crm.ts +250 -0
- package/src/studio/samples/ecommerce.ts +187 -0
- package/src/studio/samples/events.ts +199 -0
- package/src/studio/samples/fleet.ts +184 -0
- package/src/studio/samples/gym.ts +213 -0
- package/src/studio/samples/hr.ts +193 -0
- package/src/studio/samples/index.ts +55 -0
- package/src/studio/samples/insurance.ts +195 -0
- package/src/studio/samples/inventory.ts +182 -0
- package/src/studio/samples/invoicing.ts +162 -0
- package/src/studio/samples/library.ts +180 -0
- package/src/studio/samples/live-data.test.ts +98 -0
- package/src/studio/samples/live-data.ts +308 -0
- package/src/studio/samples/projects.ts +190 -0
- package/src/studio/samples/realestate.ts +196 -0
- package/src/studio/samples/restaurant.ts +187 -0
- package/src/studio/samples/samples.test.ts +208 -0
- package/src/studio/samples/school.ts +197 -0
- package/src/studio/samples/seed-floor.test.ts +25 -0
- package/src/studio/samples/shared.ts +305 -0
- package/src/studio/samples/subscriptions.ts +183 -0
- package/src/studio/samples/support.ts +182 -0
- package/src/studio/scaffold-app.test.ts +91 -0
- package/src/studio/scaffold-app.ts +161 -0
- package/src/studio/scaffold.test.ts +178 -0
- package/src/studio/scaffold.ts +374 -0
- package/src/studio/themes.ts +172 -0
- package/src/studio/verify.test.ts +47 -0
- package/src/studio/verify.ts +79 -0
- package/src/sveltekit/in-memory.test.ts +104 -0
- package/src/sveltekit/in-memory.ts +129 -0
- package/src/sveltekit/index.ts +23 -0
- package/src/sveltekit/query-plan.test.ts +109 -0
- package/src/sveltekit/query-plan.ts +125 -0
- package/src/sveltekit/sql-source.test.ts +185 -0
- package/src/sveltekit/sql-source.ts +166 -0
- package/src/sveltekit/sql.test.ts +80 -0
- package/src/sveltekit/sql.ts +131 -0
- package/src/sveltekit/transport.test.ts +175 -0
- package/src/sveltekit/transport.ts +254 -0
- package/src/sveltekit/types.ts +11 -0
- package/src/upgrade-prompt.ts +149 -148
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { EntitySchema } from '../../schema.js'
|
|
2
|
+
import type { FormatRule } from '../project.js'
|
|
3
|
+
import { screen, formScreen, calendarScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
4
|
+
|
|
5
|
+
const classes: EntitySchema = {
|
|
6
|
+
name: 'classes',
|
|
7
|
+
label: 'Class',
|
|
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: 'type', type: 'enum', required: true, options: [
|
|
13
|
+
{ value: 'yoga', label: 'Yoga', color: '#10b981' },
|
|
14
|
+
{ value: 'spin', label: 'Spin', color: '#0ea5e9' },
|
|
15
|
+
{ value: 'hiit', label: 'HIIT', color: '#ef4444' },
|
|
16
|
+
{ value: 'strength', label: 'Strength', color: '#6366f1' },
|
|
17
|
+
] },
|
|
18
|
+
{ field: 'capacity', type: 'number', label: 'Capacity', required: true, min: 1, defaultValue: 20, input: { help: 'Maximum attendees per session' } },
|
|
19
|
+
{ field: 'durationMins', type: 'number', label: 'Duration (min)', min: 15, max: 120, defaultValue: 60, input: { suffix: ' min' } },
|
|
20
|
+
{ field: 'intensity', type: 'number', label: 'Intensity', min: 0, max: 5, defaultValue: 3, input: { editorType: 'slider', help: 'Effort level from easy (0) to brutal (5)' } },
|
|
21
|
+
// Derived: training load = intensity x duration (display-only, never stored).
|
|
22
|
+
{ field: 'load', type: 'number', label: 'Load', readonly: true, formula: 'intensity * durationMins' },
|
|
23
|
+
{ field: 'color', type: 'text', label: 'Schedule colour', input: { editorType: 'color', help: 'Shown on the timetable' } },
|
|
24
|
+
{ field: 'trainer', type: 'text', label: 'Lead trainer' },
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const members: EntitySchema = {
|
|
29
|
+
name: 'members',
|
|
30
|
+
label: 'Member',
|
|
31
|
+
idField: 'id',
|
|
32
|
+
fields: [
|
|
33
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
34
|
+
{ field: 'name', type: 'text', required: true, minLength: 2 },
|
|
35
|
+
{ field: 'email', type: 'text', format: 'email', required: true },
|
|
36
|
+
{ field: 'phone', type: 'text', input: { editorType: 'phone', help: 'Mobile for class reminders' } },
|
|
37
|
+
{ field: 'country', type: 'text', label: 'Country', input: { editorType: 'country' } },
|
|
38
|
+
{ field: 'plan', type: 'enum', label: 'Membership', required: true, options: [
|
|
39
|
+
{ value: 'basic', label: 'Basic', color: '#94a3b8' },
|
|
40
|
+
{ value: 'plus', label: 'Plus', color: '#0ea5e9' },
|
|
41
|
+
{ value: 'elite', label: 'Elite', color: '#f59e0b' },
|
|
42
|
+
] },
|
|
43
|
+
{ field: 'status', type: 'enum', required: true, defaultValue: 'active', options: [
|
|
44
|
+
{ value: 'active', label: 'Active', color: '#10b981' },
|
|
45
|
+
{ value: 'paused', label: 'Paused', color: '#f59e0b' },
|
|
46
|
+
{ value: 'expired', label: 'Expired', color: '#ef4444' },
|
|
47
|
+
] },
|
|
48
|
+
{ field: 'joinDate', type: 'date', label: 'Joined' },
|
|
49
|
+
{ field: 'goals', type: 'text', label: 'Goals', input: { editorType: 'chips', help: 'What this member is training for' } },
|
|
50
|
+
{ field: 'monthlyFee', type: 'number', label: 'Monthly fee ($)', min: 0, defaultValue: 49, input: { prefix: '$' } },
|
|
51
|
+
// Derived: yearly commitment from the monthly fee (display-only).
|
|
52
|
+
{ field: 'annualFee', type: 'number', label: 'Annual fee ($)', readonly: true, formula: 'monthlyFee * 12' },
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const bookings: EntitySchema = {
|
|
57
|
+
name: 'bookings',
|
|
58
|
+
label: 'Booking',
|
|
59
|
+
idField: 'id',
|
|
60
|
+
fields: [
|
|
61
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
62
|
+
{ field: 'memberId', type: 'relation', label: 'Member', relation: { entity: 'members', foreignKey: 'memberId', labelField: 'name' } },
|
|
63
|
+
{ field: 'classId', type: 'relation', label: 'Class', relation: { entity: 'classes', foreignKey: 'classId', labelField: 'name' } },
|
|
64
|
+
{ field: 'status', type: 'enum', required: true, defaultValue: 'booked', options: [
|
|
65
|
+
{ value: 'booked', label: 'Booked', color: '#0ea5e9' },
|
|
66
|
+
{ value: 'attended', label: 'Attended', color: '#10b981' },
|
|
67
|
+
{ value: 'no_show', label: 'No show', color: '#ef4444' },
|
|
68
|
+
] },
|
|
69
|
+
{ field: 'bookedAt', type: 'datetime', label: 'Booked at', input: { editorType: 'datetime', help: 'When the spot was reserved' } },
|
|
70
|
+
{ field: 'rating', type: 'number', label: 'Feedback', min: 0, max: 5, defaultValue: 0, input: { editorType: 'rating', help: 'Post-class member rating' } },
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const seed = {
|
|
75
|
+
classes: [
|
|
76
|
+
{ id: 'cl1', name: 'Sunrise Yoga', type: 'yoga', capacity: 20, durationMins: 60, intensity: 2, color: '#10b981', trainer: 'Maya Cohen' },
|
|
77
|
+
{ id: 'cl2', name: 'Power Spin', type: 'spin', capacity: 24, durationMins: 45, intensity: 4, color: '#0ea5e9', trainer: 'Rob Sena' },
|
|
78
|
+
{ id: 'cl3', name: 'HIIT Blast', type: 'hiit', capacity: 16, durationMins: 30, intensity: 5, color: '#ef4444', trainer: 'Tara Voss' },
|
|
79
|
+
{ id: 'cl4', name: 'Iron Strength', type: 'strength', capacity: 12, durationMins: 50, intensity: 4, color: '#6366f1', trainer: 'Marcus Lee' },
|
|
80
|
+
{ id: 'cl5', name: 'Flow Yoga', type: 'yoga', capacity: 18, durationMins: 75, intensity: 3, color: '#22c55e', trainer: 'Maya Cohen' },
|
|
81
|
+
{ id: 'cl6', name: 'Endurance Spin', type: 'spin', capacity: 22, durationMins: 60, intensity: 3, color: '#38bdf8', trainer: 'Rob Sena' },
|
|
82
|
+
],
|
|
83
|
+
members: [
|
|
84
|
+
{ id: 'mb1', name: 'Olivia Grant', email: 'olivia.grant@example.com', phone: '+1 (415) 555-0111', country: 'United States', plan: 'elite', status: 'active', joinDate: '2025-01-12', goals: ['Weight loss', 'Flexibility'], monthlyFee: 99 },
|
|
85
|
+
{ id: 'mb2', name: 'James Carter', email: 'james.carter@example.com', phone: '+44 20 7946 0112', country: 'United Kingdom', plan: 'plus', status: 'active', joinDate: '2025-03-04', goals: ['Strength'], monthlyFee: 69 },
|
|
86
|
+
{ id: 'mb3', name: 'Hana Kim', email: 'hana.kim@example.com', phone: '+82 2 555 0113', country: 'South Korea', plan: 'basic', status: 'paused', joinDate: '2025-05-20', goals: ['Stress relief'], monthlyFee: 39 },
|
|
87
|
+
{ id: 'mb4', name: 'Diego Morales', email: 'diego.morales@example.com', phone: '+34 91 555 0114', country: 'Spain', plan: 'plus', status: 'active', joinDate: '2024-11-08', goals: ['Endurance', 'Cardio'], monthlyFee: 69 },
|
|
88
|
+
{ id: 'mb5', name: 'Zara Ahmed', email: 'zara.ahmed@example.com', phone: '+971 4 555 0115', country: 'United Arab Emirates', plan: 'elite', status: 'expired', joinDate: '2024-08-15', goals: ['Toning'], monthlyFee: 99 },
|
|
89
|
+
{ id: 'mb6', name: 'Ben Walker', email: 'ben.walker@example.com', phone: '+1 (650) 555-0116', country: 'United States', plan: 'basic', status: 'active', joinDate: '2025-06-01', goals: ['General fitness'], monthlyFee: 39 },
|
|
90
|
+
{ id: 'mb7', name: 'Chloe Dubois', email: 'chloe.dubois@example.com', phone: '+33 1 42 55 0117', country: 'France', plan: 'plus', status: 'active', joinDate: '2025-02-18', goals: ['Flexibility', 'Balance'], monthlyFee: 69 },
|
|
91
|
+
],
|
|
92
|
+
bookings: [
|
|
93
|
+
{ id: 'bk1', memberId: 'mb1', classId: 'cl1', status: 'attended', bookedAt: '2026-07-01T06:30:00.000Z', rating: 5 },
|
|
94
|
+
{ id: 'bk2', memberId: 'mb2', classId: 'cl2', status: 'attended', bookedAt: '2026-07-01T18:00:00.000Z', rating: 4 },
|
|
95
|
+
{ id: 'bk3', memberId: 'mb3', classId: 'cl1', status: 'no_show', bookedAt: '2026-07-02T06:30:00.000Z', rating: 0 },
|
|
96
|
+
{ id: 'bk4', memberId: 'mb4', classId: 'cl3', status: 'booked', bookedAt: '2026-07-03T12:15:00.000Z', rating: 0 },
|
|
97
|
+
{ id: 'bk5', memberId: 'mb5', classId: 'cl4', status: 'attended', bookedAt: '2026-07-03T19:00:00.000Z', rating: 5 },
|
|
98
|
+
{ id: 'bk6', memberId: 'mb6', classId: 'cl2', status: 'attended', bookedAt: '2026-07-04T18:00:00.000Z', rating: 3 },
|
|
99
|
+
{ id: 'bk7', memberId: 'mb7', classId: 'cl5', status: 'booked', bookedAt: '2026-07-05T09:00:00.000Z', rating: 0 },
|
|
100
|
+
{ id: 'bk8', memberId: 'mb1', classId: 'cl6', status: 'attended', bookedAt: '2026-07-05T17:30:00.000Z', rating: 4 },
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Class grid formatting: type pills plus numeric thresholds (tight rooms red,
|
|
105
|
+
// roomy sessions green + bold, brutal-intensity classes bold red) - data-driven
|
|
106
|
+
// color, not just status pills.
|
|
107
|
+
const classFormats: FormatRule[] = [
|
|
108
|
+
...statusPills(classes, 'type'),
|
|
109
|
+
{ field: 'capacity', op: 'lt', value: 15, color: '#dc2626' },
|
|
110
|
+
{ field: 'capacity', op: 'gte', value: 24, color: '#16a34a', bold: true },
|
|
111
|
+
{ field: 'intensity', op: 'gte', value: 4, color: '#dc2626', bold: true },
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
// Member grid formatting: status + plan pills, plus premium-fee highlight.
|
|
115
|
+
const memberFormats: FormatRule[] = [
|
|
116
|
+
...statusPills(members, 'status'),
|
|
117
|
+
...statusPills(members, 'plan'),
|
|
118
|
+
{ field: 'monthlyFee', op: 'gte', value: 90, bold: true },
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
// Booking grid formatting: status pills plus rating thresholds (poor feedback
|
|
122
|
+
// red, strong feedback green + bold).
|
|
123
|
+
const bookingFormats: FormatRule[] = [
|
|
124
|
+
...statusPills(bookings, 'status'),
|
|
125
|
+
{ field: 'rating', op: 'lt', value: 3, color: '#dc2626' },
|
|
126
|
+
{ field: 'rating', op: 'gte', value: 4, color: '#16a34a', bold: true },
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
export const gym: SampleApp = {
|
|
130
|
+
id: 'gym',
|
|
131
|
+
name: 'Gym',
|
|
132
|
+
description: 'Members, classes and bookings - membership, class and attendance dashboards (KPI sparklines + a fee target, capacity and feedback gauges, a plan-by-status pivot, join-date and booking trends, tabbed breakdowns), filtered status-pill grids with row actions, master/detail, and rich edit forms.',
|
|
133
|
+
emoji: '🏋️',
|
|
134
|
+
accent: '#22c55e',
|
|
135
|
+
build: () => {
|
|
136
|
+
const classRows = pad(classes, seed.classes, 12)
|
|
137
|
+
const memberRows = pad(members, seed.members, 22)
|
|
138
|
+
const classPool = ids(classRows)
|
|
139
|
+
const memberPool = ids(memberRows)
|
|
140
|
+
const bookingRows = pad(bookings, seed.bookings, 30, { memberId: memberPool, classId: classPool })
|
|
141
|
+
return project({
|
|
142
|
+
title: 'FitClub',
|
|
143
|
+
brand: 'FitClub',
|
|
144
|
+
accent: '#22c55e',
|
|
145
|
+
preset: 'dracula',
|
|
146
|
+
mode: 'dark',
|
|
147
|
+
footer: '© FitClub',
|
|
148
|
+
entities: [classes, members, bookings],
|
|
149
|
+
seed: { classes: classRows, members: memberRows, bookings: bookingRows },
|
|
150
|
+
screens: [
|
|
151
|
+
// Membership dashboard: revenue + member KPIs (a monthly-revenue card with a
|
|
152
|
+
// sparkline over join dates + a $1.5k target), a fee gauge, plan / status
|
|
153
|
+
// breakdowns, a join-date revenue trend, a plan x status pivot, a tabbed
|
|
154
|
+
// breakdown, a faceted filter, and a status-pill member grid.
|
|
155
|
+
dashScreen(members, { id: 'membership', title: 'Membership', order: 0 }, [
|
|
156
|
+
{ kpi: 'Members', reduce: 'count', span: 1 },
|
|
157
|
+
{ kpi: 'Monthly revenue', measure: 'monthlyFee', reduce: 'sum', format: 'currency', trendField: 'joinDate', trendReduce: 'sum', target: 1500, span: 1 },
|
|
158
|
+
{ kpi: 'Avg fee', measure: 'monthlyFee', reduce: 'avg', format: 'currency', span: 1 },
|
|
159
|
+
{ gauge: 'Avg monthly fee vs $150', measure: 'monthlyFee', reduce: 'avg', min: 0, max: 150, unit: '$', span: 1 },
|
|
160
|
+
{ chart: 'plan', measure: 'monthlyFee', reduce: 'sum', type: 'bar', span: 2 },
|
|
161
|
+
{ chart: 'status', reduce: 'count', type: 'pie', span: 1 },
|
|
162
|
+
{ chart: 'joinDate', measure: 'monthlyFee', reduce: 'sum', type: 'area', span: 3 },
|
|
163
|
+
{ pivot: { rows: ['plan'], cols: ['status'], measure: 'monthlyFee', aggregate: 'sum' }, span: 3 },
|
|
164
|
+
{ tabs: [
|
|
165
|
+
{ label: 'Revenue by plan', tiles: [{ chart: 'plan', measure: 'monthlyFee', reduce: 'sum', type: 'bar' }] },
|
|
166
|
+
{ label: 'Members by status', tiles: [{ chart: 'status', reduce: 'count', type: 'bar' }] },
|
|
167
|
+
], span: 3 },
|
|
168
|
+
{ filter: ['plan', 'status', 'country'], span: 3 },
|
|
169
|
+
{ grid: true, format: memberFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
170
|
+
]),
|
|
171
|
+
// Class insights: capacity + intensity KPIs, an average-capacity gauge, type
|
|
172
|
+
// breakdowns, a tabbed capacity / intensity view, and a class grid with type
|
|
173
|
+
// pills plus capacity / intensity thresholds.
|
|
174
|
+
dashScreen(classes, { id: 'schedule', title: 'Class insights', order: 1 }, [
|
|
175
|
+
{ kpi: 'Classes', reduce: 'count', span: 1 },
|
|
176
|
+
{ kpi: 'Total capacity', measure: 'capacity', reduce: 'sum', span: 1 },
|
|
177
|
+
{ kpi: 'Avg intensity', measure: 'intensity', reduce: 'avg', span: 1 },
|
|
178
|
+
{ gauge: 'Avg capacity', measure: 'capacity', reduce: 'avg', min: 0, max: 30, span: 1 },
|
|
179
|
+
{ chart: 'type', measure: 'capacity', reduce: 'sum', type: 'bar', span: 2 },
|
|
180
|
+
{ chart: 'type', reduce: 'count', type: 'pie', span: 1 },
|
|
181
|
+
{ tabs: [
|
|
182
|
+
{ label: 'Capacity by type', tiles: [{ chart: 'type', measure: 'capacity', reduce: 'sum', type: 'bar' }] },
|
|
183
|
+
{ label: 'Intensity by type', tiles: [{ chart: 'type', measure: 'intensity', reduce: 'avg', type: 'bar' }] },
|
|
184
|
+
{ label: 'Duration by type', tiles: [{ chart: 'type', measure: 'durationMins', reduce: 'avg', type: 'bar' }] },
|
|
185
|
+
], span: 3 },
|
|
186
|
+
{ grid: true, format: classFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
187
|
+
]),
|
|
188
|
+
// Attendance: booking + feedback KPIs (avg rating trends over booking time), a
|
|
189
|
+
// feedback gauge, status breakdown, a rating-over-time area trend, a filter,
|
|
190
|
+
// and a status-pill booking grid with rating thresholds.
|
|
191
|
+
dashScreen(bookings, { id: 'attendance', title: 'Attendance', order: 2 }, [
|
|
192
|
+
{ kpi: 'Bookings', reduce: 'count', span: 1 },
|
|
193
|
+
{ kpi: 'Avg rating', measure: 'rating', reduce: 'avg', trendField: 'bookedAt', trendReduce: 'avg', span: 1 },
|
|
194
|
+
{ kpi: 'Top rating', measure: 'rating', reduce: 'max', span: 1 },
|
|
195
|
+
{ gauge: 'Avg feedback', measure: 'rating', reduce: 'avg', min: 0, max: 5, span: 1 },
|
|
196
|
+
{ chart: 'status', reduce: 'count', type: 'pie', span: 1 },
|
|
197
|
+
{ chart: 'bookedAt', measure: 'rating', reduce: 'avg', type: 'area', span: 2 },
|
|
198
|
+
{ filter: ['status'], span: 3 },
|
|
199
|
+
{ grid: true, format: bookingFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
200
|
+
]),
|
|
201
|
+
calendarScreen(bookings, { id: 'schedule-cal', title: 'Schedule', order: 3 }, { dateField: 'bookedAt', titleField: 'classId', colorField: 'status', filter: ['status', 'classId'] }),
|
|
202
|
+
detailScreen(members, { id: 'member-360', title: 'Member 360', order: 4 }, {
|
|
203
|
+
titleField: 'name', subtitleField: 'email', statusField: 'status',
|
|
204
|
+
metricFields: ['monthlyFee'],
|
|
205
|
+
related: [{ entity: 'bookings', foreignKey: 'memberId', label: 'Bookings', titleField: 'classId', dateField: 'bookedAt', statusField: 'status' }],
|
|
206
|
+
}),
|
|
207
|
+
screen(classes, 'master-detail', { id: 'classes', title: 'Classes', order: 5, child: bookings, foreignKey: 'classId' }),
|
|
208
|
+
formScreen(members, { id: 'members', title: 'Members', order: 6 }, undefined, { format: memberFormats, summaries: true, rowActions: [{ kind: 'edit' }], rowLink: { screen: 'member-360', sourceField: 'id', targetField: 'id' } }, ['plan', 'status', 'country']),
|
|
209
|
+
formScreen(bookings, { id: 'bookings', title: 'Bookings', order: 7 }, undefined, { format: bookingFormats, summaries: true }, ['status']),
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
},
|
|
213
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { EntitySchema } from '../../schema.js'
|
|
2
|
+
import type { FormatRule } from '../project.js'
|
|
3
|
+
import { screen, formScreen, project, dashScreen, detailScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
4
|
+
|
|
5
|
+
const departments: EntitySchema = {
|
|
6
|
+
name: 'departments',
|
|
7
|
+
label: 'Department',
|
|
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: 'parentId', type: 'relation', label: 'Parent dept', relation: { entity: 'departments', foreignKey: 'parentId', labelField: 'name' } },
|
|
13
|
+
{ field: 'location', type: 'text', label: 'Location', input: { editorType: 'country', help: 'Primary office country' } },
|
|
14
|
+
{ field: 'budget', type: 'number', label: 'Budget ($)', min: 0 },
|
|
15
|
+
{ field: 'headcount', type: 'number', label: 'Headcount', min: 0 },
|
|
16
|
+
{ field: 'color', type: 'text', label: 'Accent', defaultValue: '#8b5cf6', input: { editorType: 'color', help: 'Chart accent color' } },
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const employees: EntitySchema = {
|
|
21
|
+
name: 'employees',
|
|
22
|
+
label: 'Employee',
|
|
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: 'email', type: 'text', format: 'email', required: true },
|
|
28
|
+
{ field: 'phone', type: 'text', input: { editorType: 'phone' } },
|
|
29
|
+
{ field: 'ssn', type: 'text', label: 'SSN', input: { editorType: 'mask', mask: '###-##-####', help: 'US tax identifier' } },
|
|
30
|
+
{ field: 'country', type: 'text', label: 'Country', input: { editorType: 'country' } },
|
|
31
|
+
{ field: 'deptId', type: 'relation', label: 'Department', relation: { entity: 'departments', foreignKey: 'deptId', labelField: 'name' } },
|
|
32
|
+
{ field: 'level', type: 'enum', options: [
|
|
33
|
+
{ value: 'junior', label: 'Junior', color: '#94a3b8' },
|
|
34
|
+
{ value: 'mid', label: 'Mid', color: '#0ea5e9' },
|
|
35
|
+
{ value: 'senior', label: 'Senior', color: '#6366f1' },
|
|
36
|
+
{ value: 'lead', label: 'Lead', color: '#10b981' },
|
|
37
|
+
] },
|
|
38
|
+
{ field: 'skills', type: 'text', label: 'Skills', input: { editorType: 'chips', help: 'Key skills and specialties' } },
|
|
39
|
+
{ field: 'performance', type: 'number', label: 'Performance', min: 1, max: 5, defaultValue: 3, input: { editorType: 'rating', help: '1-5 review score' } },
|
|
40
|
+
{ field: 'status', type: 'enum', defaultValue: 'active', options: [
|
|
41
|
+
{ value: 'active', label: 'Active', color: '#10b981' },
|
|
42
|
+
{ value: 'on_leave', label: 'On leave', color: '#f59e0b' },
|
|
43
|
+
{ value: 'terminated', label: 'Terminated', color: '#ef4444' },
|
|
44
|
+
] },
|
|
45
|
+
{ field: 'salary', type: 'number', label: 'Salary ($)', min: 0 },
|
|
46
|
+
// Derived: fully-loaded cost including benefits and overhead (display-only).
|
|
47
|
+
{ field: 'annualCost', type: 'number', label: 'Annual cost ($)', readonly: true, formula: 'salary * 1.3' },
|
|
48
|
+
{ field: 'hireDate', type: 'date', label: 'Hire date' },
|
|
49
|
+
{ field: 'portalPassword', type: 'text', label: 'Portal password', minLength: 8, hidden: { grid: true }, input: { editorType: 'password', help: 'Employee self-service login' } },
|
|
50
|
+
],
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const timeOff: EntitySchema = {
|
|
54
|
+
name: 'timeOff',
|
|
55
|
+
label: 'Time off',
|
|
56
|
+
idField: 'id',
|
|
57
|
+
fields: [
|
|
58
|
+
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
|
|
59
|
+
{ field: 'employeeId', type: 'relation', label: 'Employee', relation: { entity: 'employees', foreignKey: 'employeeId', labelField: 'name' } },
|
|
60
|
+
{ field: 'type', type: 'enum', required: true, options: [
|
|
61
|
+
{ value: 'vacation', label: 'Vacation', color: '#0ea5e9' },
|
|
62
|
+
{ value: 'sick', label: 'Sick', color: '#ef4444' },
|
|
63
|
+
{ value: 'personal', label: 'Personal', color: '#f59e0b' },
|
|
64
|
+
] },
|
|
65
|
+
{ field: 'startDate', type: 'date', label: 'Start date', required: true },
|
|
66
|
+
{ field: 'endDate', type: 'date', label: 'End date' },
|
|
67
|
+
{ field: 'days', type: 'number', label: 'Days', min: 0 },
|
|
68
|
+
{ field: 'status', type: 'enum', defaultValue: 'requested', options: [
|
|
69
|
+
{ value: 'requested', label: 'Requested', color: '#94a3b8' },
|
|
70
|
+
{ value: 'approved', label: 'Approved', color: '#10b981' },
|
|
71
|
+
{ value: 'rejected', label: 'Rejected', color: '#ef4444' },
|
|
72
|
+
] },
|
|
73
|
+
],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const seed = {
|
|
77
|
+
departments: [
|
|
78
|
+
{ id: 'dp0', name: 'Executive', parentId: '', location: 'United States', budget: 400000, headcount: 4, color: '#334155' },
|
|
79
|
+
{ id: 'dp1', name: 'Engineering', parentId: 'dp0', location: 'United States', budget: 1800000, headcount: 42, color: '#6366f1' },
|
|
80
|
+
{ id: 'dp2', name: 'Sales', parentId: 'dp0', location: 'United Kingdom', budget: 950000, headcount: 28, color: '#10b981' },
|
|
81
|
+
{ id: 'dp3', name: 'Marketing', parentId: 'dp2', location: 'Germany', budget: 620000, headcount: 16, color: '#f59e0b' },
|
|
82
|
+
{ id: 'dp4', name: 'Finance', parentId: 'dp0', location: 'United States', budget: 540000, headcount: 12, color: '#0ea5e9' },
|
|
83
|
+
{ id: 'dp5', name: 'People Ops', parentId: 'dp0', location: 'Canada', budget: 380000, headcount: 9, color: '#8b5cf6' },
|
|
84
|
+
],
|
|
85
|
+
employees: [
|
|
86
|
+
{ id: 'em1', name: 'Ada Lovelace', email: 'ada@company.com', phone: '+1 (415) 555-0101', ssn: '123-45-6789', country: 'United States', deptId: 'dp1', level: 'lead', skills: ['Architecture', 'Leadership', 'Rust'], performance: 5, status: 'active', salary: 165000, hireDate: '2026-01-06', portalPassword: 'lovelace-01' },
|
|
87
|
+
{ id: 'em2', name: 'Alan Turing', email: 'alan@company.com', phone: '+44 20 7946 0102', ssn: '234-56-7890', country: 'United Kingdom', deptId: 'dp1', level: 'senior', skills: ['Cryptography', 'Python'], performance: 5, status: 'active', salary: 142000, hireDate: '2026-01-20', portalPassword: 'turing-4102' },
|
|
88
|
+
{ id: 'em3', name: 'Grace Hopper', email: 'grace@company.com', phone: '+1 (312) 555-0103', ssn: '345-67-8901', country: 'United States', deptId: 'dp2', level: 'mid', skills: ['Compilers', 'Mentoring'], performance: 4, status: 'on_leave', salary: 98000, hireDate: '2026-02-03', portalPassword: 'hopper-cobol' },
|
|
89
|
+
{ id: 'em4', name: 'Linus Torvalds', email: 'linus@company.com', phone: '+1 (650) 555-0104', ssn: '456-78-9012', country: 'Finland', deptId: 'dp1', level: 'senior', skills: ['Kernel', 'Git', 'C'], performance: 4, status: 'active', salary: 138000, hireDate: '2026-02-17', portalPassword: 'torvalds-git' },
|
|
90
|
+
{ id: 'em5', name: 'Barbara Liskov', email: 'barbara@company.com', phone: '+1 (617) 555-0105', ssn: '567-89-0123', country: 'United States', deptId: 'dp3', level: 'junior', skills: ['Research', 'Writing'], performance: 3, status: 'active', salary: 72000, hireDate: '2026-03-02', portalPassword: 'liskov-2026' },
|
|
91
|
+
{ id: 'em6', name: 'Katherine Johnson', email: 'kj@company.com', phone: '+1 (757) 555-0106', ssn: '678-90-1234', country: 'United States', deptId: 'dp4', level: 'mid', skills: ['Analytics', 'Forecasting'], performance: 2, status: 'terminated', salary: 89000, hireDate: '2026-03-16', portalPassword: 'johnson-nasa' },
|
|
92
|
+
{ id: 'em7', name: 'Margaret Hamilton', email: 'mh@company.com', phone: '+1 (212) 555-0107', ssn: '789-01-2345', country: 'United States', deptId: 'dp5', level: 'lead', skills: ['Systems', 'Reliability', 'Leadership'], performance: 5, status: 'active', salary: 151000, hireDate: '2026-04-01', portalPassword: 'hamilton-apollo' },
|
|
93
|
+
],
|
|
94
|
+
timeOff: [
|
|
95
|
+
{ id: 'to1', employeeId: 'em1', type: 'vacation', startDate: '2026-07-06', endDate: '2026-07-10', days: 5, status: 'approved' },
|
|
96
|
+
{ id: 'to2', employeeId: 'em3', type: 'sick', startDate: '2026-06-15', endDate: '2026-06-17', days: 3, status: 'approved' },
|
|
97
|
+
{ id: 'to3', employeeId: 'em2', type: 'personal', startDate: '2026-07-21', endDate: '2026-07-21', days: 1, status: 'requested' },
|
|
98
|
+
{ id: 'to4', employeeId: 'em5', type: 'vacation', startDate: '2026-08-03', endDate: '2026-08-12', days: 8, status: 'rejected' },
|
|
99
|
+
{ id: 'to5', employeeId: 'em4', type: 'sick', startDate: '2026-06-22', endDate: '2026-06-23', days: 2, status: 'approved' },
|
|
100
|
+
{ id: 'to6', employeeId: 'em7', type: 'vacation', startDate: '2026-09-07', endDate: '2026-09-10', days: 4, status: 'requested' },
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Employee grid formatting: level + status pills, plus numeric thresholds
|
|
105
|
+
// (under-performers red, top reviews green + bold, six-figure salaries bold) -
|
|
106
|
+
// data-driven color, not just status pills.
|
|
107
|
+
const empFormats: FormatRule[] = [
|
|
108
|
+
...statusPills(employees, 'level'),
|
|
109
|
+
...statusPills(employees, 'status'),
|
|
110
|
+
{ field: 'performance', op: 'lt', value: 3, color: '#dc2626' },
|
|
111
|
+
{ field: 'performance', op: 'gte', value: 5, color: '#16a34a', bold: true },
|
|
112
|
+
{ field: 'salary', op: 'gte', value: 150000, bold: true },
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
// Time-off grid formatting: type + status pills for a triage-ready request list.
|
|
116
|
+
const timeOffFormats: FormatRule[] = [
|
|
117
|
+
...statusPills(timeOff, 'type'),
|
|
118
|
+
...statusPills(timeOff, 'status'),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
export const hr: SampleApp = {
|
|
122
|
+
id: 'hr',
|
|
123
|
+
name: 'HR',
|
|
124
|
+
description: 'Employees, departments and time off - headcount and time-off dashboards (payroll KPI sparkline + target, performance gauge, level/status breakdowns, a level x status pivot, tabs, a time-off area trend), filtered status-pill grids with row actions, master/detail, and rich edit forms (phone, SSN mask, country, skills chips, performance rating, password).',
|
|
125
|
+
emoji: '\u{1F465}',
|
|
126
|
+
accent: '#8b5cf6',
|
|
127
|
+
build: () => {
|
|
128
|
+
const departmentRows = pad(departments, seed.departments, 10)
|
|
129
|
+
const deptPool = ids(departmentRows)
|
|
130
|
+
const employeeRows = pad(employees, seed.employees, 32, { deptId: deptPool })
|
|
131
|
+
const employeePool = ids(employeeRows)
|
|
132
|
+
const timeOffRows = pad(timeOff, seed.timeOff, 28, { employeeId: employeePool })
|
|
133
|
+
return project({
|
|
134
|
+
title: 'People Ops',
|
|
135
|
+
brand: 'People Ops',
|
|
136
|
+
accent: '#8b5cf6',
|
|
137
|
+
preset: 'fluent',
|
|
138
|
+
footer: '',
|
|
139
|
+
entities: [departments, employees, timeOff],
|
|
140
|
+
seed: { departments: departmentRows, employees: employeeRows, timeOff: timeOffRows },
|
|
141
|
+
screens: [
|
|
142
|
+
// Headcount dashboard: KPI cards (a payroll card with a sparkline over hire
|
|
143
|
+
// dates + a $1.5M target, compact-formatted), a performance-rating gauge (0-5),
|
|
144
|
+
// payroll + headcount breakdowns by level and status, a tabbed view, a level x
|
|
145
|
+
// status payroll pivot, and a status-pill employee grid with inline edit.
|
|
146
|
+
dashScreen(employees, { id: 'headcount', title: 'Headcount', order: 0 }, [
|
|
147
|
+
{ kpi: 'Headcount', reduce: 'count', span: 1 },
|
|
148
|
+
{ kpi: 'Total payroll', measure: 'salary', reduce: 'sum', format: 'compact', trendField: 'hireDate', trendReduce: 'sum', target: 1500000, span: 1 },
|
|
149
|
+
{ kpi: 'Avg salary', measure: 'salary', reduce: 'avg', format: 'compact', span: 1 },
|
|
150
|
+
{ gauge: 'Avg performance', measure: 'performance', reduce: 'avg', min: 0, max: 5, span: 1 },
|
|
151
|
+
{ chart: 'level', measure: 'salary', reduce: 'sum', type: 'bar', span: 2 },
|
|
152
|
+
{ chart: 'status', reduce: 'count', type: 'pie', span: 1 },
|
|
153
|
+
{ chart: 'level', reduce: 'count', type: 'bar', span: 2 },
|
|
154
|
+
{ tabs: [
|
|
155
|
+
{ label: 'Payroll by level', tiles: [{ chart: 'level', measure: 'salary', reduce: 'sum', type: 'bar' }] },
|
|
156
|
+
{ label: 'Headcount by status', tiles: [{ chart: 'status', reduce: 'count', type: 'bar' }] },
|
|
157
|
+
], span: 1 },
|
|
158
|
+
{ pivot: { rows: ['level'], cols: ['status'], measure: 'salary', aggregate: 'sum' }, span: 3 },
|
|
159
|
+
{ grid: true, format: empFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
160
|
+
]),
|
|
161
|
+
// Time-off dashboard: request + days KPIs, an average-days gauge, a days-booked
|
|
162
|
+
// area trend over start dates, type/status breakdowns, a faceted filter, and a
|
|
163
|
+
// grid with type + status pills and inline edit actions.
|
|
164
|
+
dashScreen(timeOff, { id: 'timeoff', title: 'Time off', order: 1 }, [
|
|
165
|
+
{ kpi: 'Requests', reduce: 'count', span: 1 },
|
|
166
|
+
{ kpi: 'Days booked', measure: 'days', reduce: 'sum', span: 1 },
|
|
167
|
+
{ kpi: 'Avg days', measure: 'days', reduce: 'avg', span: 1 },
|
|
168
|
+
{ gauge: 'Avg days per request', measure: 'days', reduce: 'avg', min: 0, max: 20, unit: 'd', span: 1 },
|
|
169
|
+
{ chart: 'startDate', measure: 'days', reduce: 'sum', type: 'area', span: 3 },
|
|
170
|
+
{ chart: 'type', reduce: 'count', type: 'pie', span: 1 },
|
|
171
|
+
{ chart: 'status', reduce: 'count', type: 'bar', span: 2 },
|
|
172
|
+
{ filter: ['type', 'status'], span: 3 },
|
|
173
|
+
{ grid: true, format: timeOffFormats, summaries: true, rowActions: [{ kind: 'edit' }], span: 3 },
|
|
174
|
+
]),
|
|
175
|
+
// Employee 360: a full record page (header with the department subtitle, a
|
|
176
|
+
// status pill and salary/performance tiles) plus a time-off timeline tab.
|
|
177
|
+
detailScreen(employees, { id: 'employee-360', title: 'Employee 360', order: 2 }, {
|
|
178
|
+
titleField: 'name', subtitleField: 'deptId', statusField: 'status', metricFields: ['salary', 'performance'],
|
|
179
|
+
related: [{ entity: 'timeOff', foreignKey: 'employeeId', label: 'Time off', titleField: 'type', dateField: 'startDate', statusField: 'status' }],
|
|
180
|
+
}),
|
|
181
|
+
screen(departments, 'master-detail', { id: 'departments', title: 'Departments', order: 3, child: employees, foreignKey: 'deptId' }),
|
|
182
|
+
formScreen(employees, { id: 'employees', title: 'Employees', order: 4 }, undefined, { format: empFormats, summaries: true, rowActions: [{ kind: 'edit' }], rowLink: { screen: 'employee-360', sourceField: 'id', targetField: 'id' } }, ['deptId', 'level', 'status']),
|
|
183
|
+
formScreen(timeOff, { id: 'requests', title: 'Requests', order: 5 }, undefined, { format: timeOffFormats, summaries: true }, ['employeeId', 'type', 'status']),
|
|
184
|
+
// Org chart: the department hierarchy as a collapsible tree (parentId ->
|
|
185
|
+
// id, self-referential), beside the department grid.
|
|
186
|
+
dashScreen(departments, { id: 'org', title: 'Org chart', order: 6 }, [
|
|
187
|
+
{ tree: { labelField: 'name', parentField: 'parentId' }, span: 3 },
|
|
188
|
+
{ grid: true, summaries: true, span: 3 },
|
|
189
|
+
]),
|
|
190
|
+
],
|
|
191
|
+
})
|
|
192
|
+
},
|
|
193
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sample-app gallery: ready-made, polished `StudioProject`s a user loads to
|
|
3
|
+
* see a beautiful working multi-entity app in a couple of clicks, then tweaks or
|
|
4
|
+
* rebinds to their own backend. Each is plain data (entities + screens + curated
|
|
5
|
+
* seed + theme), so it round-trips and generates like any hand-built project.
|
|
6
|
+
*/
|
|
7
|
+
import type { SampleApp } from './shared.js'
|
|
8
|
+
import { crm } from './crm.js'
|
|
9
|
+
import { ecommerce } from './ecommerce.js'
|
|
10
|
+
import { projectTracker } from './projects.js'
|
|
11
|
+
import { support } from './support.js'
|
|
12
|
+
import { subscriptions } from './subscriptions.js'
|
|
13
|
+
import { invoicing } from './invoicing.js'
|
|
14
|
+
import { inventory } from './inventory.js'
|
|
15
|
+
import { ats } from './ats.js'
|
|
16
|
+
import { hr } from './hr.js'
|
|
17
|
+
import { clinic } from './clinic.js'
|
|
18
|
+
import { school } from './school.js'
|
|
19
|
+
import { events } from './events.js'
|
|
20
|
+
import { restaurant } from './restaurant.js'
|
|
21
|
+
import { realEstate } from './realestate.js'
|
|
22
|
+
import { fleet } from './fleet.js'
|
|
23
|
+
import { gym } from './gym.js'
|
|
24
|
+
import { library } from './library.js'
|
|
25
|
+
import { insurance } from './insurance.js'
|
|
26
|
+
|
|
27
|
+
export type { SampleApp } from './shared.js'
|
|
28
|
+
export { liveDataSamples, getLiveDataSample } from './live-data.js'
|
|
29
|
+
|
|
30
|
+
/** All shipped sample apps, in gallery order. */
|
|
31
|
+
export const sampleApps: SampleApp[] = [
|
|
32
|
+
crm,
|
|
33
|
+
ecommerce,
|
|
34
|
+
subscriptions,
|
|
35
|
+
invoicing,
|
|
36
|
+
inventory,
|
|
37
|
+
support,
|
|
38
|
+
ats,
|
|
39
|
+
hr,
|
|
40
|
+
projectTracker,
|
|
41
|
+
clinic,
|
|
42
|
+
school,
|
|
43
|
+
events,
|
|
44
|
+
restaurant,
|
|
45
|
+
realEstate,
|
|
46
|
+
fleet,
|
|
47
|
+
gym,
|
|
48
|
+
library,
|
|
49
|
+
insurance,
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
/** Look up a sample app by id (the `--template` value). */
|
|
53
|
+
export function getSampleApp(id: string): SampleApp | undefined {
|
|
54
|
+
return sampleApps.find((s) => s.id === id)
|
|
55
|
+
}
|