@rippling/rippling-sdk 0.2.0-alpha.34 → 0.2.0-alpha.36

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 (270) hide show
  1. package/client.d.mts +6 -0
  2. package/client.d.mts.map +1 -1
  3. package/client.d.ts +6 -0
  4. package/client.d.ts.map +1 -1
  5. package/client.js +6 -0
  6. package/client.js.map +1 -1
  7. package/client.mjs +6 -0
  8. package/client.mjs.map +1 -1
  9. package/examples/.keep +4 -0
  10. package/examples/manifest/dental-practice-management/dental-practice-management.ts +651 -0
  11. package/examples/manifest/dental-practice-management/manifest.json +1870 -0
  12. package/examples/manifest/gym-membership-management/gym-membership-management.ts +757 -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/lib/manifest/app.d.mts +81 -13
  23. package/lib/manifest/app.d.mts.map +1 -1
  24. package/lib/manifest/app.d.ts +81 -13
  25. package/lib/manifest/app.d.ts.map +1 -1
  26. package/lib/manifest/app.js +37 -16
  27. package/lib/manifest/app.js.map +1 -1
  28. package/lib/manifest/app.mjs +37 -16
  29. package/lib/manifest/app.mjs.map +1 -1
  30. package/lib/manifest/category.d.mts +63 -7
  31. package/lib/manifest/category.d.mts.map +1 -1
  32. package/lib/manifest/category.d.ts +63 -7
  33. package/lib/manifest/category.d.ts.map +1 -1
  34. package/lib/manifest/category.js +45 -10
  35. package/lib/manifest/category.js.map +1 -1
  36. package/lib/manifest/category.mjs +43 -8
  37. package/lib/manifest/category.mjs.map +1 -1
  38. package/lib/manifest/custom-object-field-section.d.mts +56 -6
  39. package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
  40. package/lib/manifest/custom-object-field-section.d.ts +56 -6
  41. package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
  42. package/lib/manifest/custom-object-field-section.js +41 -14
  43. package/lib/manifest/custom-object-field-section.js.map +1 -1
  44. package/lib/manifest/custom-object-field-section.mjs +39 -12
  45. package/lib/manifest/custom-object-field-section.mjs.map +1 -1
  46. package/lib/manifest/custom-object-page-layout.d.mts +201 -41
  47. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  48. package/lib/manifest/custom-object-page-layout.d.ts +201 -41
  49. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  50. package/lib/manifest/custom-object-page-layout.js +76 -30
  51. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  52. package/lib/manifest/custom-object-page-layout.mjs +74 -28
  53. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  54. package/lib/manifest/custom-object.d.mts +128 -15
  55. package/lib/manifest/custom-object.d.mts.map +1 -1
  56. package/lib/manifest/custom-object.d.ts +128 -15
  57. package/lib/manifest/custom-object.d.ts.map +1 -1
  58. package/lib/manifest/custom-object.js +61 -26
  59. package/lib/manifest/custom-object.js.map +1 -1
  60. package/lib/manifest/custom-object.mjs +61 -26
  61. package/lib/manifest/custom-object.mjs.map +1 -1
  62. package/lib/manifest/field.d.mts +265 -54
  63. package/lib/manifest/field.d.mts.map +1 -1
  64. package/lib/manifest/field.d.ts +265 -54
  65. package/lib/manifest/field.d.ts.map +1 -1
  66. package/lib/manifest/field.js +100 -57
  67. package/lib/manifest/field.js.map +1 -1
  68. package/lib/manifest/field.mjs +100 -57
  69. package/lib/manifest/field.mjs.map +1 -1
  70. package/lib/manifest/list-view.d.mts +105 -14
  71. package/lib/manifest/list-view.d.mts.map +1 -1
  72. package/lib/manifest/list-view.d.ts +105 -14
  73. package/lib/manifest/list-view.d.ts.map +1 -1
  74. package/lib/manifest/list-view.js +50 -14
  75. package/lib/manifest/list-view.js.map +1 -1
  76. package/lib/manifest/list-view.mjs +48 -12
  77. package/lib/manifest/list-view.mjs.map +1 -1
  78. package/lib/manifest/manifest-builder.d.mts +77 -11
  79. package/lib/manifest/manifest-builder.d.mts.map +1 -1
  80. package/lib/manifest/manifest-builder.d.ts +77 -11
  81. package/lib/manifest/manifest-builder.d.ts.map +1 -1
  82. package/lib/manifest/manifest-builder.js +46 -24
  83. package/lib/manifest/manifest-builder.js.map +1 -1
  84. package/lib/manifest/manifest-builder.mjs +44 -22
  85. package/lib/manifest/manifest-builder.mjs.map +1 -1
  86. package/lib/manifest/manifest-function.d.mts +92 -12
  87. package/lib/manifest/manifest-function.d.mts.map +1 -1
  88. package/lib/manifest/manifest-function.d.ts +92 -12
  89. package/lib/manifest/manifest-function.d.ts.map +1 -1
  90. package/lib/manifest/manifest-function.js +40 -11
  91. package/lib/manifest/manifest-function.js.map +1 -1
  92. package/lib/manifest/manifest-function.mjs +38 -9
  93. package/lib/manifest/manifest-function.mjs.map +1 -1
  94. package/lib/manifest/rule.d.mts +94 -16
  95. package/lib/manifest/rule.d.mts.map +1 -1
  96. package/lib/manifest/rule.d.ts +94 -16
  97. package/lib/manifest/rule.d.ts.map +1 -1
  98. package/lib/manifest/rule.js +36 -15
  99. package/lib/manifest/rule.js.map +1 -1
  100. package/lib/manifest/rule.mjs +36 -15
  101. package/lib/manifest/rule.mjs.map +1 -1
  102. package/lib/manifest/sdui-page.d.mts +67 -11
  103. package/lib/manifest/sdui-page.d.mts.map +1 -1
  104. package/lib/manifest/sdui-page.d.ts +67 -11
  105. package/lib/manifest/sdui-page.d.ts.map +1 -1
  106. package/lib/manifest/sdui-page.js +44 -13
  107. package/lib/manifest/sdui-page.js.map +1 -1
  108. package/lib/manifest/sdui-page.mjs +42 -11
  109. package/lib/manifest/sdui-page.mjs.map +1 -1
  110. package/lib/manifest/validation.d.mts +87 -12
  111. package/lib/manifest/validation.d.mts.map +1 -1
  112. package/lib/manifest/validation.d.ts +87 -12
  113. package/lib/manifest/validation.d.ts.map +1 -1
  114. package/lib/manifest/validation.js +41 -15
  115. package/lib/manifest/validation.js.map +1 -1
  116. package/lib/manifest/validation.mjs +39 -13
  117. package/lib/manifest/validation.mjs.map +1 -1
  118. package/lib/sdui/actions.d.mts +25 -0
  119. package/lib/sdui/actions.d.mts.map +1 -0
  120. package/lib/sdui/actions.d.ts +25 -0
  121. package/lib/sdui/actions.d.ts.map +1 -0
  122. package/lib/sdui/actions.js +53 -0
  123. package/lib/sdui/actions.js.map +1 -0
  124. package/lib/sdui/actions.mjs +42 -0
  125. package/lib/sdui/actions.mjs.map +1 -0
  126. package/lib/sdui/component.d.mts +41 -0
  127. package/lib/sdui/component.d.mts.map +1 -0
  128. package/lib/sdui/component.d.ts +41 -0
  129. package/lib/sdui/component.d.ts.map +1 -0
  130. package/lib/sdui/component.js +95 -0
  131. package/lib/sdui/component.js.map +1 -0
  132. package/lib/sdui/component.mjs +91 -0
  133. package/lib/sdui/component.mjs.map +1 -0
  134. package/lib/sdui/components.d.mts +149 -0
  135. package/lib/sdui/components.d.mts.map +1 -0
  136. package/lib/sdui/components.d.ts +149 -0
  137. package/lib/sdui/components.d.ts.map +1 -0
  138. package/lib/sdui/components.js +303 -0
  139. package/lib/sdui/components.js.map +1 -0
  140. package/lib/sdui/components.mjs +298 -0
  141. package/lib/sdui/components.mjs.map +1 -0
  142. package/lib/sdui/conditions.d.mts +25 -0
  143. package/lib/sdui/conditions.d.mts.map +1 -0
  144. package/lib/sdui/conditions.d.ts +25 -0
  145. package/lib/sdui/conditions.d.ts.map +1 -0
  146. package/lib/sdui/conditions.js +47 -0
  147. package/lib/sdui/conditions.js.map +1 -0
  148. package/lib/sdui/conditions.mjs +35 -0
  149. package/lib/sdui/conditions.mjs.map +1 -0
  150. package/lib/sdui/directives.d.mts +18 -0
  151. package/lib/sdui/directives.d.mts.map +1 -0
  152. package/lib/sdui/directives.d.ts +18 -0
  153. package/lib/sdui/directives.d.ts.map +1 -0
  154. package/lib/sdui/directives.js +28 -0
  155. package/lib/sdui/directives.js.map +1 -0
  156. package/lib/sdui/directives.mjs +21 -0
  157. package/lib/sdui/directives.mjs.map +1 -0
  158. package/lib/sdui/index.d.mts +362 -0
  159. package/lib/sdui/index.d.mts.map +1 -0
  160. package/lib/sdui/index.d.ts +362 -0
  161. package/lib/sdui/index.d.ts.map +1 -0
  162. package/lib/sdui/index.js +375 -0
  163. package/lib/sdui/index.js.map +1 -0
  164. package/lib/sdui/index.mjs +199 -0
  165. package/lib/sdui/index.mjs.map +1 -0
  166. package/lib/sdui/props.d.mts +12 -0
  167. package/lib/sdui/props.d.mts.map +1 -0
  168. package/lib/sdui/props.d.ts +12 -0
  169. package/lib/sdui/props.d.ts.map +1 -0
  170. package/lib/sdui/props.js +25 -0
  171. package/lib/sdui/props.js.map +1 -0
  172. package/lib/sdui/props.mjs +21 -0
  173. package/lib/sdui/props.mjs.map +1 -0
  174. package/lib/sdui/render.d.mts +23 -0
  175. package/lib/sdui/render.d.mts.map +1 -0
  176. package/lib/sdui/render.d.ts +23 -0
  177. package/lib/sdui/render.d.ts.map +1 -0
  178. package/lib/sdui/render.js +40 -0
  179. package/lib/sdui/render.js.map +1 -0
  180. package/lib/sdui/render.mjs +37 -0
  181. package/lib/sdui/render.mjs.map +1 -0
  182. package/lib/sdui/state.d.mts +22 -0
  183. package/lib/sdui/state.d.mts.map +1 -0
  184. package/lib/sdui/state.d.ts +22 -0
  185. package/lib/sdui/state.d.ts.map +1 -0
  186. package/lib/sdui/state.js +57 -0
  187. package/lib/sdui/state.js.map +1 -0
  188. package/lib/sdui/state.mjs +53 -0
  189. package/lib/sdui/state.mjs.map +1 -0
  190. package/package.json +11 -1
  191. package/resources/company-legal-entity-workers.d.mts +79 -0
  192. package/resources/company-legal-entity-workers.d.mts.map +1 -0
  193. package/resources/company-legal-entity-workers.d.ts +79 -0
  194. package/resources/company-legal-entity-workers.d.ts.map +1 -0
  195. package/resources/company-legal-entity-workers.js +30 -0
  196. package/resources/company-legal-entity-workers.js.map +1 -0
  197. package/resources/company-legal-entity-workers.mjs +26 -0
  198. package/resources/company-legal-entity-workers.mjs.map +1 -0
  199. package/resources/draft-hires.d.mts +4 -0
  200. package/resources/draft-hires.d.mts.map +1 -1
  201. package/resources/draft-hires.d.ts +4 -0
  202. package/resources/draft-hires.d.ts.map +1 -1
  203. package/resources/files.d.mts +16 -1
  204. package/resources/files.d.mts.map +1 -1
  205. package/resources/files.d.ts +16 -1
  206. package/resources/files.d.ts.map +1 -1
  207. package/resources/headcount-positions.d.mts +2 -2
  208. package/resources/headcount-positions.d.mts.map +1 -1
  209. package/resources/headcount-positions.d.ts +2 -2
  210. package/resources/headcount-positions.d.ts.map +1 -1
  211. package/resources/index.d.mts +1 -0
  212. package/resources/index.d.mts.map +1 -1
  213. package/resources/index.d.ts +1 -0
  214. package/resources/index.d.ts.map +1 -1
  215. package/resources/index.js +4 -2
  216. package/resources/index.js.map +1 -1
  217. package/resources/index.mjs +1 -0
  218. package/resources/index.mjs.map +1 -1
  219. package/resources/legal-entities.d.mts +1 -1
  220. package/resources/legal-entities.d.mts.map +1 -1
  221. package/resources/legal-entities.d.ts +1 -1
  222. package/resources/legal-entities.d.ts.map +1 -1
  223. package/resources/location-factors.d.mts +2 -2
  224. package/resources/location-factors.d.mts.map +1 -1
  225. package/resources/location-factors.d.ts +2 -2
  226. package/resources/location-factors.d.ts.map +1 -1
  227. package/resources/shared.d.mts +1 -1
  228. package/resources/shared.d.mts.map +1 -1
  229. package/resources/shared.d.ts +1 -1
  230. package/resources/shared.d.ts.map +1 -1
  231. package/resources/workers.d.mts +2 -2
  232. package/resources/workers.d.mts.map +1 -1
  233. package/resources/workers.d.ts +2 -2
  234. package/resources/workers.d.ts.map +1 -1
  235. package/src/client.ts +20 -0
  236. package/src/lib/manifest/app.ts +81 -26
  237. package/src/lib/manifest/category.ts +63 -14
  238. package/src/lib/manifest/custom-object-field-section.ts +56 -17
  239. package/src/lib/manifest/custom-object-page-layout.ts +201 -66
  240. package/src/lib/manifest/custom-object.ts +128 -37
  241. package/src/lib/manifest/field.ts +265 -75
  242. package/src/lib/manifest/list-view.ts +105 -25
  243. package/src/lib/manifest/manifest-builder.ts +75 -32
  244. package/src/lib/manifest/manifest-function.ts +92 -22
  245. package/src/lib/manifest/rule.ts +95 -29
  246. package/src/lib/manifest/sdui-page.ts +67 -21
  247. package/src/lib/manifest/validation.ts +87 -24
  248. package/src/lib/sdui/actions.ts +58 -0
  249. package/src/lib/sdui/component.ts +122 -0
  250. package/src/lib/sdui/components.ts +308 -0
  251. package/src/lib/sdui/conditions.ts +59 -0
  252. package/src/lib/sdui/directives.ts +42 -0
  253. package/src/lib/sdui/index.ts +365 -0
  254. package/src/lib/sdui/props.ts +32 -0
  255. package/src/lib/sdui/render.ts +58 -0
  256. package/src/lib/sdui/state.ts +60 -0
  257. package/src/resources/company-legal-entity-workers.ts +115 -0
  258. package/src/resources/draft-hires.ts +5 -0
  259. package/src/resources/files.ts +18 -1
  260. package/src/resources/headcount-positions.ts +2 -0
  261. package/src/resources/index.ts +7 -0
  262. package/src/resources/legal-entities.ts +1 -0
  263. package/src/resources/location-factors.ts +2 -0
  264. package/src/resources/shared.ts +1 -0
  265. package/src/resources/workers.ts +2 -0
  266. package/src/version.ts +1 -1
  267. package/version.d.mts +1 -1
  268. package/version.d.ts +1 -1
  269. package/version.js +1 -1
  270. 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
+ */