@rippling/rippling-sdk 0.2.0-alpha.35 → 0.2.0-alpha.37

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 (149) hide show
  1. package/client.d.mts +13 -1
  2. package/client.d.mts.map +1 -1
  3. package/client.d.ts +13 -1
  4. package/client.d.ts.map +1 -1
  5. package/client.js +13 -1
  6. package/client.js.map +1 -1
  7. package/client.mjs +13 -1
  8. package/client.mjs.map +1 -1
  9. package/examples/.keep +4 -0
  10. package/examples/manifest/dental-practice-management/dental-practice-management.ts +638 -0
  11. package/examples/manifest/dental-practice-management/manifest.json +1870 -0
  12. package/examples/manifest/gym-membership-management/gym-membership-management.ts +746 -0
  13. package/examples/manifest/gym-membership-management/manifest.json +1975 -0
  14. package/examples/sdui/sdui-crud-todo/rippling-function.ts +367 -0
  15. package/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +282 -0
  16. package/examples/sdui/sdui-showcase/rippling-function.ts +381 -0
  17. package/examples/sdui/sdui-showcase/sdui-showcase.ts +351 -0
  18. package/examples/sdui/sdui-tab-multi-view/rippling-function.ts +1061 -0
  19. package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +924 -0
  20. package/examples/sdui/vote/rippling-function.ts +330 -0
  21. package/examples/sdui/vote/vote.ts +298 -0
  22. package/internal/utils/log.d.mts.map +1 -1
  23. package/internal/utils/log.d.ts.map +1 -1
  24. package/internal/utils/log.js +2 -0
  25. package/internal/utils/log.js.map +1 -1
  26. package/internal/utils/log.mjs +2 -0
  27. package/internal/utils/log.mjs.map +1 -1
  28. package/lib/manifest/custom-object-field-section.d.mts +4 -0
  29. package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
  30. package/lib/manifest/custom-object-field-section.d.ts +4 -0
  31. package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
  32. package/lib/manifest/custom-object-field-section.js +6 -0
  33. package/lib/manifest/custom-object-field-section.js.map +1 -1
  34. package/lib/manifest/custom-object-field-section.mjs +6 -0
  35. package/lib/manifest/custom-object-field-section.mjs.map +1 -1
  36. package/lib/manifest/custom-object-page-layout.d.mts +21 -13
  37. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  38. package/lib/manifest/custom-object-page-layout.d.ts +21 -13
  39. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  40. package/lib/manifest/custom-object-page-layout.js +119 -9
  41. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  42. package/lib/manifest/custom-object-page-layout.mjs +119 -9
  43. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  44. package/lib/manifest/field.d.mts +5 -1
  45. package/lib/manifest/field.d.mts.map +1 -1
  46. package/lib/manifest/field.d.ts +5 -1
  47. package/lib/manifest/field.d.ts.map +1 -1
  48. package/lib/manifest/field.js +8 -0
  49. package/lib/manifest/field.js.map +1 -1
  50. package/lib/manifest/field.mjs +8 -0
  51. package/lib/manifest/field.mjs.map +1 -1
  52. package/lib/manifest/index.d.mts +1 -1
  53. package/lib/manifest/index.d.mts.map +1 -1
  54. package/lib/manifest/index.d.ts +1 -1
  55. package/lib/manifest/index.d.ts.map +1 -1
  56. package/lib/manifest/index.js.map +1 -1
  57. package/lib/manifest/index.mjs.map +1 -1
  58. package/package.json +11 -1
  59. package/resources/company-legal-entities.d.mts +180 -0
  60. package/resources/company-legal-entities.d.mts.map +1 -0
  61. package/resources/company-legal-entities.d.ts +180 -0
  62. package/resources/company-legal-entities.d.ts.map +1 -0
  63. package/resources/company-legal-entities.js +30 -0
  64. package/resources/company-legal-entities.js.map +1 -0
  65. package/resources/company-legal-entities.mjs +26 -0
  66. package/resources/company-legal-entities.mjs.map +1 -0
  67. package/resources/company-legal-entity-workers.d.mts +79 -0
  68. package/resources/company-legal-entity-workers.d.mts.map +1 -0
  69. package/resources/company-legal-entity-workers.d.ts +79 -0
  70. package/resources/company-legal-entity-workers.d.ts.map +1 -0
  71. package/resources/company-legal-entity-workers.js +30 -0
  72. package/resources/company-legal-entity-workers.js.map +1 -0
  73. package/resources/company-legal-entity-workers.mjs +26 -0
  74. package/resources/company-legal-entity-workers.mjs.map +1 -0
  75. package/resources/earning-types.d.mts +1 -6
  76. package/resources/earning-types.d.mts.map +1 -1
  77. package/resources/earning-types.d.ts +1 -6
  78. package/resources/earning-types.d.ts.map +1 -1
  79. package/resources/earning-types.js +1 -1
  80. package/resources/earning-types.mjs +1 -1
  81. package/resources/earnings-inputs.d.mts +1 -1
  82. package/resources/earnings-inputs.d.ts +1 -1
  83. package/resources/earnings-inputs.js +1 -1
  84. package/resources/earnings-inputs.mjs +1 -1
  85. package/resources/index.d.mts +3 -1
  86. package/resources/index.d.mts.map +1 -1
  87. package/resources/index.d.ts +3 -1
  88. package/resources/index.d.ts.map +1 -1
  89. package/resources/index.js +7 -3
  90. package/resources/index.js.map +1 -1
  91. package/resources/index.mjs +3 -1
  92. package/resources/index.mjs.map +1 -1
  93. package/resources/legal-entities.d.mts +1 -1
  94. package/resources/legal-entities.d.mts.map +1 -1
  95. package/resources/legal-entities.d.ts +1 -1
  96. package/resources/legal-entities.d.ts.map +1 -1
  97. package/resources/payroll-runs/index.d.mts +3 -0
  98. package/resources/payroll-runs/index.d.mts.map +1 -0
  99. package/resources/payroll-runs/index.d.ts +3 -0
  100. package/resources/payroll-runs/index.d.ts.map +1 -0
  101. package/resources/payroll-runs/index.js +9 -0
  102. package/resources/payroll-runs/index.js.map +1 -0
  103. package/resources/payroll-runs/index.mjs +4 -0
  104. package/resources/payroll-runs/index.mjs.map +1 -0
  105. package/resources/payroll-runs/payroll-runs.d.mts +199 -0
  106. package/resources/payroll-runs/payroll-runs.d.mts.map +1 -0
  107. package/resources/payroll-runs/payroll-runs.d.ts +199 -0
  108. package/resources/payroll-runs/payroll-runs.d.ts.map +1 -0
  109. package/resources/payroll-runs/payroll-runs.js +40 -0
  110. package/resources/payroll-runs/payroll-runs.js.map +1 -0
  111. package/resources/payroll-runs/payroll-runs.mjs +35 -0
  112. package/resources/payroll-runs/payroll-runs.mjs.map +1 -0
  113. package/resources/payroll-runs/worker-payroll-records.d.mts +200 -0
  114. package/resources/payroll-runs/worker-payroll-records.d.mts.map +1 -0
  115. package/resources/payroll-runs/worker-payroll-records.d.ts +200 -0
  116. package/resources/payroll-runs/worker-payroll-records.d.ts.map +1 -0
  117. package/resources/payroll-runs/worker-payroll-records.js +23 -0
  118. package/resources/payroll-runs/worker-payroll-records.js.map +1 -0
  119. package/resources/payroll-runs/worker-payroll-records.mjs +19 -0
  120. package/resources/payroll-runs/worker-payroll-records.mjs.map +1 -0
  121. package/resources/payroll-runs.d.mts +1 -194
  122. package/resources/payroll-runs.d.mts.map +1 -1
  123. package/resources/payroll-runs.d.ts +1 -194
  124. package/resources/payroll-runs.d.ts.map +1 -1
  125. package/resources/payroll-runs.js +2 -28
  126. package/resources/payroll-runs.js.map +1 -1
  127. package/resources/payroll-runs.mjs +1 -26
  128. package/resources/payroll-runs.mjs.map +1 -1
  129. package/src/client.ts +47 -7
  130. package/src/internal/utils/log.ts +2 -0
  131. package/src/lib/manifest/custom-object-field-section.ts +7 -0
  132. package/src/lib/manifest/custom-object-page-layout.ts +220 -18
  133. package/src/lib/manifest/field.ts +9 -1
  134. package/src/lib/manifest/index.ts +2 -0
  135. package/src/resources/company-legal-entities.ts +237 -0
  136. package/src/resources/company-legal-entity-workers.ts +115 -0
  137. package/src/resources/earning-types.ts +1 -7
  138. package/src/resources/earnings-inputs.ts +1 -1
  139. package/src/resources/index.ts +15 -1
  140. package/src/resources/legal-entities.ts +1 -1
  141. package/src/resources/payroll-runs/index.ts +15 -0
  142. package/src/resources/payroll-runs/payroll-runs.ts +270 -0
  143. package/src/resources/payroll-runs/worker-payroll-records.ts +255 -0
  144. package/src/resources/payroll-runs.ts +1 -249
  145. package/src/version.ts +1 -1
  146. package/version.d.mts +1 -1
  147. package/version.d.ts +1 -1
  148. package/version.js +1 -1
  149. package/version.mjs +1 -1
@@ -0,0 +1,1061 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * PASTE THIS ENTIRE FILE INTO THE RIPPLING FUNCTION EDITOR TO PREVIEW RENDERED
4
+ * SDUI (CUSTOM PAGE). DEPENDS ON @rippling/rippling-sdk AND @rippling/sdui-builder.
5
+ * MOCK DATA ONLY — NO objectApiName / CUSTOM OBJECTS.
6
+ * ----------------------------------------------------------------------------
7
+ */
8
+
9
+ /*
10
+ import SDUI from '@rippling/sdui-builder';
11
+ import { FunctionContext, FunctionEvent, FunctionResponse } from '@rippling/rippling-sdk';
12
+
13
+ const INITIAL_DOGS = [
14
+ {
15
+ id: 'dog_1',
16
+ _key: 'dog_1',
17
+ dog_name__c: 'Rex',
18
+ breed__c: 'Labrador',
19
+ age__c: 3,
20
+ total_training_time__c: 120,
21
+ },
22
+ {
23
+ id: 'dog_2',
24
+ _key: 'dog_2',
25
+ dog_name__c: 'Luna',
26
+ breed__c: 'Border Collie',
27
+ age__c: 5,
28
+ total_training_time__c: 340,
29
+ },
30
+ ];
31
+
32
+ const INITIAL_SESSIONS = [
33
+ {
34
+ id: 'sess_1',
35
+ _key: 'sess_1',
36
+ dog_id__c: 'dog_1',
37
+ session_date__c: '2026-04-01',
38
+ duration__c: 45,
39
+ training_type__c: 'Obedience',
40
+ notes__c: 'Recall work',
41
+ },
42
+ {
43
+ id: 'sess_2',
44
+ _key: 'sess_2',
45
+ dog_id__c: 'dog_2',
46
+ session_date__c: '2026-04-02',
47
+ duration__c: 60,
48
+ training_type__c: 'Agility',
49
+ notes__c: 'Weaves',
50
+ },
51
+ ];
52
+
53
+ const SESSION_TOTAL_MIN = INITIAL_SESSIONS.reduce((a, row) => a + Number(row.duration__c || 0), 0);
54
+
55
+ const PCT_TIME_OBEDIENCE = 43;
56
+ const PCT_TIME_AGILITY = 57;
57
+
58
+ const SESSION_TIME_SEGMENTS = [
59
+ { value: PCT_TIME_OBEDIENCE, segmentColor: '#7C3AED' },
60
+ { value: PCT_TIME_AGILITY, segmentColor: '#F97316' },
61
+ ];
62
+
63
+ let mutableDogs = INITIAL_DOGS.map((r) => ({ ...r }));
64
+ let mutableSessions = INITIAL_SESSIONS.map((r) => ({ ...r }));
65
+
66
+ function renderSpec(functionId: string) {
67
+ const s = SDUI.createState({
68
+ dogs: mutableDogs,
69
+ sessions: mutableSessions,
70
+ stats: {
71
+ dogCount: mutableDogs.length,
72
+ sessionCount: mutableSessions.length,
73
+ totalMinutes: SESSION_TOTAL_MIN,
74
+ },
75
+ selectedDog: null as unknown,
76
+ selectedSession: null as unknown,
77
+ showCreateDogModal: false,
78
+ showEditDogModal: false,
79
+ showCreateSessionModal: false,
80
+ showEditSessionModal: false,
81
+ dogForm: { dog_name: '', breed: '', age: '' },
82
+ sessionForm: {
83
+ dog_id: '',
84
+ session_date: '',
85
+ duration: '',
86
+ training_type: '',
87
+ notes: '',
88
+ },
89
+ activeTab: 'overview',
90
+ });
91
+
92
+ // ============================================================
93
+ // HOME — redesigned: dashboard layout with hero stats on left,
94
+ // chart + recent activity stacked on right. Single content
95
+ // band, no purple hero background. Cleaner and more useful.
96
+ // ============================================================
97
+ const overviewPanel = SDUI.components.Layout({
98
+ width: '100%',
99
+ maxWidth: '1200px',
100
+ margin: { left: 'auto', right: 'auto' },
101
+ padding: { top: 40, bottom: 56, left: 28, right: 28 },
102
+ children: [
103
+ SDUI.components.VStack({
104
+ gap: 'space600',
105
+ align: 'stretch',
106
+ children: [
107
+ SDUI.components.VStack({
108
+ gap: 'space200',
109
+ align: 'flex-start',
110
+ children: [
111
+ SDUI.components.Text({
112
+ typestyle: 'bodyMdEmphasis',
113
+ color: 'secondary',
114
+ text: 'BARK & TRAIN · FIELD HQ',
115
+ }),
116
+ SDUI.components.Heading({
117
+ level: '1',
118
+ text: 'Good morning, trainer 👋',
119
+ }),
120
+ SDUI.components.Text({
121
+ typestyle: 'bodyLg',
122
+ color: 'secondary',
123
+ text: 'Here is what is happening across your roster and schedule.',
124
+ }),
125
+ ],
126
+ }),
127
+ SDUI.components.HStack({
128
+ gap: 'space400',
129
+ wrap: false,
130
+ align: 'stretch',
131
+ justify: 'space-between',
132
+ children: [
133
+ SDUI.components.Layout({
134
+ width: '32%',
135
+ minWidth: '260px',
136
+ children: [
137
+ SDUI.components.Card({
138
+ padding: '32',
139
+ children: [
140
+ SDUI.components.Layout({
141
+ padding: { top: 24, bottom: 24, left: 24, right: 24 },
142
+ backgroundColor: '#EEF2FF',
143
+ radius: 12,
144
+ children: [
145
+ SDUI.components.VStack({
146
+ gap: 'space300',
147
+ align: 'flex-start',
148
+ children: [
149
+ SDUI.components.Text({
150
+ text: 'DOGS ON ROSTER',
151
+ typestyle: 'bodyXsEmphasis',
152
+ color: '#4F46E5',
153
+ }),
154
+ SDUI.components.Heading({
155
+ level: '1',
156
+ text: SDUI.template`${s.stats.dogCount}`,
157
+ }),
158
+ SDUI.components.Text({
159
+ text: 'Active clients',
160
+ color: 'secondary',
161
+ typestyle: 'bodySm',
162
+ }),
163
+ ],
164
+ }),
165
+ ],
166
+ }),
167
+ ],
168
+ }),
169
+ ],
170
+ }),
171
+ SDUI.components.Layout({
172
+ width: '32%',
173
+ minWidth: '260px',
174
+ children: [
175
+ SDUI.components.Card({
176
+ padding: '32',
177
+ children: [
178
+ SDUI.components.Layout({
179
+ padding: { top: 24, bottom: 24, left: 24, right: 24 },
180
+ backgroundColor: '#ECFDF5',
181
+ radius: 12,
182
+ children: [
183
+ SDUI.components.VStack({
184
+ gap: 'space300',
185
+ align: 'flex-start',
186
+ children: [
187
+ SDUI.components.Text({
188
+ text: 'SESSIONS BOOKED',
189
+ typestyle: 'bodyXsEmphasis',
190
+ color: '#059669',
191
+ }),
192
+ SDUI.components.Heading({
193
+ level: '1',
194
+ text: SDUI.template`${s.stats.sessionCount}`,
195
+ }),
196
+ SDUI.components.Text({
197
+ text: 'On the calendar',
198
+ color: 'secondary',
199
+ typestyle: 'bodySm',
200
+ }),
201
+ ],
202
+ }),
203
+ ],
204
+ }),
205
+ ],
206
+ }),
207
+ ],
208
+ }),
209
+ SDUI.components.Layout({
210
+ width: '32%',
211
+ minWidth: '260px',
212
+ children: [
213
+ SDUI.components.Card({
214
+ padding: '32',
215
+ children: [
216
+ SDUI.components.Layout({
217
+ padding: { top: 24, bottom: 24, left: 24, right: 24 },
218
+ backgroundColor: '#FFF7ED',
219
+ radius: 12,
220
+ children: [
221
+ SDUI.components.VStack({
222
+ gap: 'space300',
223
+ align: 'flex-start',
224
+ children: [
225
+ SDUI.components.Text({
226
+ text: 'PRACTICE MINUTES',
227
+ typestyle: 'bodyXsEmphasis',
228
+ color: '#EA580C',
229
+ }),
230
+ SDUI.components.Heading({
231
+ level: '1',
232
+ text: SDUI.template`${s.stats.totalMinutes}`,
233
+ }),
234
+ SDUI.components.Text({
235
+ text: 'Ring & field time',
236
+ color: 'secondary',
237
+ typestyle: 'bodySm',
238
+ }),
239
+ ],
240
+ }),
241
+ ],
242
+ }),
243
+ ],
244
+ }),
245
+ ],
246
+ }),
247
+ ],
248
+ }),
249
+ // Chart + Quick actions row — 66% / 32%
250
+ SDUI.components.HStack({
251
+ gap: 'space400',
252
+ wrap: true,
253
+ align: 'stretch',
254
+ justify: 'space-between',
255
+ children: [
256
+ SDUI.components.Layout({
257
+ width: '66%',
258
+ minWidth: '420px',
259
+ children: [
260
+ SDUI.components.Card({
261
+ title: 'Where practice time goes',
262
+ padding: '32',
263
+ children: [
264
+ SDUI.components.VStack({
265
+ gap: 'space400',
266
+ align: 'stretch',
267
+ children: [
268
+ SDUI.components.Text({
269
+ typestyle: 'bodyMd',
270
+ color: 'secondary',
271
+ text: 'Split across lesson types from your scheduled sessions.',
272
+ }),
273
+ // DistributionBar with no wrapping Layout — direct child of VStack
274
+ SDUI.components.DistributionBar({
275
+ segments: SESSION_TIME_SEGMENTS,
276
+ totalValue: 100,
277
+ size: 'standard',
278
+ }),
279
+ SDUI.components.HStack({
280
+ gap: 'space300',
281
+ wrap: true,
282
+ justify: 'flex-start',
283
+ children: [
284
+ SDUI.components.Badge({
285
+ text: SDUI.template`Obedience · ${PCT_TIME_OBEDIENCE}%`,
286
+ appearance: 'PRIMARY_LIGHT',
287
+ }),
288
+ SDUI.components.Badge({
289
+ text: SDUI.template`Agility · ${PCT_TIME_AGILITY}%`,
290
+ appearance: 'WARNING_LIGHT',
291
+ }),
292
+ ],
293
+ }),
294
+ SDUI.components.Divider({}),
295
+ SDUI.components.HStack({
296
+ justify: 'space-between',
297
+ align: 'center',
298
+ children: [
299
+ SDUI.components.Text({
300
+ typestyle: 'bodySm',
301
+ color: 'secondary',
302
+ text: 'Want to dig in?',
303
+ }),
304
+ SDUI.components.Button({
305
+ label: 'Open Schedule',
306
+ appearance: 'OUTLINE',
307
+ size: 'S',
308
+ onPress: SDUI.actions.set(s.activeTab, 'sessions'),
309
+ }),
310
+ ],
311
+ }),
312
+ ],
313
+ }),
314
+ ],
315
+ }),
316
+ ],
317
+ }),
318
+ SDUI.components.Layout({
319
+ width: '32%',
320
+ minWidth: '280px',
321
+ children: [
322
+ SDUI.components.Card({
323
+ title: 'Quick actions',
324
+ padding: '32',
325
+ children: [
326
+ SDUI.components.VStack({
327
+ gap: 'space300',
328
+ align: 'stretch',
329
+ children: [
330
+ SDUI.components.Button({
331
+ label: 'Add new dog',
332
+ appearance: 'PRIMARY',
333
+ size: 'M',
334
+ isFluid: true,
335
+ onPress: [
336
+ SDUI.actions.set(s.dogForm, { dog_name: '', breed: '', age: '' }),
337
+ SDUI.actions.set(s.showCreateDogModal, true),
338
+ ],
339
+ }),
340
+ SDUI.components.Button({
341
+ label: 'Schedule a session',
342
+ appearance: 'OUTLINE',
343
+ size: 'M',
344
+ isFluid: true,
345
+ onPress: [
346
+ SDUI.actions.set(s.sessionForm, {
347
+ dog_id: '',
348
+ session_date: '',
349
+ duration: '',
350
+ training_type: '',
351
+ notes: '',
352
+ }),
353
+ SDUI.actions.set(s.showCreateSessionModal, true),
354
+ ],
355
+ }),
356
+ SDUI.components.Divider({}),
357
+ // Use Link components instead of Button GHOST — they
358
+ // naturally left-align as text and look cleaner here.
359
+ SDUI.components.Text({
360
+ typestyle: 'bodyXsEmphasis',
361
+ color: 'secondary',
362
+ text: 'JUMP TO',
363
+ }),
364
+ SDUI.components.Link({
365
+ text: '→ View all dogs',
366
+ href: '#',
367
+ onPress: SDUI.actions.set(s.activeTab, 'dogs'),
368
+ }),
369
+ SDUI.components.Link({
370
+ text: '→ View today',
371
+ href: '#',
372
+ onPress: SDUI.actions.set(s.activeTab, 'today'),
373
+ }),
374
+ ],
375
+ }),
376
+ ],
377
+ }),
378
+ ],
379
+ }),
380
+ ],
381
+ }),
382
+ ],
383
+ }),
384
+ ],
385
+ });
386
+ const dogsGrid = SDUI.components.Grid({
387
+ data: s.dogs,
388
+ width: '100%',
389
+ height: 520,
390
+ columns: [
391
+ { id: 'dog_name__c', header: 'Name', data: 'dog_name__c', width: 220 },
392
+ { id: 'breed__c', header: 'Breed', data: 'breed__c', width: 280 },
393
+ { id: 'age__c', header: 'Age', data: 'age__c', width: 100 },
394
+ { id: 'total_training_time__c', header: 'Training time', data: 'total_training_time__c', width: 160 },
395
+ ],
396
+ selectedRow: s.selectedDog.$bind,
397
+ headerActions: [{ id: 'new_dog', label: 'Add Dog', icon: 'ADD', appearance: 'primary' }],
398
+ onHeaderAction: [
399
+ SDUI.actions.set(s.dogForm, { dog_name: '', breed: '', age: '' }),
400
+ SDUI.actions.set(s.showCreateDogModal, true),
401
+ ],
402
+ rowActions: [
403
+ { id: 'edit_dog', label: 'Edit', icon: 'EDIT_OUTLINE', placement: 'menu' },
404
+ { id: 'delete_dog', label: 'Delete', icon: 'TRASH_OUTLINE', placement: 'menu' },
405
+ ],
406
+ onRowAction: [
407
+ SDUI.actions.set(s.dogForm, {
408
+ dog_name: s.selectedDog['dog_name__c'],
409
+ breed: s.selectedDog['breed__c'],
410
+ age: s.selectedDog['age__c'],
411
+ }),
412
+ SDUI.actions.set(s.showEditDogModal, true),
413
+ ],
414
+ });
415
+
416
+ const sessionsGrid = SDUI.components.Grid({
417
+ data: s.sessions,
418
+ width: '100%',
419
+ height: 520,
420
+ columns: [
421
+ { id: 'session_date__c', header: 'Date', data: 'session_date__c', width: 160 },
422
+ { id: 'duration__c', header: 'Duration (min)', data: 'duration__c', width: 140 },
423
+ { id: 'training_type__c', header: 'Type', data: 'training_type__c', type: 'text', width: 180 },
424
+ { id: 'notes__c', header: 'Notes', data: 'notes__c', width: 320 },
425
+ ],
426
+ selectedRow: s.selectedSession.$bind,
427
+ headerActions: [{ id: 'new_session', label: 'Add Session', icon: 'ADD', appearance: 'primary' }],
428
+ onHeaderAction: [
429
+ SDUI.actions.set(s.sessionForm, {
430
+ dog_id: '',
431
+ session_date: '',
432
+ duration: '',
433
+ training_type: '',
434
+ notes: '',
435
+ }),
436
+ SDUI.actions.set(s.showCreateSessionModal, true),
437
+ ],
438
+ rowActions: [
439
+ { id: 'edit_session', label: 'Edit', icon: 'EDIT_OUTLINE', placement: 'menu' },
440
+ { id: 'delete_session', label: 'Delete', icon: 'TRASH_OUTLINE', placement: 'menu' },
441
+ ],
442
+ onRowAction: [
443
+ SDUI.actions.set(s.sessionForm, {
444
+ dog_id: s.selectedSession['dog_id__c'],
445
+ session_date: s.selectedSession['session_date__c'],
446
+ duration: s.selectedSession['duration__c'],
447
+ training_type: s.selectedSession['training_type__c'],
448
+ notes: s.selectedSession['notes__c'],
449
+ }),
450
+ SDUI.actions.set(s.showEditSessionModal, true),
451
+ ],
452
+ });
453
+
454
+ const dogsPanel = SDUI.components.Layout({
455
+ width: '100%',
456
+ maxWidth: '1200px',
457
+ margin: { left: 'auto', right: 'auto' },
458
+ padding: { top: 32, bottom: 48, left: 28, right: 28 },
459
+ children: [
460
+ SDUI.components.VStack({
461
+ gap: 'space500',
462
+ align: 'stretch',
463
+ children: [
464
+ SDUI.components.VStack({
465
+ gap: 'space200',
466
+ children: [
467
+ SDUI.components.Heading({
468
+ level: '1',
469
+ text: 'Your dogs',
470
+ }),
471
+ SDUI.components.Text({
472
+ typestyle: 'bodyLg',
473
+ color: 'secondary',
474
+ text: 'Add Dog or use ⋯ on a row to edit or delete.',
475
+ }),
476
+ ],
477
+ }),
478
+ dogsGrid,
479
+ ],
480
+ }),
481
+ ],
482
+ });
483
+
484
+ const sessionsPanel = SDUI.components.Layout({
485
+ width: '100%',
486
+ maxWidth: '1200px',
487
+ margin: { left: 'auto', right: 'auto' },
488
+ padding: { top: 32, bottom: 48, left: 28, right: 28 },
489
+ children: [
490
+ SDUI.components.VStack({
491
+ gap: 'space500',
492
+ align: 'stretch',
493
+ children: [
494
+ SDUI.components.VStack({
495
+ gap: 'space200',
496
+ children: [
497
+ SDUI.components.Heading({
498
+ level: '1',
499
+ text: 'Session schedule',
500
+ }),
501
+ SDUI.components.Text({
502
+ typestyle: 'bodyLg',
503
+ color: 'secondary',
504
+ text: 'Add Session or use ⋯ on a row. Dog ID must match a roster id (e.g. dog_1).',
505
+ }),
506
+ ],
507
+ }),
508
+ sessionsGrid,
509
+ ],
510
+ }),
511
+ ],
512
+ });
513
+
514
+ const todayPanel = SDUI.components.Layout({
515
+ width: '100%',
516
+ maxWidth: '1200px',
517
+ margin: { left: 'auto', right: 'auto' },
518
+ padding: { top: 32, bottom: 48, left: 28, right: 28 },
519
+ children: [
520
+ SDUI.components.VStack({
521
+ gap: 'space600',
522
+ align: 'stretch',
523
+ children: [
524
+ SDUI.components.VStack({
525
+ gap: 'space200',
526
+ children: [
527
+ SDUI.components.Heading({
528
+ level: '1',
529
+ text: 'Today & coming up',
530
+ }),
531
+ SDUI.components.Text({
532
+ typestyle: 'bodyLg',
533
+ color: 'secondary',
534
+ text: 'Snapshot of upcoming lessons — production would filter by “today” from your sessions.',
535
+ }),
536
+ ],
537
+ }),
538
+ SDUI.components.TableBasic({
539
+ title: 'Who’s on deck',
540
+ width: 1100,
541
+ columns: [
542
+ { header: 'When', key: 'when', width: '22%' },
543
+ { header: 'Dog', key: 'dog', width: '22%' },
544
+ { header: 'Type', key: 'type', width: '18%' },
545
+ { header: 'Focus', key: 'focus', width: '38%' },
546
+ ],
547
+ rows: [
548
+ { when: 'Apr 1 · AM', dog: 'Rex', type: 'Obedience', focus: 'Recall · ring A' },
549
+ { when: 'Apr 2 · PM', dog: 'Luna', type: 'Agility', focus: 'Weaves & contacts' },
550
+ ],
551
+ }),
552
+ SDUI.components.ActionCard({
553
+ title: 'Need to change something?',
554
+ caption: 'Full calendar editing lives on Schedule; roster edits live under Dogs.',
555
+ icon: 'CALENDAR',
556
+ alignment: 'CENTER',
557
+ primaryActionTitle: 'Open Schedule',
558
+ onPressPrimary: SDUI.actions.set(s.activeTab, 'sessions'),
559
+ secondaryActionTitle: 'Open Dogs',
560
+ onPressSecondary: SDUI.actions.set(s.activeTab, 'dogs'),
561
+ }),
562
+ ],
563
+ }),
564
+ ],
565
+ });
566
+
567
+ const resourcesPanel = SDUI.components.Layout({
568
+ width: '100%',
569
+ maxWidth: '1200px',
570
+ margin: { left: 'auto', right: 'auto' },
571
+ padding: { top: 32, bottom: 48, left: 28, right: 28 },
572
+ children: [
573
+ SDUI.components.VStack({
574
+ gap: 'space600',
575
+ align: 'stretch',
576
+ children: [
577
+ SDUI.components.VStack({
578
+ gap: 'space200',
579
+ children: [
580
+ SDUI.components.Heading({
581
+ level: '1',
582
+ text: 'Resources & safety',
583
+ }),
584
+ SDUI.components.Text({
585
+ typestyle: 'bodyLg',
586
+ color: 'secondary',
587
+ text: 'Replace these with real PDFs and links — structure stays the same for handlers.',
588
+ }),
589
+ ],
590
+ }),
591
+ SDUI.components.Tip({
592
+ variant: 'warning',
593
+ title: 'Reactive dogs at the gate',
594
+ description:
595
+ 'Stagger arrivals so reactive pups aren’t face-to-face in the lobby; post your muzzle policy here.',
596
+ }),
597
+ SDUI.components.Tip({
598
+ variant: 'info',
599
+ title: 'Emergency vet & after hours',
600
+ description: 'Pin clinic phone, poison control, and directions — trainers shouldn’t hunt for them mid-class.',
601
+ }),
602
+ SDUI.components.Card({
603
+ title: 'Files you’ll attach later',
604
+ padding: '32',
605
+ children: [
606
+ SDUI.components.VStack({
607
+ gap: 'space400',
608
+ align: 'stretch',
609
+ children: [
610
+ SDUI.components.Text({
611
+ typestyle: 'bodyLg',
612
+ color: 'secondary',
613
+ text: 'Typical uploads: liability waiver, vaccine proof, venue rules, incident report template.',
614
+ }),
615
+ SDUI.components.HStack({
616
+ gap: 'space300',
617
+ wrap: true,
618
+ children: [
619
+ SDUI.components.Badge({ text: 'Waivers', appearance: 'PRIMARY_LIGHT' }),
620
+ SDUI.components.Badge({ text: 'Vaccination records', appearance: 'SUCCESS_LIGHT' }),
621
+ SDUI.components.Badge({ text: 'Venue map', appearance: 'WARNING_LIGHT' }),
622
+ SDUI.components.Badge({ text: 'Incident log', appearance: 'INFO_LIGHT' }),
623
+ ],
624
+ }),
625
+ ],
626
+ }),
627
+ ],
628
+ }),
629
+ ],
630
+ }),
631
+ ],
632
+ });
633
+
634
+ const root = SDUI.components.Layout({
635
+ width: '100%',
636
+ minHeight: 920,
637
+ children: [
638
+ SDUI.components.VStack({
639
+ align: 'stretch',
640
+ gap: 'space500',
641
+ children: [
642
+ SDUI.components.Tab({
643
+ value: s.activeTab.$bind,
644
+ items: [
645
+ { label: 'Home', value: 'overview' },
646
+ { label: 'Dogs', value: 'dogs' },
647
+ { label: 'Schedule', value: 'sessions' },
648
+ { label: 'Today', value: 'today' },
649
+ { label: 'Resources', value: 'resources' },
650
+ ],
651
+ }),
652
+ SDUI.visible(SDUI.conditions.eq(s.activeTab, 'overview'), overviewPanel),
653
+ SDUI.visible(SDUI.conditions.eq(s.activeTab, 'dogs'), dogsPanel),
654
+ SDUI.visible(SDUI.conditions.eq(s.activeTab, 'sessions'), sessionsPanel),
655
+ SDUI.visible(SDUI.conditions.eq(s.activeTab, 'today'), todayPanel),
656
+ SDUI.visible(SDUI.conditions.eq(s.activeTab, 'resources'), resourcesPanel),
657
+
658
+ SDUI.components.Modal({
659
+ title: 'Add Dog',
660
+ isOpen: s.showCreateDogModal.$bind,
661
+ onClose: SDUI.actions.set(s.showCreateDogModal, false),
662
+ children: [
663
+ SDUI.components.VStack({
664
+ gap: 'space300',
665
+ children: [
666
+ SDUI.components.TextInput({
667
+ label: 'Dog Name',
668
+ name: 'dog_name',
669
+ value: s.dogForm.dog_name.$bind,
670
+ isRequired: true,
671
+ }),
672
+ SDUI.components.TextInput({
673
+ label: 'Breed',
674
+ name: 'breed',
675
+ value: s.dogForm.breed.$bind,
676
+ }),
677
+ SDUI.components.Number({
678
+ label: 'Age (years)',
679
+ name: 'age',
680
+ value: s.dogForm.age.$bind,
681
+ }),
682
+ ],
683
+ }),
684
+ SDUI.components.HStack({
685
+ gap: 'space200',
686
+ justify: 'flex-end',
687
+ children: [
688
+ SDUI.components.Button({
689
+ label: 'Cancel',
690
+ appearance: 'OUTLINE',
691
+ onPress: SDUI.actions.set(s.showCreateDogModal, false),
692
+ }),
693
+ SDUI.components.Button({
694
+ label: 'Create',
695
+ appearance: 'PRIMARY',
696
+ onPress: SDUI.actions.callFunction(functionId, 'create_dog', {
697
+ dog_name: s.dogForm.dog_name,
698
+ breed: s.dogForm.breed,
699
+ age: s.dogForm.age,
700
+ responsePath: '/dogs',
701
+ onSuccess: { set: { '/showCreateDogModal': false } },
702
+ }),
703
+ }),
704
+ ],
705
+ }),
706
+ ],
707
+ }),
708
+
709
+ SDUI.components.Modal({
710
+ title: 'Edit Dog',
711
+ isOpen: s.showEditDogModal.$bind,
712
+ onClose: SDUI.actions.set(s.showEditDogModal, false),
713
+ children: [
714
+ SDUI.components.VStack({
715
+ gap: 'space300',
716
+ children: [
717
+ SDUI.components.TextInput({
718
+ label: 'Dog Name',
719
+ name: 'edit_dog_name',
720
+ value: s.dogForm.dog_name.$bind,
721
+ isRequired: true,
722
+ }),
723
+ SDUI.components.TextInput({
724
+ label: 'Breed',
725
+ name: 'edit_breed',
726
+ value: s.dogForm.breed.$bind,
727
+ }),
728
+ SDUI.components.Number({
729
+ label: 'Age (years)',
730
+ name: 'edit_age',
731
+ value: s.dogForm.age.$bind,
732
+ }),
733
+ ],
734
+ }),
735
+ SDUI.components.HStack({
736
+ gap: 'space200',
737
+ justify: 'flex-end',
738
+ children: [
739
+ SDUI.components.Button({
740
+ label: 'Delete',
741
+ appearance: 'DESTRUCTIVE',
742
+ onPress: SDUI.actions.callFunction(functionId, 'delete_dog', {
743
+ id: s.selectedDog['id'],
744
+ responsePath: '/dogs',
745
+ onSuccess: { set: { '/showEditDogModal': false } },
746
+ }),
747
+ }),
748
+ SDUI.components.Button({
749
+ label: 'Cancel',
750
+ appearance: 'OUTLINE',
751
+ onPress: SDUI.actions.set(s.showEditDogModal, false),
752
+ }),
753
+ SDUI.components.Button({
754
+ label: 'Save',
755
+ appearance: 'PRIMARY',
756
+ onPress: SDUI.actions.callFunction(functionId, 'update_dog', {
757
+ id: s.selectedDog['id'],
758
+ dog_name: s.dogForm.dog_name,
759
+ breed: s.dogForm.breed,
760
+ age: s.dogForm.age,
761
+ responsePath: '/dogs',
762
+ onSuccess: { set: { '/showEditDogModal': false } },
763
+ }),
764
+ }),
765
+ ],
766
+ }),
767
+ ],
768
+ }),
769
+
770
+ SDUI.components.Modal({
771
+ title: 'Add Training Session',
772
+ isOpen: s.showCreateSessionModal.$bind,
773
+ onClose: SDUI.actions.set(s.showCreateSessionModal, false),
774
+ children: [
775
+ SDUI.components.VStack({
776
+ gap: 'space300',
777
+ children: [
778
+ SDUI.components.TextInput({
779
+ label: 'Dog ID',
780
+ name: 'dog_id',
781
+ value: s.sessionForm.dog_id.$bind,
782
+ isRequired: true,
783
+ }),
784
+ SDUI.components.TextInput({
785
+ label: 'Session Date',
786
+ name: 'session_date',
787
+ value: s.sessionForm.session_date.$bind,
788
+ isRequired: true,
789
+ }),
790
+ SDUI.components.Number({
791
+ label: 'Duration (min)',
792
+ name: 'duration',
793
+ value: s.sessionForm.duration.$bind,
794
+ }),
795
+ SDUI.components.Select({
796
+ label: 'Training Type',
797
+ name: 'training_type',
798
+ value: s.sessionForm.training_type.$bind,
799
+ options: [
800
+ { label: 'Obedience', value: 'Obedience' },
801
+ { label: 'Agility', value: 'Agility' },
802
+ { label: 'Socialization', value: 'Socialization' },
803
+ { label: 'Behavior', value: 'Behavior' },
804
+ ],
805
+ }),
806
+ SDUI.components.Textarea({
807
+ label: 'Notes',
808
+ name: 'notes',
809
+ value: s.sessionForm.notes.$bind,
810
+ }),
811
+ ],
812
+ }),
813
+ SDUI.components.HStack({
814
+ gap: 'space200',
815
+ justify: 'flex-end',
816
+ children: [
817
+ SDUI.components.Button({
818
+ label: 'Cancel',
819
+ appearance: 'OUTLINE',
820
+ onPress: SDUI.actions.set(s.showCreateSessionModal, false),
821
+ }),
822
+ SDUI.components.Button({
823
+ label: 'Create',
824
+ appearance: 'PRIMARY',
825
+ onPress: SDUI.actions.callFunction(functionId, 'create_session', {
826
+ dog_id: s.sessionForm.dog_id,
827
+ session_date: s.sessionForm.session_date,
828
+ duration: s.sessionForm.duration,
829
+ training_type: s.sessionForm.training_type,
830
+ notes: s.sessionForm.notes,
831
+ responsePath: '/sessions',
832
+ onSuccess: { set: { '/showCreateSessionModal': false } },
833
+ }),
834
+ }),
835
+ ],
836
+ }),
837
+ ],
838
+ }),
839
+
840
+ SDUI.components.Modal({
841
+ title: 'Edit Training Session',
842
+ isOpen: s.showEditSessionModal.$bind,
843
+ onClose: SDUI.actions.set(s.showEditSessionModal, false),
844
+ children: [
845
+ SDUI.components.VStack({
846
+ gap: 'space300',
847
+ children: [
848
+ SDUI.components.TextInput({
849
+ label: 'Dog ID',
850
+ name: 'edit_dog_id',
851
+ value: s.sessionForm.dog_id.$bind,
852
+ isRequired: true,
853
+ }),
854
+ SDUI.components.TextInput({
855
+ label: 'Session Date',
856
+ name: 'edit_session_date',
857
+ value: s.sessionForm.session_date.$bind,
858
+ isRequired: true,
859
+ }),
860
+ SDUI.components.Number({
861
+ label: 'Duration (min)',
862
+ name: 'edit_duration',
863
+ value: s.sessionForm.duration.$bind,
864
+ }),
865
+ SDUI.components.Select({
866
+ label: 'Training Type',
867
+ name: 'edit_training_type',
868
+ value: s.sessionForm.training_type.$bind,
869
+ options: [
870
+ { label: 'Obedience', value: 'Obedience' },
871
+ { label: 'Agility', value: 'Agility' },
872
+ { label: 'Socialization', value: 'Socialization' },
873
+ { label: 'Behavior', value: 'Behavior' },
874
+ ],
875
+ }),
876
+ SDUI.components.Textarea({
877
+ label: 'Notes',
878
+ name: 'edit_notes',
879
+ value: s.sessionForm.notes.$bind,
880
+ }),
881
+ ],
882
+ }),
883
+ SDUI.components.HStack({
884
+ gap: 'space200',
885
+ justify: 'flex-end',
886
+ children: [
887
+ SDUI.components.Button({
888
+ label: 'Delete',
889
+ appearance: 'DESTRUCTIVE',
890
+ onPress: SDUI.actions.callFunction(functionId, 'delete_session', {
891
+ id: s.selectedSession['id'],
892
+ responsePath: '/sessions',
893
+ onSuccess: { set: { '/showEditSessionModal': false } },
894
+ }),
895
+ }),
896
+ SDUI.components.Button({
897
+ label: 'Cancel',
898
+ appearance: 'OUTLINE',
899
+ onPress: SDUI.actions.set(s.showEditSessionModal, false),
900
+ }),
901
+ SDUI.components.Button({
902
+ label: 'Save',
903
+ appearance: 'PRIMARY',
904
+ onPress: SDUI.actions.callFunction(functionId, 'update_session', {
905
+ id: s.selectedSession['id'],
906
+ dog_id: s.sessionForm.dog_id,
907
+ session_date: s.sessionForm.session_date,
908
+ duration: s.sessionForm.duration,
909
+ training_type: s.sessionForm.training_type,
910
+ notes: s.sessionForm.notes,
911
+ responsePath: '/sessions',
912
+ onSuccess: { set: { '/showEditSessionModal': false } },
913
+ }),
914
+ }),
915
+ ],
916
+ }),
917
+ ],
918
+ }),
919
+ ],
920
+ }),
921
+ ],
922
+ });
923
+
924
+ return SDUI.render(s, root);
925
+ }
926
+
927
+ export async function onRipplingEvent(
928
+ event: FunctionEvent,
929
+ context: FunctionContext,
930
+ ): Promise<FunctionResponse> {
931
+ const params = event['parameters'] as Record<string, unknown> | undefined;
932
+ const action = params?.['action'] as string | undefined;
933
+ const functionId = context.function?.function_id ?? 'demo-fn';
934
+
935
+ if (!action || action === 'init') {
936
+ mutableDogs = INITIAL_DOGS.map((r) => ({ ...r }));
937
+ mutableSessions = INITIAL_SESSIONS.map((r) => ({ ...r }));
938
+ const spec = renderSpec(functionId);
939
+ return new FunctionResponse({
940
+ body: { spec: JSON.stringify(spec) },
941
+ statusCode: 200,
942
+ headers: {},
943
+ message: '',
944
+ });
945
+ }
946
+
947
+ const p = params ?? {};
948
+
949
+ if (action === 'create_dog') {
950
+ const id = `dog_${mutableDogs.length + 1}`;
951
+ mutableDogs = [
952
+ ...mutableDogs,
953
+ {
954
+ id,
955
+ _key: id,
956
+ dog_name__c: String(p['dog_name'] ?? 'Unnamed'),
957
+ breed__c: String(p['breed'] ?? ''),
958
+ age__c: p['age'] !== '' && p['age'] != null ? Number(p['age']) : 0,
959
+ total_training_time__c: 0,
960
+ },
961
+ ];
962
+ return new FunctionResponse({
963
+ body: mutableDogs as unknown as Record<string, unknown>,
964
+ statusCode: 200,
965
+ headers: {},
966
+ message: '',
967
+ });
968
+ }
969
+
970
+ if (action === 'update_dog') {
971
+ mutableDogs = mutableDogs.map((r) =>
972
+ r.id === p['id']
973
+ ? {
974
+ ...r,
975
+ dog_name__c: String(p['dog_name'] ?? r.dog_name__c),
976
+ breed__c: String(p['breed'] ?? r.breed__c),
977
+ age__c: p['age'] !== '' && p['age'] != null ? Number(p['age']) : r.age__c,
978
+ }
979
+ : r,
980
+ );
981
+ return new FunctionResponse({
982
+ body: mutableDogs as unknown as Record<string, unknown>,
983
+ statusCode: 200,
984
+ headers: {},
985
+ message: '',
986
+ });
987
+ }
988
+
989
+ if (action === 'delete_dog') {
990
+ mutableDogs = mutableDogs.filter((r) => r.id !== p['id']);
991
+ return new FunctionResponse({
992
+ body: mutableDogs as unknown as Record<string, unknown>,
993
+ statusCode: 200,
994
+ headers: {},
995
+ message: '',
996
+ });
997
+ }
998
+
999
+ if (action === 'create_session') {
1000
+ const id = `sess_${mutableSessions.length + 1}`;
1001
+ mutableSessions = [
1002
+ ...mutableSessions,
1003
+ {
1004
+ id,
1005
+ _key: id,
1006
+ dog_id__c: String(p['dog_id'] ?? ''),
1007
+ session_date__c: String(p['session_date'] ?? ''),
1008
+ duration__c: p['duration'] !== '' && p['duration'] != null ? Number(p['duration']) : 0,
1009
+ training_type__c: String(p['training_type'] ?? ''),
1010
+ notes__c: String(p['notes'] ?? ''),
1011
+ },
1012
+ ];
1013
+ return new FunctionResponse({
1014
+ body: mutableSessions as unknown as Record<string, unknown>,
1015
+ statusCode: 200,
1016
+ headers: {},
1017
+ message: '',
1018
+ });
1019
+ }
1020
+
1021
+ if (action === 'update_session') {
1022
+ mutableSessions = mutableSessions.map((r) =>
1023
+ r.id === p['id']
1024
+ ? {
1025
+ ...r,
1026
+ dog_id__c: String(p['dog_id'] ?? r.dog_id__c),
1027
+ session_date__c: String(p['session_date'] ?? r.session_date__c),
1028
+ duration__c: p['duration'] !== '' && p['duration'] != null ? Number(p['duration']) : r.duration__c,
1029
+ training_type__c: String(p['training_type'] ?? r.training_type__c),
1030
+ notes__c: String(p['notes'] ?? r.notes__c),
1031
+ }
1032
+ : r,
1033
+ );
1034
+ return new FunctionResponse({
1035
+ body: mutableSessions as unknown as Record<string, unknown>,
1036
+ statusCode: 200,
1037
+ headers: {},
1038
+ message: '',
1039
+ });
1040
+ }
1041
+
1042
+ if (action === 'delete_session') {
1043
+ mutableSessions = mutableSessions.filter((r) => r.id !== p['id']);
1044
+ return new FunctionResponse({
1045
+ body: mutableSessions as unknown as Record<string, unknown>,
1046
+ statusCode: 200,
1047
+ headers: {},
1048
+ message: '',
1049
+ });
1050
+ }
1051
+
1052
+ return new FunctionResponse({
1053
+ body: {},
1054
+ statusCode: 400,
1055
+ headers: {},
1056
+ message: 'Unknown action',
1057
+ });
1058
+ }
1059
+
1060
+
1061
+ */