@petrarca/sonnet-playground 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/{PlayAdvancedComponents-ZYK66UII.js → PlayAdvancedComponents-VMUI472R.js} +2 -2
- package/dist/PlayCodeEditor-AMTFE5SW.js +8 -0
- package/dist/PlayCodedValues-G2EVIVMO.js +9 -0
- package/dist/{PlayCoreComponents-YTEAS3GL.js → PlayCoreComponents-EQ2BMY2S.js} +2 -2
- package/dist/{PlayDataComponents-W44OITT5.js → PlayDataComponents-W2KQHOI7.js} +2 -2
- package/dist/PlayFhirQuestionnaire-TO35Q7CP.js +8 -0
- package/dist/{PlayFormWidgets-6JYFRTAL.js → PlayFormWidgets-HP3VF2LK.js} +2 -2
- package/dist/PlayForms-4CQNEBLS.js +7 -0
- package/dist/PlayGraph-25ZKQAQR.js +7 -0
- package/dist/PlayGraph-25ZKQAQR.js.map +1 -0
- package/dist/PlayMarkdownViewer-X6AIFO2U.js +8 -0
- package/dist/PlayMarkdownViewer-X6AIFO2U.js.map +1 -0
- package/dist/PlayQuestionnaireDesigner-IR7TKSOT.js +9 -0
- package/dist/PlayQuestionnaireDesigner-IR7TKSOT.js.map +1 -0
- package/dist/PlaySchemaDesigner-ZQBA7AYO.js +8 -0
- package/dist/PlaySchemaDesigner-ZQBA7AYO.js.map +1 -0
- package/dist/{PlayShellApi-5O6VYR2X.js → PlayShellApi-MMCQ2AKK.js} +2 -2
- package/dist/PlayShellApi-MMCQ2AKK.js.map +1 -0
- package/dist/chunk-3FYM6B3E.js +1163 -0
- package/dist/chunk-3FYM6B3E.js.map +1 -0
- package/dist/{chunk-CQ6PGFRV.js → chunk-3RZHYZRL.js} +57 -1
- package/dist/chunk-3RZHYZRL.js.map +1 -0
- package/dist/chunk-7IMCPUSI.js +100 -0
- package/dist/chunk-7IMCPUSI.js.map +1 -0
- package/dist/{chunk-2XMWOZ5O.js → chunk-EPCUUTBZ.js} +48 -41
- package/dist/chunk-EPCUUTBZ.js.map +1 -0
- package/dist/{chunk-I2FLG6ZD.js → chunk-LDCNLHO2.js} +195 -18
- package/dist/chunk-LDCNLHO2.js.map +1 -0
- package/dist/chunk-LVV32F7G.js +104 -0
- package/dist/chunk-LVV32F7G.js.map +1 -0
- package/dist/chunk-OUL3DN2E.js +510 -0
- package/dist/chunk-OUL3DN2E.js.map +1 -0
- package/dist/chunk-QKFQI4VX.js +83 -0
- package/dist/chunk-QKFQI4VX.js.map +1 -0
- package/dist/{chunk-6WI3N7JI.js → chunk-RGBSHXS5.js} +145 -4
- package/dist/chunk-RGBSHXS5.js.map +1 -0
- package/dist/{chunk-UB7462QG.js → chunk-RLJI5VMP.js} +72 -1
- package/dist/chunk-RLJI5VMP.js.map +1 -0
- package/dist/chunk-VGAS5Z6X.js +543 -0
- package/dist/chunk-VGAS5Z6X.js.map +1 -0
- package/dist/chunk-VKZ2RJFL.js +647 -0
- package/dist/chunk-VKZ2RJFL.js.map +1 -0
- package/dist/chunk-WR74TMW4.js +219 -0
- package/dist/chunk-WR74TMW4.js.map +1 -0
- package/dist/{chunk-E5X7GVLU.js → chunk-XNDD4UN4.js} +2 -2
- package/dist/chunk-XNDD4UN4.js.map +1 -0
- package/dist/chunk-YTGQRDD2.js +343 -0
- package/dist/chunk-YTGQRDD2.js.map +1 -0
- package/dist/index.d.ts +13 -10
- package/dist/index.js +72 -11
- package/dist/index.js.map +1 -1
- package/dist/pages/index.d.ts +56 -20
- package/dist/pages/index.js +39 -17
- package/package.json +15 -14
- package/dist/PlayForms-L532W3JL.js +0 -7
- package/dist/PlayGraph-M4KQSFI7.js +0 -7
- package/dist/PlaySchemaDesigner-S4DBB53G.js +0 -7
- package/dist/chunk-2XMWOZ5O.js.map +0 -1
- package/dist/chunk-4Y4JFKAK.js +0 -240
- package/dist/chunk-4Y4JFKAK.js.map +0 -1
- package/dist/chunk-6WI3N7JI.js.map +0 -1
- package/dist/chunk-CQ6PGFRV.js.map +0 -1
- package/dist/chunk-E5X7GVLU.js.map +0 -1
- package/dist/chunk-I2FLG6ZD.js.map +0 -1
- package/dist/chunk-O2QMWJZI.js +0 -881
- package/dist/chunk-O2QMWJZI.js.map +0 -1
- package/dist/chunk-UB7462QG.js.map +0 -1
- /package/dist/{PlayAdvancedComponents-ZYK66UII.js.map → PlayAdvancedComponents-VMUI472R.js.map} +0 -0
- /package/dist/{PlayCoreComponents-YTEAS3GL.js.map → PlayCodeEditor-AMTFE5SW.js.map} +0 -0
- /package/dist/{PlayDataComponents-W44OITT5.js.map → PlayCodedValues-G2EVIVMO.js.map} +0 -0
- /package/dist/{PlayFormWidgets-6JYFRTAL.js.map → PlayCoreComponents-EQ2BMY2S.js.map} +0 -0
- /package/dist/{PlayForms-L532W3JL.js.map → PlayDataComponents-W2KQHOI7.js.map} +0 -0
- /package/dist/{PlayGraph-M4KQSFI7.js.map → PlayFhirQuestionnaire-TO35Q7CP.js.map} +0 -0
- /package/dist/{PlaySchemaDesigner-S4DBB53G.js.map → PlayFormWidgets-HP3VF2LK.js.map} +0 -0
- /package/dist/{PlayShellApi-5O6VYR2X.js.map → PlayForms-4CQNEBLS.js.map} +0 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PLAYGROUND_TERMINOLOGY
|
|
3
|
+
} from "./chunk-WR74TMW4.js";
|
|
4
|
+
|
|
5
|
+
// src/pages/PlayFhirQuestionnaire.tsx
|
|
6
|
+
import { useMemo, useState } from "react";
|
|
7
|
+
import {
|
|
8
|
+
fromFhirQuestionnaire,
|
|
9
|
+
fromFhirQuestionnaireResponse,
|
|
10
|
+
toFhirQuestionnaire,
|
|
11
|
+
toFhirQuestionnaireResponse
|
|
12
|
+
} from "@petrarca/sonnet-fhir/questionnaire";
|
|
13
|
+
import { JsonSchemaFormRenderer } from "@petrarca/sonnet-forms";
|
|
14
|
+
import {
|
|
15
|
+
Badge,
|
|
16
|
+
Button,
|
|
17
|
+
Card,
|
|
18
|
+
IconButton,
|
|
19
|
+
SimpleTooltip,
|
|
20
|
+
ViewSwitch
|
|
21
|
+
} from "@petrarca/sonnet-ui";
|
|
22
|
+
import { CodeEditor } from "@petrarca/sonnet-ui/code-editor";
|
|
23
|
+
import { ArrowRight, Check, Info, TriangleAlert } from "lucide-react";
|
|
24
|
+
|
|
25
|
+
// src/pages/fhir/samples.ts
|
|
26
|
+
var SYSTEM = "https://example.org/fhir/CodeSystem/acme-contact";
|
|
27
|
+
var unitOption = (code, display) => ({
|
|
28
|
+
url: "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption",
|
|
29
|
+
valueCoding: { system: "http://unitsofmeasure.org", code, display }
|
|
30
|
+
});
|
|
31
|
+
var coding = (code, display) => ({
|
|
32
|
+
valueCoding: { system: SYSTEM, code, display }
|
|
33
|
+
});
|
|
34
|
+
var SAMPLE_QUESTIONNAIRE = {
|
|
35
|
+
resourceType: "Questionnaire",
|
|
36
|
+
url: "https://example.org/fhir/Questionnaire/acme-intake",
|
|
37
|
+
version: "1.2.0",
|
|
38
|
+
name: "AcmeIntake",
|
|
39
|
+
status: "active",
|
|
40
|
+
title: "Acme intake",
|
|
41
|
+
description: "Before the first visit.",
|
|
42
|
+
item: [
|
|
43
|
+
{
|
|
44
|
+
linkId: "1",
|
|
45
|
+
text: "About you",
|
|
46
|
+
type: "group",
|
|
47
|
+
item: [
|
|
48
|
+
{ linkId: "1.intro", type: "display", text: "Tell us about yourself." },
|
|
49
|
+
{
|
|
50
|
+
linkId: "1.1",
|
|
51
|
+
prefix: "a)",
|
|
52
|
+
text: "Name",
|
|
53
|
+
type: "string",
|
|
54
|
+
required: true,
|
|
55
|
+
maxLength: 80,
|
|
56
|
+
extension: [
|
|
57
|
+
{
|
|
58
|
+
url: "http://hl7.org/fhir/StructureDefinition/entryFormat",
|
|
59
|
+
valueString: "First and last name"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{ linkId: "1.2", text: "Do you smoke?", type: "boolean" },
|
|
64
|
+
{
|
|
65
|
+
linkId: "1.3",
|
|
66
|
+
text: "Packs a day",
|
|
67
|
+
type: "integer",
|
|
68
|
+
enableWhen: [{ question: "1.2", operator: "=", answerBoolean: true }]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
linkId: "1.4",
|
|
72
|
+
text: "Contact by",
|
|
73
|
+
type: "choice",
|
|
74
|
+
answerOption: [coding("email", "E-mail"), coding("phone", "Phone")]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
linkId: "1.5",
|
|
78
|
+
text: "Heard of us by",
|
|
79
|
+
type: "open-choice",
|
|
80
|
+
answerOption: [
|
|
81
|
+
coding("web", "The web"),
|
|
82
|
+
coding("friend", "A friend")
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
linkId: "1.6",
|
|
87
|
+
text: "Weight",
|
|
88
|
+
type: "quantity",
|
|
89
|
+
extension: [unitOption("kg", "kg"), unitOption("[lb_av]", "lb")]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
linkId: "2",
|
|
95
|
+
text: "Visits",
|
|
96
|
+
type: "group",
|
|
97
|
+
repeats: true,
|
|
98
|
+
item: [
|
|
99
|
+
{ linkId: "2.1", text: "Date", type: "date" },
|
|
100
|
+
{
|
|
101
|
+
linkId: "2.2",
|
|
102
|
+
text: "Contact by",
|
|
103
|
+
type: "choice",
|
|
104
|
+
answerOption: [coding("email", "E-mail"), coding("phone", "Phone")],
|
|
105
|
+
item: [{ linkId: "2.2.1", text: "When to call", type: "string" }]
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{ linkId: "3", type: "display", text: "Thank you." }
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
var SAMPLE_RESPONSE = {
|
|
113
|
+
resourceType: "QuestionnaireResponse",
|
|
114
|
+
questionnaire: "https://example.org/fhir/Questionnaire/acme-intake|1.2.0",
|
|
115
|
+
status: "completed",
|
|
116
|
+
item: [
|
|
117
|
+
{
|
|
118
|
+
linkId: "1",
|
|
119
|
+
text: "About you",
|
|
120
|
+
item: [
|
|
121
|
+
{ linkId: "1.1", text: "Name", answer: [{ valueString: "Jane Roe" }] },
|
|
122
|
+
{
|
|
123
|
+
linkId: "1.2",
|
|
124
|
+
text: "Do you smoke?",
|
|
125
|
+
answer: [{ valueBoolean: true }]
|
|
126
|
+
},
|
|
127
|
+
{ linkId: "1.3", text: "Packs a day", answer: [{ valueInteger: 1 }] },
|
|
128
|
+
{
|
|
129
|
+
linkId: "1.4",
|
|
130
|
+
text: "Contact by",
|
|
131
|
+
answer: [
|
|
132
|
+
{
|
|
133
|
+
valueCoding: { system: SYSTEM, code: "phone", display: "Phone" }
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
linkId: "1.5",
|
|
139
|
+
text: "Heard of us by",
|
|
140
|
+
answer: [{ valueString: "a flyer" }]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
linkId: "1.6",
|
|
144
|
+
text: "Weight",
|
|
145
|
+
answer: [
|
|
146
|
+
{
|
|
147
|
+
valueQuantity: {
|
|
148
|
+
value: 72.5,
|
|
149
|
+
unit: "kg",
|
|
150
|
+
system: "http://unitsofmeasure.org",
|
|
151
|
+
code: "kg"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
linkId: "2",
|
|
160
|
+
text: "Visits",
|
|
161
|
+
item: [
|
|
162
|
+
{ linkId: "2.1", text: "Date", answer: [{ valueDate: "2026-11-02" }] }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/pages/PlayFhirQuestionnaire.tsx
|
|
169
|
+
import { TerminologyProvider } from "@petrarca/sonnet-core/terminology";
|
|
170
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
171
|
+
var pretty = (value) => JSON.stringify(value, null, 2);
|
|
172
|
+
function canonical(value) {
|
|
173
|
+
return JSON.stringify(
|
|
174
|
+
value,
|
|
175
|
+
(_, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(
|
|
176
|
+
Object.entries(v).sort(
|
|
177
|
+
([a], [b]) => a.localeCompare(b)
|
|
178
|
+
)
|
|
179
|
+
) : v
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function parseResource(text, resourceType) {
|
|
183
|
+
try {
|
|
184
|
+
const value = JSON.parse(text);
|
|
185
|
+
return value.resourceType === resourceType ? { value } : { error: `Not a ${resourceType}` };
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return { error: `Invalid JSON: ${e.message}` };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function read(questionnaire, version) {
|
|
191
|
+
const { model, notConverted } = fromFhirQuestionnaire(questionnaire, {
|
|
192
|
+
version
|
|
193
|
+
});
|
|
194
|
+
return { questionnaire, model, notConverted, version };
|
|
195
|
+
}
|
|
196
|
+
function NotConvertedBadge({ items }) {
|
|
197
|
+
if (items.length === 0) return null;
|
|
198
|
+
return /* @__PURE__ */ jsx(
|
|
199
|
+
SimpleTooltip,
|
|
200
|
+
{
|
|
201
|
+
side: "bottom",
|
|
202
|
+
label: /* @__PURE__ */ jsx("ul", { className: "text-xs space-y-1 max-w-sm", children: items.map((n) => /* @__PURE__ */ jsxs("li", { children: [
|
|
203
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: n.linkId || "(root)" }),
|
|
204
|
+
" --",
|
|
205
|
+
" ",
|
|
206
|
+
n.what
|
|
207
|
+
] }, `${n.linkId}:${n.what}`)) }),
|
|
208
|
+
children: /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "gap-1 cursor-default", children: [
|
|
209
|
+
/* @__PURE__ */ jsx(Info, { className: "size-3" }),
|
|
210
|
+
items.length,
|
|
211
|
+
" kept, not working as in FHIR"
|
|
212
|
+
] })
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
function RoundTripBadge({ same }) {
|
|
217
|
+
return same ? /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "gap-1", children: [
|
|
218
|
+
/* @__PURE__ */ jsx(Check, { className: "size-3" }),
|
|
219
|
+
"Questionnaire written back unchanged"
|
|
220
|
+
] }) : /* @__PURE__ */ jsxs(Badge, { variant: "destructive", className: "gap-1", children: [
|
|
221
|
+
/* @__PURE__ */ jsx(TriangleAlert, { className: "size-3" }),
|
|
222
|
+
"Questionnaire written back changed"
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
function PaneAction(props) {
|
|
226
|
+
if (!props.out)
|
|
227
|
+
return /* @__PURE__ */ jsx(
|
|
228
|
+
IconButton,
|
|
229
|
+
{
|
|
230
|
+
label: "Read into form",
|
|
231
|
+
className: "ml-auto shrink-0",
|
|
232
|
+
onClick: props.onRead,
|
|
233
|
+
children: /* @__PURE__ */ jsx(ArrowRight, {})
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
if (props.resource !== "response" || !props.submitted) return null;
|
|
237
|
+
return /* @__PURE__ */ jsx(
|
|
238
|
+
Button,
|
|
239
|
+
{
|
|
240
|
+
size: "sm",
|
|
241
|
+
variant: "outline",
|
|
242
|
+
className: "ml-auto shrink-0",
|
|
243
|
+
onClick: props.onUseSubmitted,
|
|
244
|
+
children: "Edit as input"
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
function FhirPane(props) {
|
|
249
|
+
const { resource, direction } = props;
|
|
250
|
+
const out = direction === "out";
|
|
251
|
+
const writtenValue = props.written[resource];
|
|
252
|
+
return /* @__PURE__ */ jsxs(Card, { className: "border shadow-sm p-4 min-h-0 flex flex-col gap-3", children: [
|
|
253
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
254
|
+
/* @__PURE__ */ jsx(
|
|
255
|
+
ViewSwitch,
|
|
256
|
+
{
|
|
257
|
+
className: "shrink-0",
|
|
258
|
+
label: "Resource",
|
|
259
|
+
value: resource,
|
|
260
|
+
onChange: props.onResource,
|
|
261
|
+
options: [
|
|
262
|
+
{ value: "questionnaire", label: "Questionnaire" },
|
|
263
|
+
{ value: "response", label: "Response" }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
/* @__PURE__ */ jsx(
|
|
268
|
+
ViewSwitch,
|
|
269
|
+
{
|
|
270
|
+
className: "shrink-0",
|
|
271
|
+
label: "Direction",
|
|
272
|
+
value: direction,
|
|
273
|
+
onChange: props.onDirection,
|
|
274
|
+
options: [
|
|
275
|
+
{ value: "in", label: "Into form" },
|
|
276
|
+
{ value: "out", label: "From form" }
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
),
|
|
280
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate min-w-0", children: out ? "written by the form -- read only" : "edit it, then read it into the form" }),
|
|
281
|
+
/* @__PURE__ */ jsx(
|
|
282
|
+
PaneAction,
|
|
283
|
+
{
|
|
284
|
+
resource,
|
|
285
|
+
out,
|
|
286
|
+
submitted: writtenValue !== null,
|
|
287
|
+
onRead: props.onRead[resource],
|
|
288
|
+
onUseSubmitted: props.onUseSubmitted
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 relative", children: out && writtenValue === null ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Fill the form and submit it: the response appears here." }) : /* @__PURE__ */ jsx(
|
|
293
|
+
CodeEditor,
|
|
294
|
+
{
|
|
295
|
+
value: out ? pretty(writtenValue) : props.texts[resource],
|
|
296
|
+
language: "json",
|
|
297
|
+
readOnly: out,
|
|
298
|
+
onChange: out ? void 0 : props.onEdit[resource],
|
|
299
|
+
fillContainer: true
|
|
300
|
+
},
|
|
301
|
+
`${resource}:${direction}`
|
|
302
|
+
) })
|
|
303
|
+
] });
|
|
304
|
+
}
|
|
305
|
+
function FormPane(props) {
|
|
306
|
+
const [view, setView] = useState("form");
|
|
307
|
+
return /* @__PURE__ */ jsxs(Card, { className: "border shadow-sm p-4 min-h-0 flex flex-col gap-3", children: [
|
|
308
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
309
|
+
/* @__PURE__ */ jsx(
|
|
310
|
+
ViewSwitch,
|
|
311
|
+
{
|
|
312
|
+
className: "shrink-0",
|
|
313
|
+
label: "View",
|
|
314
|
+
value: view,
|
|
315
|
+
onChange: setView,
|
|
316
|
+
options: [
|
|
317
|
+
{ value: "form", label: "Form" },
|
|
318
|
+
{ value: "schema", label: "Schema" }
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
),
|
|
322
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate min-w-0", children: view === "form" ? "fill it, then submit it as a response" : "the schema and uiSchema it was read as" })
|
|
323
|
+
] }),
|
|
324
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 relative overflow-auto", children: view === "form" ? /* @__PURE__ */ jsx(
|
|
325
|
+
JsonSchemaFormRenderer,
|
|
326
|
+
{
|
|
327
|
+
schema: props.model.schema,
|
|
328
|
+
uiSchema: props.model.uiSchema,
|
|
329
|
+
data: props.answers,
|
|
330
|
+
onChange: props.onAnswers,
|
|
331
|
+
onUpdate: props.onSubmit,
|
|
332
|
+
showActions: true,
|
|
333
|
+
showCancel: false,
|
|
334
|
+
allowPristineSave: true,
|
|
335
|
+
saveLabel: "Submit",
|
|
336
|
+
showExtraProperties: false
|
|
337
|
+
},
|
|
338
|
+
props.formKey
|
|
339
|
+
) : /* @__PURE__ */ jsx(
|
|
340
|
+
CodeEditor,
|
|
341
|
+
{
|
|
342
|
+
value: pretty(props.model),
|
|
343
|
+
language: "json",
|
|
344
|
+
readOnly: true,
|
|
345
|
+
fillContainer: true
|
|
346
|
+
}
|
|
347
|
+
) })
|
|
348
|
+
] });
|
|
349
|
+
}
|
|
350
|
+
function PlayFhirQuestionnaire() {
|
|
351
|
+
const [questionnaireText, setQuestionnaireText] = useState(
|
|
352
|
+
() => pretty(SAMPLE_QUESTIONNAIRE)
|
|
353
|
+
);
|
|
354
|
+
const [responseText, setResponseText] = useState(
|
|
355
|
+
() => pretty(SAMPLE_RESPONSE)
|
|
356
|
+
);
|
|
357
|
+
const [current, setCurrent] = useState(
|
|
358
|
+
() => read(SAMPLE_QUESTIONNAIRE, "R4")
|
|
359
|
+
);
|
|
360
|
+
const [answers, setAnswers] = useState({});
|
|
361
|
+
const [formKey, setFormKey] = useState(0);
|
|
362
|
+
const [submitted, setSubmitted] = useState(
|
|
363
|
+
null
|
|
364
|
+
);
|
|
365
|
+
const [responseNotConverted, setResponseNotConverted] = useState([]);
|
|
366
|
+
const [responseElements, setResponseElements] = useState();
|
|
367
|
+
const [error, setError] = useState(null);
|
|
368
|
+
const [resource, setResource] = useState("questionnaire");
|
|
369
|
+
const [direction, setDirection] = useState("in");
|
|
370
|
+
const written = useMemo(
|
|
371
|
+
() => toFhirQuestionnaire(current.model, { version: current.version }),
|
|
372
|
+
[current.model, current.version]
|
|
373
|
+
);
|
|
374
|
+
const same = canonical(written.resource) === canonical(current.questionnaire);
|
|
375
|
+
const replaceAnswers = (next) => {
|
|
376
|
+
setAnswers(next);
|
|
377
|
+
setFormKey((k) => k + 1);
|
|
378
|
+
};
|
|
379
|
+
const readQuestionnaire = (version = current.version) => {
|
|
380
|
+
const parsed = parseResource(
|
|
381
|
+
questionnaireText,
|
|
382
|
+
"Questionnaire"
|
|
383
|
+
);
|
|
384
|
+
if ("error" in parsed) return setError(parsed.error);
|
|
385
|
+
setError(null);
|
|
386
|
+
setCurrent(read(parsed.value, version));
|
|
387
|
+
setSubmitted(null);
|
|
388
|
+
setResponseNotConverted([]);
|
|
389
|
+
setResponseElements(void 0);
|
|
390
|
+
replaceAnswers({});
|
|
391
|
+
};
|
|
392
|
+
const readResponse = () => {
|
|
393
|
+
const parsed = parseResource(
|
|
394
|
+
responseText,
|
|
395
|
+
"QuestionnaireResponse"
|
|
396
|
+
);
|
|
397
|
+
if ("error" in parsed) return setError(parsed.error);
|
|
398
|
+
setError(null);
|
|
399
|
+
const { data, elements, notConverted } = fromFhirQuestionnaireResponse(
|
|
400
|
+
current.model,
|
|
401
|
+
parsed.value,
|
|
402
|
+
{ version: current.version }
|
|
403
|
+
);
|
|
404
|
+
setResponseNotConverted(notConverted);
|
|
405
|
+
setResponseElements(elements);
|
|
406
|
+
replaceAnswers(data);
|
|
407
|
+
};
|
|
408
|
+
const submit = (data) => {
|
|
409
|
+
setSubmitted(
|
|
410
|
+
toFhirQuestionnaireResponse(current.model, data, {
|
|
411
|
+
version: current.version,
|
|
412
|
+
elements: responseElements
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
setResource("response");
|
|
416
|
+
setDirection("out");
|
|
417
|
+
};
|
|
418
|
+
const loadSample = () => {
|
|
419
|
+
setQuestionnaireText(pretty(SAMPLE_QUESTIONNAIRE));
|
|
420
|
+
setResponseText(pretty(SAMPLE_RESPONSE));
|
|
421
|
+
setCurrent(read(SAMPLE_QUESTIONNAIRE, current.version));
|
|
422
|
+
setSubmitted(null);
|
|
423
|
+
setResponseNotConverted([]);
|
|
424
|
+
setResponseElements(void 0);
|
|
425
|
+
setError(null);
|
|
426
|
+
replaceAnswers({});
|
|
427
|
+
setResource("questionnaire");
|
|
428
|
+
setDirection("in");
|
|
429
|
+
};
|
|
430
|
+
const useSubmittedAsInput = () => {
|
|
431
|
+
if (!submitted) return;
|
|
432
|
+
setResponseText(pretty(submitted));
|
|
433
|
+
setDirection("in");
|
|
434
|
+
};
|
|
435
|
+
return /* @__PURE__ */ jsx(TerminologyProvider, { service: PLAYGROUND_TERMINOLOGY, children: /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-3 h-[calc(100vh-8rem)]", children: [
|
|
436
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
437
|
+
/* @__PURE__ */ jsx("h1", { children: "FHIR Questionnaire" }),
|
|
438
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-3", children: [
|
|
439
|
+
"Read a FHIR Questionnaire -- R4 or R5 -- as a questionnaire on JSON Schema, fill its form, submit it as a QuestionnaireResponse -- and read a response back into the form. What JSON Schema has no word for travels in ",
|
|
440
|
+
/* @__PURE__ */ jsx("code", { children: "x-fhir-*" }),
|
|
441
|
+
" annotations, so the questionnaire is written back as it was read."
|
|
442
|
+
] })
|
|
443
|
+
] }),
|
|
444
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
445
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: loadSample, children: "Sample" }),
|
|
446
|
+
/* @__PURE__ */ jsx(
|
|
447
|
+
ViewSwitch,
|
|
448
|
+
{
|
|
449
|
+
label: "FHIR version",
|
|
450
|
+
value: current.version,
|
|
451
|
+
onChange: (v) => readQuestionnaire(v),
|
|
452
|
+
options: [
|
|
453
|
+
{ value: "R4", label: "R4" },
|
|
454
|
+
{ value: "R5", label: "R5" }
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
),
|
|
458
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 ml-auto", children: [
|
|
459
|
+
error && /* @__PURE__ */ jsx(Badge, { variant: "destructive", children: error }),
|
|
460
|
+
/* @__PURE__ */ jsx(RoundTripBadge, { same }),
|
|
461
|
+
/* @__PURE__ */ jsx(
|
|
462
|
+
NotConvertedBadge,
|
|
463
|
+
{
|
|
464
|
+
items: [...current.notConverted, ...responseNotConverted]
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
] })
|
|
468
|
+
] }),
|
|
469
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-2 gap-6", children: [
|
|
470
|
+
/* @__PURE__ */ jsx(
|
|
471
|
+
FhirPane,
|
|
472
|
+
{
|
|
473
|
+
resource,
|
|
474
|
+
direction,
|
|
475
|
+
onResource: setResource,
|
|
476
|
+
onDirection: setDirection,
|
|
477
|
+
texts: {
|
|
478
|
+
questionnaire: questionnaireText,
|
|
479
|
+
response: responseText
|
|
480
|
+
},
|
|
481
|
+
onEdit: {
|
|
482
|
+
questionnaire: setQuestionnaireText,
|
|
483
|
+
response: setResponseText
|
|
484
|
+
},
|
|
485
|
+
written: { questionnaire: written.resource, response: submitted },
|
|
486
|
+
onRead: {
|
|
487
|
+
questionnaire: () => readQuestionnaire(),
|
|
488
|
+
response: readResponse
|
|
489
|
+
},
|
|
490
|
+
onUseSubmitted: useSubmittedAsInput
|
|
491
|
+
}
|
|
492
|
+
),
|
|
493
|
+
/* @__PURE__ */ jsx(
|
|
494
|
+
FormPane,
|
|
495
|
+
{
|
|
496
|
+
model: current.model,
|
|
497
|
+
formKey,
|
|
498
|
+
answers,
|
|
499
|
+
onAnswers: setAnswers,
|
|
500
|
+
onSubmit: submit
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
] })
|
|
504
|
+
] }) });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export {
|
|
508
|
+
PlayFhirQuestionnaire
|
|
509
|
+
};
|
|
510
|
+
//# sourceMappingURL=chunk-OUL3DN2E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pages/PlayFhirQuestionnaire.tsx","../src/pages/fhir/samples.ts"],"sourcesContent":["/**\n * FHIR Questionnaire -- end to end: read a FHIR Questionnaire (R4 or R5) as a\n * questionnaire on JSON Schema, fill its form, submit it as a\n * QuestionnaireResponse, and read a response back into the form. Two panes:\n * the FHIR JSON (into the form, editable and read by its pane's action; from\n * the form, generated) and the form.\n */\n\nimport { useMemo, useState } from \"react\";\nimport type { DesignerModel } from \"@petrarca/sonnet-schema-designer/questionnaire\";\nimport {\n fromFhirQuestionnaire,\n fromFhirQuestionnaireResponse,\n toFhirQuestionnaire,\n toFhirQuestionnaireResponse,\n type NotConverted,\n type ResponseElements,\n} from \"@petrarca/sonnet-fhir/questionnaire\";\nimport type * as R4 from \"@petrarca/sonnet-fhir/R4\";\nimport type { FhirVersion } from \"@petrarca/sonnet-fhir\";\nimport { JsonSchemaFormRenderer } from \"@petrarca/sonnet-forms\";\nimport {\n Badge,\n Button,\n Card,\n IconButton,\n SimpleTooltip,\n ViewSwitch,\n} from \"@petrarca/sonnet-ui\";\nimport { CodeEditor } from \"@petrarca/sonnet-ui/code-editor\";\nimport { ArrowRight, Check, Info, TriangleAlert } from \"lucide-react\";\nimport { SAMPLE_QUESTIONNAIRE, SAMPLE_RESPONSE } from \"./fhir/samples\";\nimport { TerminologyProvider } from \"@petrarca/sonnet-core/terminology\";\nimport { PLAYGROUND_TERMINOLOGY } from \"./codedValues/lists\";\n\ntype Answers = Record<string, unknown>;\ntype Resource = \"questionnaire\" | \"response\";\ntype Direction = \"in\" | \"out\";\n\nconst pretty = (value: unknown) => JSON.stringify(value, null, 2);\n\n// JSON with its keys sorted: two resources are the same when these are equal.\nfunction canonical(value: unknown): string {\n return JSON.stringify(value, (_, v: unknown) =>\n v && typeof v === \"object\" && !Array.isArray(v)\n ? Object.fromEntries(\n Object.entries(v as Record<string, unknown>).sort(([a], [b]) =>\n a.localeCompare(b),\n ),\n )\n : v,\n );\n}\n\n// Parse a FHIR resource of a type; the error, in words, when it is not one.\nfunction parseResource<T>(\n text: string,\n resourceType: string,\n): { value: T } | { error: string } {\n try {\n const value = JSON.parse(text) as { resourceType?: string };\n return value.resourceType === resourceType\n ? { value: value as T }\n : { error: `Not a ${resourceType}` };\n } catch (e) {\n return { error: `Invalid JSON: ${(e as Error).message}` };\n }\n}\n\n// What was read: the questionnaire as it came, and as a schema.\ninterface Read {\n questionnaire: R4.Questionnaire;\n model: DesignerModel;\n notConverted: NotConverted[];\n /** The FHIR version it was read as -- and is written back and answered in. */\n version: FhirVersion;\n}\n\nfunction read(questionnaire: R4.Questionnaire, version: FhirVersion): Read {\n const { model, notConverted } = fromFhirQuestionnaire(questionnaire, {\n version,\n });\n return { questionnaire, model, notConverted, version };\n}\n\nfunction NotConvertedBadge({ items }: { items: NotConverted[] }) {\n if (items.length === 0) return null;\n return (\n <SimpleTooltip\n side=\"bottom\"\n label={\n <ul className=\"text-xs space-y-1 max-w-sm\">\n {items.map((n) => (\n <li key={`${n.linkId}:${n.what}`}>\n <span className=\"font-mono\">{n.linkId || \"(root)\"}</span> --{\" \"}\n {n.what}\n </li>\n ))}\n </ul>\n }\n >\n <Badge variant=\"outline\" className=\"gap-1 cursor-default\">\n <Info className=\"size-3\" />\n {items.length} kept, not working as in FHIR\n </Badge>\n </SimpleTooltip>\n );\n}\n\nfunction RoundTripBadge({ same }: { same: boolean }) {\n return same ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n <Check className=\"size-3\" />\n Questionnaire written back unchanged\n </Badge>\n ) : (\n <Badge variant=\"destructive\" className=\"gap-1\">\n <TriangleAlert className=\"size-3\" />\n Questionnaire written back changed\n </Badge>\n );\n}\n\n// The pane's action: read what is shown into the form; or take a submitted response as the one to read.\nfunction PaneAction(props: {\n resource: Resource;\n out: boolean;\n submitted: boolean;\n onRead: () => void;\n onUseSubmitted: () => void;\n}) {\n if (!props.out)\n return (\n <IconButton\n label=\"Read into form\"\n className=\"ml-auto shrink-0\"\n onClick={props.onRead}\n >\n <ArrowRight />\n </IconButton>\n );\n if (props.resource !== \"response\" || !props.submitted) return null;\n return (\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"ml-auto shrink-0\"\n onClick={props.onUseSubmitted}\n >\n Edit as input\n </Button>\n );\n}\n\n// The left pane: one JSON editor, switched between the resources and the directions.\nfunction FhirPane(props: {\n resource: Resource;\n direction: Direction;\n onResource: (r: Resource) => void;\n onDirection: (d: Direction) => void;\n texts: Record<Resource, string>;\n onEdit: Record<Resource, (text: string) => void>;\n written: Record<Resource, unknown>;\n onRead: Record<Resource, () => void>;\n onUseSubmitted: () => void;\n}) {\n const { resource, direction } = props;\n const out = direction === \"out\";\n const writtenValue = props.written[resource];\n return (\n <Card className=\"border shadow-sm p-4 min-h-0 flex flex-col gap-3\">\n <div className=\"flex items-center gap-2\">\n <ViewSwitch<Resource>\n className=\"shrink-0\"\n label=\"Resource\"\n value={resource}\n onChange={props.onResource}\n options={[\n { value: \"questionnaire\", label: \"Questionnaire\" },\n { value: \"response\", label: \"Response\" },\n ]}\n />\n <ViewSwitch<Direction>\n className=\"shrink-0\"\n label=\"Direction\"\n value={direction}\n onChange={props.onDirection}\n options={[\n { value: \"in\", label: \"Into form\" },\n { value: \"out\", label: \"From form\" },\n ]}\n />\n <span className=\"text-xs text-muted-foreground truncate min-w-0\">\n {out\n ? \"written by the form -- read only\"\n : \"edit it, then read it into the form\"}\n </span>\n <PaneAction\n resource={resource}\n out={out}\n submitted={writtenValue !== null}\n onRead={props.onRead[resource]}\n onUseSubmitted={props.onUseSubmitted}\n />\n </div>\n <div className=\"flex-1 min-h-0 relative\">\n {out && writtenValue === null ? (\n <p className=\"text-sm text-muted-foreground\">\n Fill the form and submit it: the response appears here.\n </p>\n ) : (\n <CodeEditor\n key={`${resource}:${direction}`}\n value={out ? pretty(writtenValue) : props.texts[resource]}\n language=\"json\"\n readOnly={out}\n onChange={out ? undefined : props.onEdit[resource]}\n fillContainer\n />\n )}\n </div>\n </Card>\n );\n}\n\n// The right pane: the questionnaire's form, to fill and submit; or its schema.\nfunction FormPane(props: {\n model: DesignerModel;\n formKey: number;\n answers: Answers;\n onAnswers: (answers: Answers) => void;\n onSubmit: (answers: Answers) => void;\n}) {\n const [view, setView] = useState<\"form\" | \"schema\">(\"form\");\n return (\n <Card className=\"border shadow-sm p-4 min-h-0 flex flex-col gap-3\">\n <div className=\"flex items-center gap-2\">\n <ViewSwitch<\"form\" | \"schema\">\n className=\"shrink-0\"\n label=\"View\"\n value={view}\n onChange={setView}\n options={[\n { value: \"form\", label: \"Form\" },\n { value: \"schema\", label: \"Schema\" },\n ]}\n />\n <span className=\"text-xs text-muted-foreground truncate min-w-0\">\n {view === \"form\"\n ? \"fill it, then submit it as a response\"\n : \"the schema and uiSchema it was read as\"}\n </span>\n </div>\n <div className=\"flex-1 min-h-0 relative overflow-auto\">\n {view === \"form\" ? (\n <JsonSchemaFormRenderer\n key={props.formKey}\n schema={props.model.schema}\n uiSchema={props.model.uiSchema}\n data={props.answers}\n onChange={props.onAnswers}\n onUpdate={props.onSubmit}\n showActions\n showCancel={false}\n allowPristineSave\n saveLabel=\"Submit\"\n showExtraProperties={false}\n />\n ) : (\n <CodeEditor\n value={pretty(props.model)}\n language=\"json\"\n readOnly\n fillContainer\n />\n )}\n </div>\n </Card>\n );\n}\n\nexport default function PlayFhirQuestionnaire() {\n const [questionnaireText, setQuestionnaireText] = useState(() =>\n pretty(SAMPLE_QUESTIONNAIRE),\n );\n const [responseText, setResponseText] = useState(() =>\n pretty(SAMPLE_RESPONSE),\n );\n const [current, setCurrent] = useState<Read>(() =>\n read(SAMPLE_QUESTIONNAIRE, \"R4\"),\n );\n const [answers, setAnswers] = useState<Answers>({});\n // The form is remounted when its answers are replaced from outside.\n const [formKey, setFormKey] = useState(0);\n const [submitted, setSubmitted] = useState<R4.QuestionnaireResponse | null>(\n null,\n );\n const [responseNotConverted, setResponseNotConverted] = useState<\n NotConverted[]\n >([]);\n // The read response's own elements -- id, subject, author, authored -- written back on submit.\n const [responseElements, setResponseElements] = useState<ResponseElements>();\n const [error, setError] = useState<string | null>(null);\n const [resource, setResource] = useState<Resource>(\"questionnaire\");\n const [direction, setDirection] = useState<Direction>(\"in\");\n\n const written = useMemo(\n () => toFhirQuestionnaire(current.model, { version: current.version }),\n [current.model, current.version],\n );\n const same = canonical(written.resource) === canonical(current.questionnaire);\n\n const replaceAnswers = (next: Answers) => {\n setAnswers(next);\n setFormKey((k) => k + 1);\n };\n\n // Read the questionnaire's text into the form, as a FHIR version -- the current one by default.\n const readQuestionnaire = (version: FhirVersion = current.version) => {\n const parsed = parseResource<R4.Questionnaire>(\n questionnaireText,\n \"Questionnaire\",\n );\n if (\"error\" in parsed) return setError(parsed.error);\n setError(null);\n setCurrent(read(parsed.value, version));\n setSubmitted(null);\n setResponseNotConverted([]);\n setResponseElements(undefined);\n replaceAnswers({});\n };\n\n const readResponse = () => {\n const parsed = parseResource<R4.QuestionnaireResponse>(\n responseText,\n \"QuestionnaireResponse\",\n );\n if (\"error\" in parsed) return setError(parsed.error);\n setError(null);\n const { data, elements, notConverted } = fromFhirQuestionnaireResponse(\n current.model,\n parsed.value,\n { version: current.version },\n );\n setResponseNotConverted(notConverted);\n setResponseElements(elements);\n replaceAnswers(data);\n };\n\n const submit = (data: Answers) => {\n setSubmitted(\n toFhirQuestionnaireResponse(current.model, data, {\n version: current.version,\n elements: responseElements,\n }),\n );\n setResource(\"response\");\n setDirection(\"out\");\n };\n\n const loadSample = () => {\n setQuestionnaireText(pretty(SAMPLE_QUESTIONNAIRE));\n setResponseText(pretty(SAMPLE_RESPONSE));\n setCurrent(read(SAMPLE_QUESTIONNAIRE, current.version));\n setSubmitted(null);\n setResponseNotConverted([]);\n setResponseElements(undefined);\n setError(null);\n replaceAnswers({});\n setResource(\"questionnaire\");\n setDirection(\"in\");\n };\n\n // The submitted response as the response to read: the round trip of the answers.\n const useSubmittedAsInput = () => {\n if (!submitted) return;\n setResponseText(pretty(submitted));\n setDirection(\"in\");\n };\n\n return (\n <TerminologyProvider service={PLAYGROUND_TERMINOLOGY}>\n <div className=\"w-full flex flex-col gap-3 h-[calc(100vh-8rem)]\">\n <div>\n <h1>FHIR Questionnaire</h1>\n <p className=\"text-sm text-muted-foreground mb-3\">\n Read a FHIR Questionnaire -- R4 or R5 -- as a questionnaire on JSON\n Schema, fill its form, submit it as a QuestionnaireResponse -- and\n read a response back into the form. What JSON Schema has no word for\n travels in <code>x-fhir-*</code> annotations, so the questionnaire\n is written back as it was read.\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={loadSample}>\n Sample\n </Button>\n <ViewSwitch<FhirVersion>\n label=\"FHIR version\"\n value={current.version}\n onChange={(v) => readQuestionnaire(v)}\n options={[\n { value: \"R4\", label: \"R4\" },\n { value: \"R5\", label: \"R5\" },\n ]}\n />\n <div className=\"flex flex-wrap items-center gap-2 ml-auto\">\n {error && <Badge variant=\"destructive\">{error}</Badge>}\n <RoundTripBadge same={same} />\n <NotConvertedBadge\n items={[...current.notConverted, ...responseNotConverted]}\n />\n </div>\n </div>\n\n <div className=\"flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <FhirPane\n resource={resource}\n direction={direction}\n onResource={setResource}\n onDirection={setDirection}\n texts={{\n questionnaire: questionnaireText,\n response: responseText,\n }}\n onEdit={{\n questionnaire: setQuestionnaireText,\n response: setResponseText,\n }}\n written={{ questionnaire: written.resource, response: submitted }}\n onRead={{\n questionnaire: () => readQuestionnaire(),\n response: readResponse,\n }}\n onUseSubmitted={useSubmittedAsInput}\n />\n\n <FormPane\n model={current.model}\n formKey={formKey}\n answers={answers}\n onAnswers={setAnswers}\n onSubmit={submit}\n />\n </div>\n </div>\n </TerminologyProvider>\n );\n}\n","/**\n * A FHIR R4 Questionnaire as a FHIR server holds one -- numbered linkIds,\n * elements and extensions JSON Schema has no word for, a quantity with its\n * units -- and a response to it. Neutral sample data for Acme.\n */\n\nimport type * as R4 from \"@petrarca/sonnet-fhir/R4\";\n\nconst SYSTEM = \"https://example.org/fhir/CodeSystem/acme-contact\";\nconst unitOption = (code: string, display: string) => ({\n url: \"http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption\",\n valueCoding: { system: \"http://unitsofmeasure.org\", code, display },\n});\nconst coding = (code: string, display: string) => ({\n valueCoding: { system: SYSTEM, code, display },\n});\n\nexport const SAMPLE_QUESTIONNAIRE: R4.Questionnaire = {\n resourceType: \"Questionnaire\",\n url: \"https://example.org/fhir/Questionnaire/acme-intake\",\n version: \"1.2.0\",\n name: \"AcmeIntake\",\n status: \"active\",\n title: \"Acme intake\",\n description: \"Before the first visit.\",\n item: [\n {\n linkId: \"1\",\n text: \"About you\",\n type: \"group\",\n item: [\n { linkId: \"1.intro\", type: \"display\", text: \"Tell us about yourself.\" },\n {\n linkId: \"1.1\",\n prefix: \"a)\",\n text: \"Name\",\n type: \"string\",\n required: true,\n maxLength: 80,\n extension: [\n {\n url: \"http://hl7.org/fhir/StructureDefinition/entryFormat\",\n valueString: \"First and last name\",\n },\n ],\n },\n { linkId: \"1.2\", text: \"Do you smoke?\", type: \"boolean\" },\n {\n linkId: \"1.3\",\n text: \"Packs a day\",\n type: \"integer\",\n enableWhen: [{ question: \"1.2\", operator: \"=\", answerBoolean: true }],\n },\n {\n linkId: \"1.4\",\n text: \"Contact by\",\n type: \"choice\",\n answerOption: [coding(\"email\", \"E-mail\"), coding(\"phone\", \"Phone\")],\n },\n {\n linkId: \"1.5\",\n text: \"Heard of us by\",\n type: \"open-choice\",\n answerOption: [\n coding(\"web\", \"The web\"),\n coding(\"friend\", \"A friend\"),\n ],\n },\n {\n linkId: \"1.6\",\n text: \"Weight\",\n type: \"quantity\",\n extension: [unitOption(\"kg\", \"kg\"), unitOption(\"[lb_av]\", \"lb\")],\n },\n ],\n },\n {\n linkId: \"2\",\n text: \"Visits\",\n type: \"group\",\n repeats: true,\n item: [\n { linkId: \"2.1\", text: \"Date\", type: \"date\" },\n {\n linkId: \"2.2\",\n text: \"Contact by\",\n type: \"choice\",\n answerOption: [coding(\"email\", \"E-mail\"), coding(\"phone\", \"Phone\")],\n item: [{ linkId: \"2.2.1\", text: \"When to call\", type: \"string\" }],\n },\n ],\n },\n { linkId: \"3\", type: \"display\", text: \"Thank you.\" },\n ],\n};\n\nexport const SAMPLE_RESPONSE: R4.QuestionnaireResponse = {\n resourceType: \"QuestionnaireResponse\",\n questionnaire: \"https://example.org/fhir/Questionnaire/acme-intake|1.2.0\",\n status: \"completed\",\n item: [\n {\n linkId: \"1\",\n text: \"About you\",\n item: [\n { linkId: \"1.1\", text: \"Name\", answer: [{ valueString: \"Jane Roe\" }] },\n {\n linkId: \"1.2\",\n text: \"Do you smoke?\",\n answer: [{ valueBoolean: true }],\n },\n { linkId: \"1.3\", text: \"Packs a day\", answer: [{ valueInteger: 1 }] },\n {\n linkId: \"1.4\",\n text: \"Contact by\",\n answer: [\n {\n valueCoding: { system: SYSTEM, code: \"phone\", display: \"Phone\" },\n },\n ],\n },\n {\n linkId: \"1.5\",\n text: \"Heard of us by\",\n answer: [{ valueString: \"a flyer\" }],\n },\n {\n linkId: \"1.6\",\n text: \"Weight\",\n answer: [\n {\n valueQuantity: {\n value: 72.5,\n unit: \"kg\",\n system: \"http://unitsofmeasure.org\",\n code: \"kg\",\n },\n },\n ],\n },\n ],\n },\n {\n linkId: \"2\",\n text: \"Visits\",\n item: [\n { linkId: \"2.1\", text: \"Date\", answer: [{ valueDate: \"2026-11-02\" }] },\n ],\n },\n ],\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/pages/DesignerControls.tsx
|
|
2
|
+
import { FormSelect } from "@petrarca/sonnet-forms";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
function LabelledSelect({
|
|
5
|
+
id,
|
|
6
|
+
label,
|
|
7
|
+
choices,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
disabled
|
|
11
|
+
}) {
|
|
12
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13
|
+
/* @__PURE__ */ jsx("label", { htmlFor: id, className: "shrink-0 text-sm font-medium", children: label }),
|
|
14
|
+
/* @__PURE__ */ jsx("div", { className: "w-64", children: /* @__PURE__ */ jsx(
|
|
15
|
+
FormSelect,
|
|
16
|
+
{
|
|
17
|
+
id,
|
|
18
|
+
name: id,
|
|
19
|
+
value,
|
|
20
|
+
options: Object.keys(choices).map((k) => ({
|
|
21
|
+
value: k,
|
|
22
|
+
label: choices[k].label
|
|
23
|
+
})),
|
|
24
|
+
onChange: (v) => v && onChange(v),
|
|
25
|
+
clearable: false,
|
|
26
|
+
disabled
|
|
27
|
+
}
|
|
28
|
+
) })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
function DesignerControls({
|
|
32
|
+
examples,
|
|
33
|
+
example,
|
|
34
|
+
onExample,
|
|
35
|
+
exampleLocked,
|
|
36
|
+
profiles,
|
|
37
|
+
profile,
|
|
38
|
+
onProfile,
|
|
39
|
+
right
|
|
40
|
+
}) {
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
43
|
+
exampleLocked ? /* @__PURE__ */ jsx(
|
|
44
|
+
LabelledSelect,
|
|
45
|
+
{
|
|
46
|
+
id: "designer-example",
|
|
47
|
+
label: "Example",
|
|
48
|
+
choices: { locked: { label: exampleLocked } },
|
|
49
|
+
value: "locked",
|
|
50
|
+
onChange: () => {
|
|
51
|
+
},
|
|
52
|
+
disabled: true
|
|
53
|
+
}
|
|
54
|
+
) : /* @__PURE__ */ jsx(
|
|
55
|
+
LabelledSelect,
|
|
56
|
+
{
|
|
57
|
+
id: "designer-example",
|
|
58
|
+
label: "Example",
|
|
59
|
+
choices: examples,
|
|
60
|
+
value: example,
|
|
61
|
+
onChange: onExample
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
/* @__PURE__ */ jsx(
|
|
65
|
+
LabelledSelect,
|
|
66
|
+
{
|
|
67
|
+
id: "capability-profile",
|
|
68
|
+
label: "Capability profile",
|
|
69
|
+
choices: profiles,
|
|
70
|
+
value: profile,
|
|
71
|
+
onChange: onProfile
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
right && /* @__PURE__ */ jsx("div", { className: "ml-auto", children: right })
|
|
75
|
+
] }),
|
|
76
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: profiles[profile].description })
|
|
77
|
+
] });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
DesignerControls
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=chunk-QKFQI4VX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pages/DesignerControls.tsx"],"sourcesContent":["/**\n * The controls of a designer page: the example to work on and the capability\n * profile to work with -- two separate choices -- the profile's description,\n * and whatever the page adds on the right.\n */\n\nimport type React from \"react\";\nimport { FormSelect } from \"@petrarca/sonnet-forms\";\n\ninterface Choice {\n label: string;\n}\n\nfunction LabelledSelect<K extends string>({\n id,\n label,\n choices,\n value,\n onChange,\n disabled,\n}: {\n id: string;\n label: string;\n choices: Record<K, Choice>;\n value: K;\n onChange: (key: K) => void;\n disabled?: boolean;\n}): React.ReactElement {\n return (\n <>\n <label htmlFor={id} className=\"shrink-0 text-sm font-medium\">\n {label}\n </label>\n <div className=\"w-64\">\n <FormSelect\n id={id}\n name={id}\n value={value}\n options={(Object.keys(choices) as K[]).map((k) => ({\n value: k,\n label: choices[k].label,\n }))}\n onChange={(v) => v && onChange(v as K)}\n clearable={false}\n disabled={disabled}\n />\n </div>\n </>\n );\n}\n\nexport function DesignerControls<E extends string, P extends string>({\n examples,\n example,\n onExample,\n exampleLocked,\n profiles,\n profile,\n onProfile,\n right,\n}: {\n examples: Record<E, Choice>;\n example: E;\n onExample: (key: E) => void;\n /** The schema the profile brings instead of an example: named, the select disabled. */\n exampleLocked?: string;\n profiles: Record<P, Choice & { description: string }>;\n profile: P;\n onProfile: (key: P) => void;\n right?: React.ReactNode;\n}): React.ReactElement {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center gap-3\">\n {exampleLocked ? (\n <LabelledSelect\n id=\"designer-example\"\n label=\"Example\"\n choices={{ locked: { label: exampleLocked } }}\n value=\"locked\"\n onChange={() => {}}\n disabled\n />\n ) : (\n <LabelledSelect\n id=\"designer-example\"\n label=\"Example\"\n choices={examples}\n value={example}\n onChange={onExample}\n />\n )}\n <LabelledSelect\n id=\"capability-profile\"\n label=\"Capability profile\"\n choices={profiles}\n value={profile}\n onChange={onProfile}\n />\n {right && <div className=\"ml-auto\">{right}</div>}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {profiles[profile].description}\n </p>\n </div>\n );\n}\n"],"mappings":";AAOA,SAAS,kBAAkB;AAsBvB,mBACE,KADF;AAhBJ,SAAS,eAAiC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOuB;AACrB,SACE,iCACE;AAAA,wBAAC,WAAM,SAAS,IAAI,WAAU,gCAC3B,iBACH;AAAA,IACA,oBAAC,SAAI,WAAU,QACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA,SAAU,OAAO,KAAK,OAAO,EAAU,IAAI,CAAC,OAAO;AAAA,UACjD,OAAO;AAAA,UACP,OAAO,QAAQ,CAAC,EAAE;AAAA,QACpB,EAAE;AAAA,QACF,UAAU,CAAC,MAAM,KAAK,SAAS,CAAM;AAAA,QACrC,WAAW;AAAA,QACX;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;AAEO,SAAS,iBAAqD;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUuB;AACrB,SACE,qBAAC,SAAI,WAAU,uBACb;AAAA,yBAAC,SAAI,WAAU,2BACZ;AAAA,sBACC;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAM;AAAA,UACN,SAAS,EAAE,QAAQ,EAAE,OAAO,cAAc,EAAE;AAAA,UAC5C,OAAM;AAAA,UACN,UAAU,MAAM;AAAA,UAAC;AAAA,UACjB,UAAQ;AAAA;AAAA,MACV,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAM;AAAA,UACN,SAAS;AAAA,UACT,OAAO;AAAA,UACP,UAAU;AAAA;AAAA,MACZ;AAAA,MAEF;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAM;AAAA,UACN,SAAS;AAAA,UACT,OAAO;AAAA,UACP,UAAU;AAAA;AAAA,MACZ;AAAA,MACC,SAAS,oBAAC,SAAI,WAAU,WAAW,iBAAM;AAAA,OAC5C;AAAA,IACA,oBAAC,OAAE,WAAU,iCACV,mBAAS,OAAO,EAAE,aACrB;AAAA,KACF;AAEJ;","names":[]}
|