@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.
- package/client.d.mts +6 -0
- package/client.d.mts.map +1 -1
- package/client.d.ts +6 -0
- package/client.d.ts.map +1 -1
- package/client.js +6 -0
- package/client.js.map +1 -1
- package/client.mjs +6 -0
- package/client.mjs.map +1 -1
- package/examples/.keep +4 -0
- package/examples/manifest/dental-practice-management/dental-practice-management.ts +651 -0
- package/examples/manifest/dental-practice-management/manifest.json +1870 -0
- package/examples/manifest/gym-membership-management/gym-membership-management.ts +757 -0
- package/examples/manifest/gym-membership-management/manifest.json +1975 -0
- package/examples/sdui/sdui-crud-todo/rippling-function.ts +367 -0
- package/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +282 -0
- package/examples/sdui/sdui-showcase/rippling-function.ts +381 -0
- package/examples/sdui/sdui-showcase/sdui-showcase.ts +351 -0
- package/examples/sdui/sdui-tab-multi-view/rippling-function.ts +1061 -0
- package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +924 -0
- package/examples/sdui/vote/rippling-function.ts +330 -0
- package/examples/sdui/vote/vote.ts +298 -0
- package/lib/manifest/app.d.mts +81 -13
- package/lib/manifest/app.d.mts.map +1 -1
- package/lib/manifest/app.d.ts +81 -13
- package/lib/manifest/app.d.ts.map +1 -1
- package/lib/manifest/app.js +37 -16
- package/lib/manifest/app.js.map +1 -1
- package/lib/manifest/app.mjs +37 -16
- package/lib/manifest/app.mjs.map +1 -1
- package/lib/manifest/category.d.mts +63 -7
- package/lib/manifest/category.d.mts.map +1 -1
- package/lib/manifest/category.d.ts +63 -7
- package/lib/manifest/category.d.ts.map +1 -1
- package/lib/manifest/category.js +45 -10
- package/lib/manifest/category.js.map +1 -1
- package/lib/manifest/category.mjs +43 -8
- package/lib/manifest/category.mjs.map +1 -1
- package/lib/manifest/custom-object-field-section.d.mts +56 -6
- package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
- package/lib/manifest/custom-object-field-section.d.ts +56 -6
- package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
- package/lib/manifest/custom-object-field-section.js +41 -14
- package/lib/manifest/custom-object-field-section.js.map +1 -1
- package/lib/manifest/custom-object-field-section.mjs +39 -12
- package/lib/manifest/custom-object-field-section.mjs.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.mts +201 -41
- package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.ts +201 -41
- package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
- package/lib/manifest/custom-object-page-layout.js +76 -30
- package/lib/manifest/custom-object-page-layout.js.map +1 -1
- package/lib/manifest/custom-object-page-layout.mjs +74 -28
- package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
- package/lib/manifest/custom-object.d.mts +128 -15
- package/lib/manifest/custom-object.d.mts.map +1 -1
- package/lib/manifest/custom-object.d.ts +128 -15
- package/lib/manifest/custom-object.d.ts.map +1 -1
- package/lib/manifest/custom-object.js +61 -26
- package/lib/manifest/custom-object.js.map +1 -1
- package/lib/manifest/custom-object.mjs +61 -26
- package/lib/manifest/custom-object.mjs.map +1 -1
- package/lib/manifest/field.d.mts +265 -54
- package/lib/manifest/field.d.mts.map +1 -1
- package/lib/manifest/field.d.ts +265 -54
- package/lib/manifest/field.d.ts.map +1 -1
- package/lib/manifest/field.js +100 -57
- package/lib/manifest/field.js.map +1 -1
- package/lib/manifest/field.mjs +100 -57
- package/lib/manifest/field.mjs.map +1 -1
- package/lib/manifest/list-view.d.mts +105 -14
- package/lib/manifest/list-view.d.mts.map +1 -1
- package/lib/manifest/list-view.d.ts +105 -14
- package/lib/manifest/list-view.d.ts.map +1 -1
- package/lib/manifest/list-view.js +50 -14
- package/lib/manifest/list-view.js.map +1 -1
- package/lib/manifest/list-view.mjs +48 -12
- package/lib/manifest/list-view.mjs.map +1 -1
- package/lib/manifest/manifest-builder.d.mts +77 -11
- package/lib/manifest/manifest-builder.d.mts.map +1 -1
- package/lib/manifest/manifest-builder.d.ts +77 -11
- package/lib/manifest/manifest-builder.d.ts.map +1 -1
- package/lib/manifest/manifest-builder.js +46 -24
- package/lib/manifest/manifest-builder.js.map +1 -1
- package/lib/manifest/manifest-builder.mjs +44 -22
- package/lib/manifest/manifest-builder.mjs.map +1 -1
- package/lib/manifest/manifest-function.d.mts +92 -12
- package/lib/manifest/manifest-function.d.mts.map +1 -1
- package/lib/manifest/manifest-function.d.ts +92 -12
- package/lib/manifest/manifest-function.d.ts.map +1 -1
- package/lib/manifest/manifest-function.js +40 -11
- package/lib/manifest/manifest-function.js.map +1 -1
- package/lib/manifest/manifest-function.mjs +38 -9
- package/lib/manifest/manifest-function.mjs.map +1 -1
- package/lib/manifest/rule.d.mts +94 -16
- package/lib/manifest/rule.d.mts.map +1 -1
- package/lib/manifest/rule.d.ts +94 -16
- package/lib/manifest/rule.d.ts.map +1 -1
- package/lib/manifest/rule.js +36 -15
- package/lib/manifest/rule.js.map +1 -1
- package/lib/manifest/rule.mjs +36 -15
- package/lib/manifest/rule.mjs.map +1 -1
- package/lib/manifest/sdui-page.d.mts +67 -11
- package/lib/manifest/sdui-page.d.mts.map +1 -1
- package/lib/manifest/sdui-page.d.ts +67 -11
- package/lib/manifest/sdui-page.d.ts.map +1 -1
- package/lib/manifest/sdui-page.js +44 -13
- package/lib/manifest/sdui-page.js.map +1 -1
- package/lib/manifest/sdui-page.mjs +42 -11
- package/lib/manifest/sdui-page.mjs.map +1 -1
- package/lib/manifest/validation.d.mts +87 -12
- package/lib/manifest/validation.d.mts.map +1 -1
- package/lib/manifest/validation.d.ts +87 -12
- package/lib/manifest/validation.d.ts.map +1 -1
- package/lib/manifest/validation.js +41 -15
- package/lib/manifest/validation.js.map +1 -1
- package/lib/manifest/validation.mjs +39 -13
- package/lib/manifest/validation.mjs.map +1 -1
- package/lib/sdui/actions.d.mts +25 -0
- package/lib/sdui/actions.d.mts.map +1 -0
- package/lib/sdui/actions.d.ts +25 -0
- package/lib/sdui/actions.d.ts.map +1 -0
- package/lib/sdui/actions.js +53 -0
- package/lib/sdui/actions.js.map +1 -0
- package/lib/sdui/actions.mjs +42 -0
- package/lib/sdui/actions.mjs.map +1 -0
- package/lib/sdui/component.d.mts +41 -0
- package/lib/sdui/component.d.mts.map +1 -0
- package/lib/sdui/component.d.ts +41 -0
- package/lib/sdui/component.d.ts.map +1 -0
- package/lib/sdui/component.js +95 -0
- package/lib/sdui/component.js.map +1 -0
- package/lib/sdui/component.mjs +91 -0
- package/lib/sdui/component.mjs.map +1 -0
- package/lib/sdui/components.d.mts +149 -0
- package/lib/sdui/components.d.mts.map +1 -0
- package/lib/sdui/components.d.ts +149 -0
- package/lib/sdui/components.d.ts.map +1 -0
- package/lib/sdui/components.js +303 -0
- package/lib/sdui/components.js.map +1 -0
- package/lib/sdui/components.mjs +298 -0
- package/lib/sdui/components.mjs.map +1 -0
- package/lib/sdui/conditions.d.mts +25 -0
- package/lib/sdui/conditions.d.mts.map +1 -0
- package/lib/sdui/conditions.d.ts +25 -0
- package/lib/sdui/conditions.d.ts.map +1 -0
- package/lib/sdui/conditions.js +47 -0
- package/lib/sdui/conditions.js.map +1 -0
- package/lib/sdui/conditions.mjs +35 -0
- package/lib/sdui/conditions.mjs.map +1 -0
- package/lib/sdui/directives.d.mts +18 -0
- package/lib/sdui/directives.d.mts.map +1 -0
- package/lib/sdui/directives.d.ts +18 -0
- package/lib/sdui/directives.d.ts.map +1 -0
- package/lib/sdui/directives.js +28 -0
- package/lib/sdui/directives.js.map +1 -0
- package/lib/sdui/directives.mjs +21 -0
- package/lib/sdui/directives.mjs.map +1 -0
- package/lib/sdui/index.d.mts +362 -0
- package/lib/sdui/index.d.mts.map +1 -0
- package/lib/sdui/index.d.ts +362 -0
- package/lib/sdui/index.d.ts.map +1 -0
- package/lib/sdui/index.js +375 -0
- package/lib/sdui/index.js.map +1 -0
- package/lib/sdui/index.mjs +199 -0
- package/lib/sdui/index.mjs.map +1 -0
- package/lib/sdui/props.d.mts +12 -0
- package/lib/sdui/props.d.mts.map +1 -0
- package/lib/sdui/props.d.ts +12 -0
- package/lib/sdui/props.d.ts.map +1 -0
- package/lib/sdui/props.js +25 -0
- package/lib/sdui/props.js.map +1 -0
- package/lib/sdui/props.mjs +21 -0
- package/lib/sdui/props.mjs.map +1 -0
- package/lib/sdui/render.d.mts +23 -0
- package/lib/sdui/render.d.mts.map +1 -0
- package/lib/sdui/render.d.ts +23 -0
- package/lib/sdui/render.d.ts.map +1 -0
- package/lib/sdui/render.js +40 -0
- package/lib/sdui/render.js.map +1 -0
- package/lib/sdui/render.mjs +37 -0
- package/lib/sdui/render.mjs.map +1 -0
- package/lib/sdui/state.d.mts +22 -0
- package/lib/sdui/state.d.mts.map +1 -0
- package/lib/sdui/state.d.ts +22 -0
- package/lib/sdui/state.d.ts.map +1 -0
- package/lib/sdui/state.js +57 -0
- package/lib/sdui/state.js.map +1 -0
- package/lib/sdui/state.mjs +53 -0
- package/lib/sdui/state.mjs.map +1 -0
- package/package.json +11 -1
- package/resources/company-legal-entity-workers.d.mts +79 -0
- package/resources/company-legal-entity-workers.d.mts.map +1 -0
- package/resources/company-legal-entity-workers.d.ts +79 -0
- package/resources/company-legal-entity-workers.d.ts.map +1 -0
- package/resources/company-legal-entity-workers.js +30 -0
- package/resources/company-legal-entity-workers.js.map +1 -0
- package/resources/company-legal-entity-workers.mjs +26 -0
- package/resources/company-legal-entity-workers.mjs.map +1 -0
- package/resources/draft-hires.d.mts +4 -0
- package/resources/draft-hires.d.mts.map +1 -1
- package/resources/draft-hires.d.ts +4 -0
- package/resources/draft-hires.d.ts.map +1 -1
- package/resources/files.d.mts +16 -1
- package/resources/files.d.mts.map +1 -1
- package/resources/files.d.ts +16 -1
- package/resources/files.d.ts.map +1 -1
- package/resources/headcount-positions.d.mts +2 -2
- package/resources/headcount-positions.d.mts.map +1 -1
- package/resources/headcount-positions.d.ts +2 -2
- package/resources/headcount-positions.d.ts.map +1 -1
- package/resources/index.d.mts +1 -0
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +1 -0
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +4 -2
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +1 -0
- package/resources/index.mjs.map +1 -1
- package/resources/legal-entities.d.mts +1 -1
- package/resources/legal-entities.d.mts.map +1 -1
- package/resources/legal-entities.d.ts +1 -1
- package/resources/legal-entities.d.ts.map +1 -1
- package/resources/location-factors.d.mts +2 -2
- package/resources/location-factors.d.mts.map +1 -1
- package/resources/location-factors.d.ts +2 -2
- package/resources/location-factors.d.ts.map +1 -1
- package/resources/shared.d.mts +1 -1
- package/resources/shared.d.mts.map +1 -1
- package/resources/shared.d.ts +1 -1
- package/resources/shared.d.ts.map +1 -1
- package/resources/workers.d.mts +2 -2
- package/resources/workers.d.mts.map +1 -1
- package/resources/workers.d.ts +2 -2
- package/resources/workers.d.ts.map +1 -1
- package/src/client.ts +20 -0
- package/src/lib/manifest/app.ts +81 -26
- package/src/lib/manifest/category.ts +63 -14
- package/src/lib/manifest/custom-object-field-section.ts +56 -17
- package/src/lib/manifest/custom-object-page-layout.ts +201 -66
- package/src/lib/manifest/custom-object.ts +128 -37
- package/src/lib/manifest/field.ts +265 -75
- package/src/lib/manifest/list-view.ts +105 -25
- package/src/lib/manifest/manifest-builder.ts +75 -32
- package/src/lib/manifest/manifest-function.ts +92 -22
- package/src/lib/manifest/rule.ts +95 -29
- package/src/lib/manifest/sdui-page.ts +67 -21
- package/src/lib/manifest/validation.ts +87 -24
- package/src/lib/sdui/actions.ts +58 -0
- package/src/lib/sdui/component.ts +122 -0
- package/src/lib/sdui/components.ts +308 -0
- package/src/lib/sdui/conditions.ts +59 -0
- package/src/lib/sdui/directives.ts +42 -0
- package/src/lib/sdui/index.ts +365 -0
- package/src/lib/sdui/props.ts +32 -0
- package/src/lib/sdui/render.ts +58 -0
- package/src/lib/sdui/state.ts +60 -0
- package/src/resources/company-legal-entity-workers.ts +115 -0
- package/src/resources/draft-hires.ts +5 -0
- package/src/resources/files.ts +18 -1
- package/src/resources/headcount-positions.ts +2 -0
- package/src/resources/index.ts +7 -0
- package/src/resources/legal-entities.ts +1 -0
- package/src/resources/location-factors.ts +2 -0
- package/src/resources/shared.ts +1 -0
- package/src/resources/workers.ts +2 -0
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/examples/.keep
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Dental Practice Management — patient charts, operatories (chairs), appointments
|
|
3
|
+
* with status, treatment plans, rollups, and an SDUI ObjectListView tab for appointments.
|
|
4
|
+
*
|
|
5
|
+
* dental_patient__c ← chart: demographics, allergies, notes
|
|
6
|
+
* dental_operatory__c ← chair / room (parent for same-day board rollups)
|
|
7
|
+
* dental_appointment__c ← child of operatory; patient lookup, time, status, visit type
|
|
8
|
+
* dental_treatment_plan__c ← child of patient; plan status, procedures, estimates
|
|
9
|
+
*
|
|
10
|
+
* Run:
|
|
11
|
+
* yarn tsn examples/manifest/dental-practice-management/dental-practice-management.ts \
|
|
12
|
+
* > examples/manifest/dental-practice-management/manifest.json
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
App,
|
|
17
|
+
Category,
|
|
18
|
+
CustomObject,
|
|
19
|
+
CustomObjectFieldSection,
|
|
20
|
+
CustomObjectPageLayout,
|
|
21
|
+
DatetimeField,
|
|
22
|
+
DateField,
|
|
23
|
+
EmailField,
|
|
24
|
+
FormulaField,
|
|
25
|
+
ListViewDef,
|
|
26
|
+
LongTextField,
|
|
27
|
+
LookupField,
|
|
28
|
+
ManifestBuilder,
|
|
29
|
+
ManifestFunction,
|
|
30
|
+
NumberField,
|
|
31
|
+
ParentChildField,
|
|
32
|
+
PhoneField,
|
|
33
|
+
Rule,
|
|
34
|
+
SelectField,
|
|
35
|
+
SduiPage,
|
|
36
|
+
SummaryField,
|
|
37
|
+
TextField,
|
|
38
|
+
Validation,
|
|
39
|
+
} from '@rippling/rippling-sdk/lib/manifest';
|
|
40
|
+
const manifest = new ManifestBuilder({
|
|
41
|
+
key: 'dental_practice_management',
|
|
42
|
+
name: 'Dental Practice Management',
|
|
43
|
+
description:
|
|
44
|
+
'Patient charts, chair schedule, appointments with status, and treatment plans for a dental office',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const dentalCategory = new Category(manifest, {
|
|
48
|
+
apiName: 'dental__c',
|
|
49
|
+
name: 'Dental',
|
|
50
|
+
description: 'Patients, chairs, visits, and treatment plans',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ── Patient (chart) ─────────────────────────────────────────────────────────
|
|
54
|
+
const patientObj = new CustomObject(manifest, {
|
|
55
|
+
apiName: 'dental_patient__c',
|
|
56
|
+
name: 'Patient',
|
|
57
|
+
pluralLabel: 'Patients',
|
|
58
|
+
category: dentalCategory,
|
|
59
|
+
description: 'Clinical chart, demographics, and care preferences',
|
|
60
|
+
icon: { emoji: '🦷' },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const patientIdentitySection = new CustomObjectFieldSection(patientObj, {
|
|
64
|
+
name: 'Demographics',
|
|
65
|
+
sectionId: 'sec_pt_identity',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const patientClinicalSection = new CustomObjectFieldSection(patientObj, {
|
|
69
|
+
name: 'Clinical',
|
|
70
|
+
sectionId: 'sec_pt_clinical',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const patientNameField = new TextField(patientObj, {
|
|
74
|
+
apiName: 'legal_name__c',
|
|
75
|
+
displayName: 'Full legal name',
|
|
76
|
+
required: true,
|
|
77
|
+
maxLength: 160,
|
|
78
|
+
section: patientIdentitySection,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const patientDobField = new DateField(patientObj, {
|
|
82
|
+
apiName: 'date_of_birth__c',
|
|
83
|
+
displayName: 'Date of birth',
|
|
84
|
+
section: patientIdentitySection,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const patientPhoneField = new PhoneField(patientObj, {
|
|
88
|
+
apiName: 'phone__c',
|
|
89
|
+
displayName: 'Phone',
|
|
90
|
+
section: patientIdentitySection,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const patientEmailField = new EmailField(patientObj, {
|
|
94
|
+
apiName: 'email__c',
|
|
95
|
+
displayName: 'Email',
|
|
96
|
+
unique: true,
|
|
97
|
+
section: patientIdentitySection,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const patientAllergiesField = new LongTextField(patientObj, {
|
|
101
|
+
apiName: 'allergies_and_alerts__c',
|
|
102
|
+
displayName: 'Allergies & medical alerts',
|
|
103
|
+
maxLength: 2000,
|
|
104
|
+
description: 'Drug allergies, bisphosphonates, anticoagulants, latex, etc.',
|
|
105
|
+
section: patientClinicalSection,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const patientInsuranceField = new TextField(patientObj, {
|
|
109
|
+
apiName: 'insurance_plan__c',
|
|
110
|
+
displayName: 'Insurance / payer',
|
|
111
|
+
maxLength: 120,
|
|
112
|
+
section: patientClinicalSection,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const patientChartNotesField = new LongTextField(patientObj, {
|
|
116
|
+
apiName: 'chart_summary__c',
|
|
117
|
+
displayName: 'Chart summary & preferences',
|
|
118
|
+
maxLength: 4000,
|
|
119
|
+
description: 'Narrative, anxiety notes, perio class, last prophy, family history',
|
|
120
|
+
section: patientClinicalSection,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const patientLabelField = new FormulaField(patientObj, {
|
|
124
|
+
apiName: 'patient_label__c',
|
|
125
|
+
displayName: 'Chart label',
|
|
126
|
+
fieldType: 'TEXT',
|
|
127
|
+
maxLength: 220,
|
|
128
|
+
formula: "CONCATENATE(legal_name__c, ' (b. ', YEAR(date_of_birth__c), ')')",
|
|
129
|
+
description: 'Quick list label (name and birth year)',
|
|
130
|
+
section: patientIdentitySection,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── Operatory (chair) ─────────────────────────────────────────────────────────
|
|
134
|
+
const operatoryObj = new CustomObject(manifest, {
|
|
135
|
+
apiName: 'dental_operatory__c',
|
|
136
|
+
name: 'Operatory',
|
|
137
|
+
pluralLabel: 'Operatories',
|
|
138
|
+
category: dentalCategory,
|
|
139
|
+
description: 'A chair, bay, or room you book on the daily board',
|
|
140
|
+
icon: { emoji: '🪥' },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const operatorySection = new CustomObjectFieldSection(operatoryObj, {
|
|
144
|
+
name: 'Operatory',
|
|
145
|
+
sectionId: 'sec_op_info',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const opNameField = new TextField(operatoryObj, {
|
|
149
|
+
apiName: 'operatory_name__c',
|
|
150
|
+
displayName: 'Name / label',
|
|
151
|
+
required: true,
|
|
152
|
+
maxLength: 80,
|
|
153
|
+
description: 'E.g. "Hyg 1", "DDS-2", "Surg A"',
|
|
154
|
+
section: operatorySection,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const opCodeField = new TextField(operatoryObj, {
|
|
158
|
+
apiName: 'room_code__c',
|
|
159
|
+
displayName: 'Room code',
|
|
160
|
+
maxLength: 20,
|
|
161
|
+
description: 'Short code for whiteboards and schedules',
|
|
162
|
+
section: operatorySection,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ── Appointment (child of operatory) ─────────────────────────────────────────
|
|
166
|
+
const apptObj = new CustomObject(manifest, {
|
|
167
|
+
apiName: 'dental_appointment__c',
|
|
168
|
+
name: 'Appointment',
|
|
169
|
+
pluralLabel: 'Appointments',
|
|
170
|
+
category: dentalCategory,
|
|
171
|
+
description: 'Chair booking with visit type and real-time visit status',
|
|
172
|
+
icon: { emoji: '📅' },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const apptOperatorySection = new CustomObjectFieldSection(apptObj, {
|
|
176
|
+
name: 'Operatory & time',
|
|
177
|
+
sectionId: 'sec_appt_op',
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const apptPatientSection = new CustomObjectFieldSection(apptObj, {
|
|
181
|
+
name: 'Patient & visit',
|
|
182
|
+
sectionId: 'sec_appt_patient',
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const apptOperatoryRef = new ParentChildField(apptObj, {
|
|
186
|
+
apiName: 'operatory_ref__c',
|
|
187
|
+
displayName: 'Operatory (chair)',
|
|
188
|
+
target: operatoryObj,
|
|
189
|
+
relatedDataLabel: 'Appointments',
|
|
190
|
+
parentChildType: 'PRIMARY',
|
|
191
|
+
enableParentOwnerInheritance: false,
|
|
192
|
+
description: 'Which chair or bay this visit is booked on',
|
|
193
|
+
section: apptOperatorySection,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const apptStartField = new DatetimeField(apptObj, {
|
|
197
|
+
apiName: 'start_time__c',
|
|
198
|
+
displayName: 'Start (local)',
|
|
199
|
+
required: true,
|
|
200
|
+
description: 'Planned or actual start; board sorts by this',
|
|
201
|
+
section: apptOperatorySection,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const apptDurationMins = new NumberField(apptObj, {
|
|
205
|
+
apiName: 'duration_minutes__c',
|
|
206
|
+
displayName: 'Duration (minutes)',
|
|
207
|
+
required: true,
|
|
208
|
+
decimalPlaces: 0,
|
|
209
|
+
description: 'Expected chair time for the procedure block',
|
|
210
|
+
section: apptOperatorySection,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const apptPatientRef = new LookupField(apptObj, {
|
|
214
|
+
apiName: 'patient_ref__c',
|
|
215
|
+
displayName: 'Patient',
|
|
216
|
+
target: patientObj,
|
|
217
|
+
relatedDataLabel: 'Appointments',
|
|
218
|
+
required: true,
|
|
219
|
+
description: 'Chart to open for this visit',
|
|
220
|
+
section: apptPatientSection,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const apptVisitTypeField = new SelectField(apptObj, {
|
|
224
|
+
apiName: 'visit_type__c',
|
|
225
|
+
displayName: 'Visit type',
|
|
226
|
+
options: [
|
|
227
|
+
'New patient exam',
|
|
228
|
+
'Periodic / recall exam',
|
|
229
|
+
'Prophy / cleaning',
|
|
230
|
+
'Restorative',
|
|
231
|
+
'Endodontic',
|
|
232
|
+
'Perio / SRP',
|
|
233
|
+
'Oral surgery / extraction',
|
|
234
|
+
'Orthodontic',
|
|
235
|
+
'Emergency / pain',
|
|
236
|
+
'Other',
|
|
237
|
+
],
|
|
238
|
+
required: true,
|
|
239
|
+
section: apptPatientSection,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const apptStatusField = new SelectField(apptObj, {
|
|
243
|
+
apiName: 'appointment_status__c',
|
|
244
|
+
displayName: 'Visit status',
|
|
245
|
+
options: ['Scheduled', 'Checked in', 'In treatment', 'Completed', 'Cancelled', 'No-show'],
|
|
246
|
+
required: true,
|
|
247
|
+
description: 'Front-desk and clinical workflow status',
|
|
248
|
+
section: apptPatientSection,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const apptClinicalNotes = new LongTextField(apptObj, {
|
|
252
|
+
apiName: 'visit_notes__c',
|
|
253
|
+
displayName: 'Visit notes',
|
|
254
|
+
maxLength: 4000,
|
|
255
|
+
description: 'Chairside notes for this block (sync with EDR as needed)',
|
|
256
|
+
section: apptPatientSection,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const apptBoardLabel = new FormulaField(apptObj, {
|
|
260
|
+
apiName: 'board_label__c',
|
|
261
|
+
displayName: 'Board label',
|
|
262
|
+
fieldType: 'TEXT',
|
|
263
|
+
maxLength: 260,
|
|
264
|
+
formula: "CONCATENATE(visit_type__c, ' · ', appointment_status__c)",
|
|
265
|
+
section: apptPatientSection,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ── Treatment plan (child of patient) ─────────────────────────────────────────
|
|
269
|
+
const planObj = new CustomObject(manifest, {
|
|
270
|
+
apiName: 'dental_treatment_plan__c',
|
|
271
|
+
name: 'Treatment plan',
|
|
272
|
+
pluralLabel: 'Treatment plans',
|
|
273
|
+
category: dentalCategory,
|
|
274
|
+
description: 'Staged work sequenced for a patient',
|
|
275
|
+
icon: { emoji: '📋' },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const planLinkSection = new CustomObjectFieldSection(planObj, {
|
|
279
|
+
name: 'Link',
|
|
280
|
+
sectionId: 'sec_plan_link',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const planBodySection = new CustomObjectFieldSection(planObj, {
|
|
284
|
+
name: 'Plan',
|
|
285
|
+
sectionId: 'sec_plan_body',
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const planPatientRef = new ParentChildField(planObj, {
|
|
289
|
+
apiName: 'plan_patient_ref__c',
|
|
290
|
+
displayName: 'Patient',
|
|
291
|
+
target: patientObj,
|
|
292
|
+
relatedDataLabel: 'Treatment plans',
|
|
293
|
+
parentChildType: 'PRIMARY',
|
|
294
|
+
enableParentOwnerInheritance: false,
|
|
295
|
+
description: 'Which chart owns this plan',
|
|
296
|
+
section: planLinkSection,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const planNameField = new TextField(planObj, {
|
|
300
|
+
apiName: 'plan_name__c',
|
|
301
|
+
displayName: 'Plan name',
|
|
302
|
+
required: true,
|
|
303
|
+
maxLength: 200,
|
|
304
|
+
description: 'E.g. "Full mouth rehab - phase 1", "Crown 14 DO"',
|
|
305
|
+
section: planBodySection,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const planStatusField = new SelectField(planObj, {
|
|
309
|
+
apiName: 'plan_status__c',
|
|
310
|
+
displayName: 'Status',
|
|
311
|
+
options: ['Draft', 'Presented', 'Accepted', 'In progress', 'Completed', 'Declined / on hold'],
|
|
312
|
+
required: true,
|
|
313
|
+
section: planBodySection,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const planTargetField = new DateField(planObj, {
|
|
317
|
+
apiName: 'target_completion__c',
|
|
318
|
+
displayName: 'Target completion',
|
|
319
|
+
description: 'Goal to finish or next review',
|
|
320
|
+
section: planBodySection,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const planProceduresField = new LongTextField(planObj, {
|
|
324
|
+
apiName: 'procedures_summary__c',
|
|
325
|
+
displayName: 'Procedures & sequence',
|
|
326
|
+
maxLength: 8000,
|
|
327
|
+
description: 'Tooth numbers, codes, and staging',
|
|
328
|
+
section: planBodySection,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const planFeeCents = new NumberField(planObj, {
|
|
332
|
+
apiName: 'fee_estimate_cents__c',
|
|
333
|
+
displayName: 'Fee estimate (cents)',
|
|
334
|
+
decimalPlaces: 0,
|
|
335
|
+
description: 'Rough office fee total for planning; adjust per office policy',
|
|
336
|
+
section: planBodySection,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ── Rollups ────────────────────────────────────────────────────────────────
|
|
340
|
+
const apptCountOnOperatory = new SummaryField(operatoryObj, {
|
|
341
|
+
apiName: 'appointment_count__c',
|
|
342
|
+
displayName: 'Appointments (count)',
|
|
343
|
+
aggregates: apptPatientRef,
|
|
344
|
+
using: 'COUNT',
|
|
345
|
+
lookup: apptOperatoryRef,
|
|
346
|
+
description: 'Rows in this operatory (filter by day in the chair board function)',
|
|
347
|
+
section: operatorySection,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const planCountOnPatient = new SummaryField(patientObj, {
|
|
351
|
+
apiName: 'treatment_plan_count__c',
|
|
352
|
+
displayName: 'Treatment plans',
|
|
353
|
+
aggregates: planNameField,
|
|
354
|
+
using: 'COUNT',
|
|
355
|
+
lookup: planPatientRef,
|
|
356
|
+
description: 'Active plans tied to the chart',
|
|
357
|
+
section: patientClinicalSection,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// ── Validations & defaults ──────────────────────────────────────────────────
|
|
361
|
+
new Validation(apptObj, {
|
|
362
|
+
ruleId: 'dental_appt_duration_positive',
|
|
363
|
+
displayName: 'Duration must be positive',
|
|
364
|
+
rqlFormula: '(duration_minutes__c <= 0)',
|
|
365
|
+
errorMessage: 'Duration must be at least 1 minute.',
|
|
366
|
+
ruleDescription: 'Blocks empty or zero chair blocks',
|
|
367
|
+
state: 'ACTIVE',
|
|
368
|
+
level: 'FIELD',
|
|
369
|
+
fieldRef: apptDurationMins,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
new Rule(apptObj, {
|
|
373
|
+
flowId: 'dental_default_appt_status',
|
|
374
|
+
flowName: 'Default visit status to Scheduled',
|
|
375
|
+
triggerType: 'CREATE',
|
|
376
|
+
description: 'New bookings start as Scheduled unless the team changes it',
|
|
377
|
+
actions: [{ targetField: apptStatusField, fixedValue: ['Scheduled'] }],
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// ── List views ─────────────────────────────────────────────────────────────
|
|
381
|
+
new ListViewDef(patientObj, {
|
|
382
|
+
viewId: 'view_dental_all_patients',
|
|
383
|
+
name: 'All patients',
|
|
384
|
+
description: 'Full chart list with plan counts',
|
|
385
|
+
fields: [patientNameField, patientDobField, patientPhoneField, patientLabelField, planCountOnPatient],
|
|
386
|
+
orderBy: patientNameField,
|
|
387
|
+
sortOrder: 'ASC',
|
|
388
|
+
isPublic: true,
|
|
389
|
+
objectScope: 'all',
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
new ListViewDef(operatoryObj, {
|
|
393
|
+
viewId: 'view_dental_all_operatories',
|
|
394
|
+
name: 'All operatories',
|
|
395
|
+
description: 'Chairs and daily volume rollups',
|
|
396
|
+
fields: [opNameField, opCodeField, apptCountOnOperatory],
|
|
397
|
+
orderBy: opNameField,
|
|
398
|
+
sortOrder: 'ASC',
|
|
399
|
+
isPublic: true,
|
|
400
|
+
objectScope: 'all',
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
new ListViewDef(apptObj, {
|
|
404
|
+
viewId: 'view_dental_appointments_all',
|
|
405
|
+
name: 'All appointments',
|
|
406
|
+
description: 'Chronological board with chair and status',
|
|
407
|
+
fields: [
|
|
408
|
+
apptOperatoryRef,
|
|
409
|
+
apptStartField,
|
|
410
|
+
apptDurationMins,
|
|
411
|
+
apptPatientRef,
|
|
412
|
+
apptVisitTypeField,
|
|
413
|
+
apptStatusField,
|
|
414
|
+
apptBoardLabel,
|
|
415
|
+
],
|
|
416
|
+
orderBy: apptStartField,
|
|
417
|
+
sortOrder: 'ASC',
|
|
418
|
+
isPublic: true,
|
|
419
|
+
objectScope: 'all',
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
new ListViewDef(planObj, {
|
|
423
|
+
viewId: 'view_dental_treatment_plans',
|
|
424
|
+
name: 'All treatment plans',
|
|
425
|
+
description: 'Case tracking by status',
|
|
426
|
+
fields: [planPatientRef, planNameField, planStatusField, planTargetField, planFeeCents],
|
|
427
|
+
orderBy: planTargetField,
|
|
428
|
+
sortOrder: 'ASC',
|
|
429
|
+
isPublic: true,
|
|
430
|
+
objectScope: 'all',
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// ── Page layouts ─────────────────────────────────────────────────────────────
|
|
434
|
+
new CustomObjectPageLayout(patientObj, {
|
|
435
|
+
apiName: 'default',
|
|
436
|
+
name: 'Patient chart',
|
|
437
|
+
layoutId: 'layout_dental_patient',
|
|
438
|
+
headerEdits: { newTitleField: patientNameField.getApiName() },
|
|
439
|
+
tabEdits: {
|
|
440
|
+
newTabs: [
|
|
441
|
+
{
|
|
442
|
+
key: 'chart',
|
|
443
|
+
name: 'Chart',
|
|
444
|
+
type: 'tab_with_sections',
|
|
445
|
+
sections: [
|
|
446
|
+
{
|
|
447
|
+
key: 'id',
|
|
448
|
+
name: 'Profile',
|
|
449
|
+
type: 'fields_section',
|
|
450
|
+
fields: [
|
|
451
|
+
{ fieldRqlName: patientNameField.getApiName() },
|
|
452
|
+
{ fieldRqlName: patientDobField.getApiName() },
|
|
453
|
+
{ fieldRqlName: patientPhoneField.getApiName() },
|
|
454
|
+
{ fieldRqlName: patientEmailField.getApiName() },
|
|
455
|
+
{ fieldRqlName: patientLabelField.getApiName() },
|
|
456
|
+
],
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
key: 'clinical',
|
|
460
|
+
name: 'Clinical & billing',
|
|
461
|
+
type: 'fields_section',
|
|
462
|
+
fields: [
|
|
463
|
+
{ fieldRqlName: patientAllergiesField.getApiName() },
|
|
464
|
+
{ fieldRqlName: patientInsuranceField.getApiName() },
|
|
465
|
+
{ fieldRqlName: patientChartNotesField.getApiName() },
|
|
466
|
+
{ fieldRqlName: planCountOnPatient.getApiName() },
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
systemTabEdits: {},
|
|
473
|
+
tabsOrder: ['chart'],
|
|
474
|
+
},
|
|
475
|
+
visibilityConditions: {},
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
new CustomObjectPageLayout(operatoryObj, {
|
|
479
|
+
apiName: 'default',
|
|
480
|
+
name: 'Operatory',
|
|
481
|
+
layoutId: 'layout_dental_operatory',
|
|
482
|
+
headerEdits: { newTitleField: opNameField.getApiName() },
|
|
483
|
+
tabEdits: {
|
|
484
|
+
newTabs: [
|
|
485
|
+
{
|
|
486
|
+
key: 'overview',
|
|
487
|
+
name: 'Overview',
|
|
488
|
+
type: 'tab_with_sections',
|
|
489
|
+
sections: [
|
|
490
|
+
{
|
|
491
|
+
key: 'room',
|
|
492
|
+
name: 'Room',
|
|
493
|
+
type: 'fields_section',
|
|
494
|
+
fields: [
|
|
495
|
+
{ fieldRqlName: opNameField.getApiName() },
|
|
496
|
+
{ fieldRqlName: opCodeField.getApiName() },
|
|
497
|
+
{ fieldRqlName: apptCountOnOperatory.getApiName() },
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
systemTabEdits: {},
|
|
504
|
+
tabsOrder: ['overview'],
|
|
505
|
+
},
|
|
506
|
+
visibilityConditions: {},
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
new CustomObjectPageLayout(apptObj, {
|
|
510
|
+
apiName: 'default',
|
|
511
|
+
name: 'Appointment',
|
|
512
|
+
layoutId: 'layout_dental_appointment',
|
|
513
|
+
headerEdits: { newTitleField: apptBoardLabel.getApiName() },
|
|
514
|
+
tabEdits: {
|
|
515
|
+
newTabs: [
|
|
516
|
+
{
|
|
517
|
+
key: 'visit',
|
|
518
|
+
name: 'Visit',
|
|
519
|
+
type: 'tab_with_sections',
|
|
520
|
+
sections: [
|
|
521
|
+
{
|
|
522
|
+
key: 'chair',
|
|
523
|
+
name: 'Chair & schedule',
|
|
524
|
+
type: 'fields_section',
|
|
525
|
+
fields: [
|
|
526
|
+
{ fieldRqlName: apptOperatoryRef.getApiName() },
|
|
527
|
+
{ fieldRqlName: apptStartField.getApiName() },
|
|
528
|
+
{ fieldRqlName: apptDurationMins.getApiName() },
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
key: 'patient',
|
|
533
|
+
name: 'Patient & status',
|
|
534
|
+
type: 'fields_section',
|
|
535
|
+
fields: [
|
|
536
|
+
{ fieldRqlName: apptPatientRef.getApiName() },
|
|
537
|
+
{ fieldRqlName: apptVisitTypeField.getApiName() },
|
|
538
|
+
{ fieldRqlName: apptStatusField.getApiName() },
|
|
539
|
+
{ fieldRqlName: apptBoardLabel.getApiName() },
|
|
540
|
+
{ fieldRqlName: apptClinicalNotes.getApiName() },
|
|
541
|
+
],
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
systemTabEdits: {},
|
|
547
|
+
tabsOrder: ['visit'],
|
|
548
|
+
},
|
|
549
|
+
visibilityConditions: {},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
new CustomObjectPageLayout(planObj, {
|
|
553
|
+
apiName: 'default',
|
|
554
|
+
name: 'Treatment plan',
|
|
555
|
+
layoutId: 'layout_dental_treatment_plan',
|
|
556
|
+
headerEdits: { newTitleField: planNameField.getApiName() },
|
|
557
|
+
tabEdits: {
|
|
558
|
+
newTabs: [
|
|
559
|
+
{
|
|
560
|
+
key: 'plan',
|
|
561
|
+
name: 'Plan',
|
|
562
|
+
type: 'tab_with_sections',
|
|
563
|
+
sections: [
|
|
564
|
+
{
|
|
565
|
+
key: 'link',
|
|
566
|
+
name: 'Patient',
|
|
567
|
+
type: 'fields_section',
|
|
568
|
+
fields: [{ fieldRqlName: planPatientRef.getApiName() }],
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
key: 'content',
|
|
572
|
+
name: 'Planned work',
|
|
573
|
+
type: 'fields_section',
|
|
574
|
+
fields: [
|
|
575
|
+
{ fieldRqlName: planNameField.getApiName() },
|
|
576
|
+
{ fieldRqlName: planStatusField.getApiName() },
|
|
577
|
+
{ fieldRqlName: planTargetField.getApiName() },
|
|
578
|
+
{ fieldRqlName: planProceduresField.getApiName() },
|
|
579
|
+
{ fieldRqlName: planFeeCents.getApiName() },
|
|
580
|
+
],
|
|
581
|
+
},
|
|
582
|
+
],
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
systemTabEdits: {},
|
|
586
|
+
tabsOrder: ['plan'],
|
|
587
|
+
},
|
|
588
|
+
visibilityConditions: {},
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// ── SDUI: chair schedule tab — ObjectListView on appointments (same style as other examples) ─
|
|
592
|
+
const chairBoardCode = [
|
|
593
|
+
"import { FunctionContext, FunctionEvent, FunctionResponse } from '@rippling/rippling-sdk';",
|
|
594
|
+
"import SDUI from '@rippling/sdui-builder';",
|
|
595
|
+
'',
|
|
596
|
+
`const OBJECT = '${apptObj.getApiName()}';`,
|
|
597
|
+
'',
|
|
598
|
+
'export async function onRipplingEvent(',
|
|
599
|
+
' event: FunctionEvent,',
|
|
600
|
+
' context: FunctionContext,',
|
|
601
|
+
'): Promise<FunctionResponse> {',
|
|
602
|
+
' const action = event.parameters?.action;',
|
|
603
|
+
'',
|
|
604
|
+
" if (!action || action === 'init') {",
|
|
605
|
+
' const s = SDUI.createState({});',
|
|
606
|
+
' const root = SDUI.components.VStack({',
|
|
607
|
+
' children: [',
|
|
608
|
+
' SDUI.components.ObjectListView({',
|
|
609
|
+
' objectApiName: OBJECT,',
|
|
610
|
+
' canSearchInGrid: true,',
|
|
611
|
+
' }),',
|
|
612
|
+
' ],',
|
|
613
|
+
' });',
|
|
614
|
+
' return new FunctionResponse({',
|
|
615
|
+
' body: { spec: JSON.stringify(SDUI.render(s, root)) },',
|
|
616
|
+
' statusCode: 200,',
|
|
617
|
+
' headers: {},',
|
|
618
|
+
" message: '',",
|
|
619
|
+
' });',
|
|
620
|
+
' }',
|
|
621
|
+
'',
|
|
622
|
+
" return new FunctionResponse({ body: {}, statusCode: 200, headers: {}, message: '' });",
|
|
623
|
+
'}',
|
|
624
|
+
].join('\n');
|
|
625
|
+
|
|
626
|
+
const chairBoardFn = new ManifestFunction(manifest, {
|
|
627
|
+
apiName: 'dental_chair_schedule_fn',
|
|
628
|
+
name: 'Daily chair board (SDUI)',
|
|
629
|
+
description: 'SDUI ObjectListView for appointments (init + spec render)',
|
|
630
|
+
runtime: 'lambda-deno',
|
|
631
|
+
code: chairBoardCode,
|
|
632
|
+
dependencies: { '@rippling/rippling-sdk': '^0.2.0-alpha.33', '@rippling/sdui-builder': '^0.1.0' },
|
|
633
|
+
isAsync: false,
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
const chairBoardPage = new SduiPage(manifest, {
|
|
637
|
+
sduiPageId: 'dental_chair_schedule',
|
|
638
|
+
name: 'Chair schedule',
|
|
639
|
+
function: chairBoardFn,
|
|
640
|
+
description: 'SDUI list of appointments (searchable grid); use object records for status edits',
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
new App(manifest, {
|
|
644
|
+
apiName: 'dental_practice_management_app',
|
|
645
|
+
name: 'Dental Practice Management',
|
|
646
|
+
description: 'Patient charts, operatories, treatment plans, and an SDUI appointment list tab',
|
|
647
|
+
appType: 'custom',
|
|
648
|
+
pages: [patientObj, operatoryObj, apptObj, planObj, chairBoardPage],
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
console.log(manifest.preview());
|