@squaredr/fieldcraft-pro 0.0.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,976 @@
1
+ import { cn } from './chunk-QQ4JZGTD.mjs';
2
+ import { requireLicense } from '@squaredr/fieldcraft-pro-license';
3
+ import { useState } from 'react';
4
+ import { Button, Separator } from '@squaredr/fieldcraft-react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+
7
+ // src/response-viewer/clinical-display-data.ts
8
+ var BODY_REGION_LABELS = {
9
+ head: "Head",
10
+ neck: "Neck",
11
+ "left-shoulder": "Left Shoulder",
12
+ "right-shoulder": "Right Shoulder",
13
+ chest: "Chest",
14
+ abdomen: "Abdomen",
15
+ "left-upper-arm": "Left Upper Arm",
16
+ "right-upper-arm": "Right Upper Arm",
17
+ "left-forearm": "Left Forearm",
18
+ "right-forearm": "Right Forearm",
19
+ "left-hand": "Left Hand",
20
+ "right-hand": "Right Hand",
21
+ pelvis: "Pelvis",
22
+ "left-thigh": "Left Thigh",
23
+ "right-thigh": "Right Thigh",
24
+ "left-knee": "Left Knee",
25
+ "right-knee": "Right Knee",
26
+ "left-shin": "Left Shin",
27
+ "right-shin": "Right Shin",
28
+ "left-foot": "Left Foot",
29
+ "right-foot": "Right Foot"
30
+ };
31
+ var PAIN_FACE_LABELS = [
32
+ { score: 0, label: "No Hurt", color: "#22c55e" },
33
+ { score: 2, label: "Hurts Little Bit", color: "#84cc16" },
34
+ { score: 4, label: "Hurts Little More", color: "#eab308" },
35
+ { score: 6, label: "Hurts Even More", color: "#f97316" },
36
+ { score: 8, label: "Hurts Whole Lot", color: "#ef4444" },
37
+ { score: 10, label: "Hurts Worst", color: "#dc2626" }
38
+ ];
39
+ function getPainLabel(score) {
40
+ const face = PAIN_FACE_LABELS.reduce(
41
+ (closest, f) => closest == null || Math.abs(f.score - score) < Math.abs(closest.score - score) ? f : closest,
42
+ void 0
43
+ );
44
+ return face;
45
+ }
46
+ var SEVERITY_COLORS = {
47
+ mild: "#eab308",
48
+ moderate: "#f97316",
49
+ severe: "#ef4444",
50
+ "life-threatening": "#991b1b"
51
+ };
52
+ var BMI_CATEGORIES = [
53
+ { label: "Underweight", max: 18.5, color: "#3b82f6" },
54
+ { label: "Normal", max: 25, color: "#22c55e" },
55
+ { label: "Overweight", max: 30, color: "#eab308" },
56
+ { label: "Obese", max: Infinity, color: "#ef4444" }
57
+ ];
58
+ function getBmiCategory(bmi) {
59
+ return BMI_CATEGORIES.find((c) => bmi < c.max) ?? BMI_CATEGORIES[BMI_CATEGORIES.length - 1];
60
+ }
61
+ var INSTRUMENT_DISPLAY_NAMES = {
62
+ phq9: "PHQ-9",
63
+ phq2: "PHQ-2",
64
+ gad7: "GAD-7",
65
+ gad2: "GAD-2",
66
+ "audit-c": "AUDIT-C",
67
+ dast10: "DAST-10",
68
+ cssrs: "C-SSRS",
69
+ epds: "EPDS",
70
+ isi: "ISI",
71
+ pcl5: "PCL-5",
72
+ psc17: "PSC-17"
73
+ };
74
+ var SCORE_THRESHOLDS = {
75
+ phq9: [
76
+ { min: 0, max: 4, label: "Minimal", color: "#22c55e" },
77
+ { min: 5, max: 9, label: "Mild", color: "#eab308" },
78
+ { min: 10, max: 14, label: "Moderate", color: "#f97316" },
79
+ { min: 15, max: 19, label: "Moderately Severe", color: "#ef4444" },
80
+ { min: 20, max: 27, label: "Severe", color: "#dc2626" }
81
+ ],
82
+ phq2: [
83
+ { min: 0, max: 2, label: "Negative Screen", color: "#22c55e" },
84
+ { min: 3, max: 6, label: "Positive Screen", color: "#ef4444" }
85
+ ],
86
+ gad7: [
87
+ { min: 0, max: 4, label: "Minimal", color: "#22c55e" },
88
+ { min: 5, max: 9, label: "Mild", color: "#eab308" },
89
+ { min: 10, max: 14, label: "Moderate", color: "#f97316" },
90
+ { min: 15, max: 21, label: "Severe", color: "#ef4444" }
91
+ ],
92
+ gad2: [
93
+ { min: 0, max: 2, label: "Negative Screen", color: "#22c55e" },
94
+ { min: 3, max: 6, label: "Positive Screen", color: "#ef4444" }
95
+ ],
96
+ "audit-c": [
97
+ { min: 0, max: 3, label: "Low Risk", color: "#22c55e" },
98
+ { min: 4, max: 7, label: "At Risk", color: "#eab308" },
99
+ { min: 8, max: 12, label: "High Risk", color: "#ef4444" }
100
+ ],
101
+ dast10: [
102
+ { min: 0, max: 0, label: "No Problems", color: "#22c55e" },
103
+ { min: 1, max: 2, label: "Low Level", color: "#eab308" },
104
+ { min: 3, max: 5, label: "Moderate Level", color: "#f97316" },
105
+ { min: 6, max: 8, label: "Substantial Level", color: "#ef4444" },
106
+ { min: 9, max: 10, label: "Severe Level", color: "#dc2626" }
107
+ ],
108
+ cssrs: [
109
+ { min: 0, max: 0, label: "No Risk Identified", color: "#22c55e" },
110
+ { min: 1, max: 1, label: "Wish to be Dead", color: "#eab308" },
111
+ { min: 2, max: 2, label: "Non-specific Active Suicidal Thoughts", color: "#f97316" },
112
+ { min: 3, max: 3, label: "Active Suicidal Ideation", color: "#ef4444" },
113
+ { min: 4, max: 4, label: "Active Ideation with Intent", color: "#dc2626" },
114
+ { min: 5, max: 5, label: "Active Ideation with Plan", color: "#991b1b" },
115
+ { min: 6, max: 6, label: "Imminent Risk", color: "#7f1d1d" }
116
+ ],
117
+ epds: [
118
+ { min: 0, max: 8, label: "Low Likelihood", color: "#22c55e" },
119
+ { min: 9, max: 11, label: "Possible Depression", color: "#eab308" },
120
+ { min: 12, max: 13, label: "Fairly High Possibility", color: "#f97316" },
121
+ { min: 14, max: 30, label: "Probable Depression", color: "#ef4444" }
122
+ ],
123
+ isi: [
124
+ { min: 0, max: 7, label: "No Clinically Significant Insomnia", color: "#22c55e" },
125
+ { min: 8, max: 14, label: "Subthreshold Insomnia", color: "#eab308" },
126
+ { min: 15, max: 21, label: "Clinical Insomnia (Moderate)", color: "#f97316" },
127
+ { min: 22, max: 28, label: "Clinical Insomnia (Severe)", color: "#ef4444" }
128
+ ],
129
+ pcl5: [
130
+ { min: 0, max: 30, label: "Below Threshold", color: "#22c55e" },
131
+ { min: 31, max: 80, label: "Probable PTSD", color: "#ef4444" }
132
+ ],
133
+ psc17: [
134
+ { min: 0, max: 14, label: "No Significant Concerns", color: "#22c55e" },
135
+ { min: 15, max: 34, label: "Significant Concerns", color: "#ef4444" }
136
+ ]
137
+ };
138
+ function getScoreSeverity(instrumentKey, score) {
139
+ const thresholds = SCORE_THRESHOLDS[instrumentKey];
140
+ if (!thresholds) return void 0;
141
+ return thresholds.find((t) => score >= t.min && score <= t.max);
142
+ }
143
+ function ResponseTable({ schema, responses, onRowClick }) {
144
+ const questions = getAllQuestions(schema);
145
+ return /* @__PURE__ */ jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-[13px]", children: [
146
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "bg-muted", children: [
147
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-semibold text-foreground border-b-2 border-border whitespace-nowrap", children: "Submitted" }),
148
+ questions.map((q) => /* @__PURE__ */ jsx(
149
+ "th",
150
+ {
151
+ className: "px-3 py-2 text-left font-semibold text-foreground border-b-2 border-border whitespace-nowrap",
152
+ children: q.label
153
+ },
154
+ q.id
155
+ )),
156
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-semibold text-foreground border-b-2 border-border whitespace-nowrap", children: "Score" })
157
+ ] }) }),
158
+ /* @__PURE__ */ jsxs("tbody", { children: [
159
+ responses.map((response, idx) => /* @__PURE__ */ jsxs(
160
+ "tr",
161
+ {
162
+ onClick: () => onRowClick?.(response),
163
+ className: onRowClick ? "cursor-pointer border-b border-border hover:bg-accent transition-colors" : "border-b border-border",
164
+ children: [
165
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground max-w-50 overflow-hidden text-ellipsis whitespace-nowrap", children: new Date(response.submittedAt).toLocaleString() }),
166
+ questions.map((q) => /* @__PURE__ */ jsx(
167
+ "td",
168
+ {
169
+ className: "px-3 py-2 text-foreground max-w-50 overflow-hidden text-ellipsis whitespace-nowrap",
170
+ children: formatCellValue(response.values[q.id], q.type)
171
+ },
172
+ q.id
173
+ )),
174
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground max-w-50 overflow-hidden text-ellipsis whitespace-nowrap", children: response.totalScore ?? "\u2014" })
175
+ ]
176
+ },
177
+ response.sessionToken || idx
178
+ )),
179
+ responses.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
180
+ "td",
181
+ {
182
+ colSpan: questions.length + 2,
183
+ className: "px-3 py-8 text-center text-muted-foreground",
184
+ children: "No responses yet"
185
+ }
186
+ ) })
187
+ ] })
188
+ ] }) });
189
+ }
190
+ function getAllQuestions(schema) {
191
+ const questions = [];
192
+ for (const section of schema.sections) {
193
+ for (const q of section.questions) {
194
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
195
+ continue;
196
+ }
197
+ questions.push(q);
198
+ }
199
+ }
200
+ return questions;
201
+ }
202
+ function formatCellValue(value, type) {
203
+ if (value == null) return "\u2014";
204
+ if (type) {
205
+ switch (type) {
206
+ case "vitals_entry": {
207
+ if (typeof value !== "object" || value === null) break;
208
+ const v = value;
209
+ const parts = [];
210
+ if (v.systolicBp && v.diastolicBp) parts.push(`BP ${v.systolicBp}/${v.diastolicBp}`);
211
+ if (v.heartRate) parts.push(`HR ${v.heartRate}`);
212
+ if (v.temperature) parts.push(`${v.temperature}\xB0F`);
213
+ if (v.oxygenSaturation) parts.push(`SpO\u2082 ${v.oxygenSaturation}%`);
214
+ return parts.length > 0 ? parts.join(", ") : "\u2014";
215
+ }
216
+ case "medication_list":
217
+ if (Array.isArray(value)) return `${value.length} medication${value.length !== 1 ? "s" : ""}`;
218
+ break;
219
+ case "allergy_list":
220
+ if (Array.isArray(value)) return `${value.length} allerg${value.length !== 1 ? "ies" : "y"}`;
221
+ break;
222
+ case "body_diagram":
223
+ if (Array.isArray(value)) {
224
+ const labels = value.map((id) => BODY_REGION_LABELS[id] ?? id);
225
+ return labels.join(", ");
226
+ }
227
+ break;
228
+ case "pain_scale":
229
+ if (typeof value === "number") return `${value}/10`;
230
+ break;
231
+ case "bmi_calculator": {
232
+ if (typeof value !== "object" || value === null) break;
233
+ const d = value;
234
+ if (d.bmi != null) return `BMI ${d.bmi}`;
235
+ break;
236
+ }
237
+ case "payment": {
238
+ if (typeof value !== "object" || value === null) break;
239
+ const p = value;
240
+ const status = p.status;
241
+ return status ? status.charAt(0).toUpperCase() + status.slice(1) : "\u2014";
242
+ }
243
+ case "insurance_card": {
244
+ if (typeof value !== "object" || value === null) break;
245
+ const ins = value;
246
+ const parts = [ins.carrierId, ins.planName].filter(Boolean);
247
+ return parts.length > 0 ? parts.join(" \u2014 ") : "Card uploaded";
248
+ }
249
+ case "legal_name": {
250
+ if (typeof value !== "object" || value === null) break;
251
+ const n = value;
252
+ return [n.first, n.last].filter(Boolean).join(" ") || "\u2014";
253
+ }
254
+ case "address": {
255
+ if (typeof value !== "object" || value === null) break;
256
+ const a = value;
257
+ return [a.city, a.state].filter(Boolean).join(", ") || "\u2014";
258
+ }
259
+ case "consent":
260
+ return value === true || value === "true" || value === "agreed" ? "Agreed" : "Not agreed";
261
+ case "signature":
262
+ return typeof value === "string" && value.startsWith("data:image") ? "Signed" : "\u2014";
263
+ }
264
+ }
265
+ if (typeof value === "boolean") return value ? "Yes" : "No";
266
+ if (Array.isArray(value)) return value.join(", ");
267
+ if (typeof value === "object") return JSON.stringify(value);
268
+ return String(value);
269
+ }
270
+ function ResponseCard({ response, fields, onClick }) {
271
+ return /* @__PURE__ */ jsxs(
272
+ "div",
273
+ {
274
+ onClick,
275
+ className: onClick ? "border border-border rounded-lg p-4 bg-card cursor-pointer transition-shadow hover:shadow-md" : "border border-border rounded-lg p-4 bg-card",
276
+ children: [
277
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between mb-3 text-xs text-muted-foreground", children: [
278
+ /* @__PURE__ */ jsx("span", { children: new Date(response.submittedAt).toLocaleString() }),
279
+ response.completionTimeMs != null && /* @__PURE__ */ jsxs("span", { children: [
280
+ Math.round(response.completionTimeMs / 1e3),
281
+ "s"
282
+ ] })
283
+ ] }),
284
+ fields.slice(0, 4).map((field) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
285
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground mb-0.5", children: field.label }),
286
+ /* @__PURE__ */ jsx("div", { className: "text-[13px] text-foreground", children: formatCardValue(field.value, field.type) })
287
+ ] }, field.questionId)),
288
+ fields.length > 4 && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mt-2", children: [
289
+ "+",
290
+ fields.length - 4,
291
+ " more fields"
292
+ ] }),
293
+ response.totalScore != null && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-2 border-t border-border text-[13px] font-semibold text-foreground", children: [
294
+ "Score: ",
295
+ response.totalScore
296
+ ] })
297
+ ]
298
+ }
299
+ );
300
+ }
301
+ function formatCardValue(value, type) {
302
+ if (value == null) return "\u2014";
303
+ if (type) {
304
+ switch (type) {
305
+ case "vitals_entry": {
306
+ if (typeof value !== "object" || value === null) break;
307
+ const v = value;
308
+ const parts = [];
309
+ if (v.systolicBp && v.diastolicBp) parts.push(`BP ${v.systolicBp}/${v.diastolicBp}`);
310
+ if (v.heartRate) parts.push(`HR ${v.heartRate}`);
311
+ if (v.temperature) parts.push(`${v.temperature}\xB0F`);
312
+ if (v.oxygenSaturation) parts.push(`SpO\u2082 ${v.oxygenSaturation}%`);
313
+ return parts.length > 0 ? parts.join(", ") : "\u2014";
314
+ }
315
+ case "medication_list":
316
+ if (Array.isArray(value)) return `${value.length} medication${value.length !== 1 ? "s" : ""}`;
317
+ break;
318
+ case "allergy_list":
319
+ if (Array.isArray(value)) return `${value.length} allerg${value.length !== 1 ? "ies" : "y"}`;
320
+ break;
321
+ case "body_diagram":
322
+ if (Array.isArray(value)) {
323
+ const labels = value.map((id) => BODY_REGION_LABELS[id] ?? id);
324
+ return labels.join(", ");
325
+ }
326
+ break;
327
+ case "pain_scale":
328
+ if (typeof value === "number") return `${value}/10`;
329
+ break;
330
+ case "bmi_calculator": {
331
+ if (typeof value !== "object" || value === null) break;
332
+ const d = value;
333
+ if (d.bmi != null) return `BMI ${d.bmi}`;
334
+ break;
335
+ }
336
+ case "payment": {
337
+ if (typeof value !== "object" || value === null) break;
338
+ const p = value;
339
+ const status = p.status;
340
+ return status ? status.charAt(0).toUpperCase() + status.slice(1) : "\u2014";
341
+ }
342
+ case "insurance_card": {
343
+ if (typeof value !== "object" || value === null) break;
344
+ const ins = value;
345
+ const parts = [ins.carrierId, ins.planName].filter(Boolean);
346
+ return parts.length > 0 ? parts.join(" \u2014 ") : "Card uploaded";
347
+ }
348
+ case "legal_name": {
349
+ if (typeof value !== "object" || value === null) break;
350
+ const n = value;
351
+ return [n.first, n.last].filter(Boolean).join(" ") || "\u2014";
352
+ }
353
+ case "address": {
354
+ if (typeof value !== "object" || value === null) break;
355
+ const a = value;
356
+ return [a.city, a.state].filter(Boolean).join(", ") || "\u2014";
357
+ }
358
+ case "consent":
359
+ return value === true || value === "true" || value === "agreed" ? "Agreed" : "Not agreed";
360
+ case "signature":
361
+ return typeof value === "string" && value.startsWith("data:image") ? "Signed" : "\u2014";
362
+ }
363
+ }
364
+ if (typeof value === "boolean") return value ? "Yes" : "No";
365
+ if (Array.isArray(value)) return value.map(String).join(", ");
366
+ if (typeof value === "object") return JSON.stringify(value);
367
+ return String(value);
368
+ }
369
+ function ResponseDetail({ response, fields, onBack }) {
370
+ return /* @__PURE__ */ jsxs("div", { children: [
371
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-5 pb-3 border-b border-border", children: [
372
+ onBack && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onBack, children: "Back" }),
373
+ /* @__PURE__ */ jsxs("div", { children: [
374
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground", children: "Response Detail" }),
375
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
376
+ "Submitted ",
377
+ new Date(response.submittedAt).toLocaleString(),
378
+ response.completionTimeMs != null && ` \u2014 ${Math.round(response.completionTimeMs / 1e3)}s`
379
+ ] })
380
+ ] })
381
+ ] }),
382
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: fields.map((field) => /* @__PURE__ */ jsxs(
383
+ "div",
384
+ {
385
+ className: "p-3 bg-muted rounded-md",
386
+ children: [
387
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mb-1", children: [
388
+ field.label,
389
+ /* @__PURE__ */ jsxs("span", { className: "ml-2 text-[11px] text-muted-foreground", children: [
390
+ "(",
391
+ field.type,
392
+ ")"
393
+ ] })
394
+ ] }),
395
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground", children: renderFieldValue(field) })
396
+ ]
397
+ },
398
+ field.questionId
399
+ )) }),
400
+ response.scores && Object.keys(response.scores).length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-5", children: [
401
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground mb-3", children: "Scores" }),
402
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 flex-wrap", children: [
403
+ Object.entries(response.scores).map(([key, value]) => {
404
+ const displayName = INSTRUMENT_DISPLAY_NAMES[key] ?? key;
405
+ const severity = typeof value === "number" ? getScoreSeverity(key, value) : void 0;
406
+ return /* @__PURE__ */ jsxs(
407
+ "div",
408
+ {
409
+ className: "px-4 py-2 bg-accent rounded-md text-[13px] flex items-center gap-2",
410
+ children: [
411
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
412
+ displayName,
413
+ ": "
414
+ ] }),
415
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-primary", children: value }),
416
+ severity && /* @__PURE__ */ jsx(
417
+ "span",
418
+ {
419
+ className: "text-[11px] font-semibold px-2 py-0.5 rounded-full text-white",
420
+ style: { backgroundColor: severity.color },
421
+ children: severity.label
422
+ }
423
+ )
424
+ ]
425
+ },
426
+ key
427
+ );
428
+ }),
429
+ response.totalScore != null && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-accent rounded-md text-[13px]", children: [
430
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Total: " }),
431
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-primary", children: response.totalScore })
432
+ ] })
433
+ ] })
434
+ ] }),
435
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 pt-3 border-t border-border text-xs text-muted-foreground", children: [
436
+ /* @__PURE__ */ jsxs("div", { children: [
437
+ "Schema: ",
438
+ response.schemaId,
439
+ " (v",
440
+ response.schemaVersion,
441
+ ")"
442
+ ] }),
443
+ /* @__PURE__ */ jsxs("div", { children: [
444
+ "Session: ",
445
+ response.sessionToken
446
+ ] })
447
+ ] })
448
+ ] });
449
+ }
450
+ function renderFieldValue(field) {
451
+ const { type, value } = field;
452
+ if (value == null) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
453
+ switch (type) {
454
+ case "vitals_entry":
455
+ return renderVitals(value);
456
+ case "medication_list":
457
+ return renderMedications(value);
458
+ case "allergy_list":
459
+ return renderAllergies(value);
460
+ case "bmi_calculator":
461
+ return renderBmi(value);
462
+ case "body_diagram":
463
+ return renderBodyDiagram(value);
464
+ case "pain_scale":
465
+ return renderPainScale(value);
466
+ case "insurance_card":
467
+ return renderInsurance(value);
468
+ case "payment":
469
+ return renderPayment(value);
470
+ case "legal_name":
471
+ return renderLegalName(value);
472
+ case "address":
473
+ return renderAddress(value);
474
+ case "consent":
475
+ return renderConsent(value);
476
+ case "signature":
477
+ return renderSignature(value);
478
+ default:
479
+ return /* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap", children: formatFallbackValue(value) });
480
+ }
481
+ }
482
+ var VITALS_LABELS = {
483
+ systolicBp: { label: "Systolic BP", unit: "mmHg", normalRange: "90-140" },
484
+ diastolicBp: { label: "Diastolic BP", unit: "mmHg", normalRange: "60-90" },
485
+ heartRate: { label: "Heart Rate", unit: "bpm", normalRange: "60-100" },
486
+ temperature: { label: "Temperature", unit: "\xB0F", normalRange: "97.0-99.5" },
487
+ respiratoryRate: { label: "Respiratory Rate", unit: "/min", normalRange: "12-20" },
488
+ oxygenSaturation: { label: "SpO\u2082", unit: "%", normalRange: "95-100" }
489
+ };
490
+ function renderVitals(value) {
491
+ if (typeof value !== "object" || value === null) return formatFallbackValue(value);
492
+ const vitals = value;
493
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2", children: Object.entries(VITALS_LABELS).map(([key, meta]) => {
494
+ const val = vitals[key];
495
+ if (val == null || val === 0) return null;
496
+ return /* @__PURE__ */ jsxs("div", { className: "text-[13px]", children: [
497
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
498
+ meta.label,
499
+ ": "
500
+ ] }),
501
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
502
+ String(val),
503
+ " ",
504
+ meta.unit
505
+ ] }),
506
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground ml-1", children: [
507
+ "(",
508
+ meta.normalRange,
509
+ ")"
510
+ ] })
511
+ ] }, key);
512
+ }) });
513
+ }
514
+ function renderMedications(value) {
515
+ if (!Array.isArray(value) || value.length === 0) return "No medications";
516
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: value.map((med, idx) => {
517
+ const m = med;
518
+ const parts = [m.dosage, m.frequency, m.route].filter(Boolean);
519
+ return /* @__PURE__ */ jsxs(
520
+ "div",
521
+ {
522
+ className: "px-3 py-2 border border-border rounded-md text-[13px]",
523
+ children: [
524
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: m.name || "Unnamed" }),
525
+ parts.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
526
+ " \u2014 ",
527
+ parts.join(" \xB7 ")
528
+ ] }),
529
+ m.notes && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: m.notes })
530
+ ]
531
+ },
532
+ idx
533
+ );
534
+ }) });
535
+ }
536
+ function renderAllergies(value) {
537
+ if (!Array.isArray(value) || value.length === 0) return "No allergies";
538
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: value.map((allergy, idx) => {
539
+ const a = allergy;
540
+ const sevColor = SEVERITY_COLORS[a.severity];
541
+ return /* @__PURE__ */ jsxs(
542
+ "div",
543
+ {
544
+ className: "px-3 py-2 border border-border rounded-md text-[13px] flex items-center gap-2 flex-wrap",
545
+ children: [
546
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: a.allergen || "Unknown" }),
547
+ a.type && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
548
+ "(",
549
+ a.type,
550
+ ")"
551
+ ] }),
552
+ a.severity && /* @__PURE__ */ jsx(
553
+ "span",
554
+ {
555
+ className: "text-[11px] font-semibold px-2 py-0.5 rounded-full",
556
+ style: {
557
+ backgroundColor: sevColor ?? "var(--accent)",
558
+ color: sevColor ? "#fff" : "var(--foreground)"
559
+ },
560
+ children: a.severity
561
+ }
562
+ ),
563
+ a.reaction && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
564
+ "\u2014 ",
565
+ a.reaction
566
+ ] })
567
+ ]
568
+ },
569
+ idx
570
+ );
571
+ }) });
572
+ }
573
+ function renderBmi(value) {
574
+ if (typeof value !== "object" || value === null) return formatFallbackValue(value);
575
+ const data = value;
576
+ const bmi = data.bmi;
577
+ const unit = data.unit ?? "imperial";
578
+ const height = data.height;
579
+ const weight = data.weight;
580
+ const heightUnit = unit === "metric" ? "cm" : "in";
581
+ const weightUnit = unit === "metric" ? "kg" : "lbs";
582
+ const category = bmi != null ? getBmiCategory(bmi) : void 0;
583
+ return /* @__PURE__ */ jsxs("div", { className: "text-[13px] flex items-center gap-2 flex-wrap", children: [
584
+ height != null && /* @__PURE__ */ jsxs("span", { children: [
585
+ "Height: ",
586
+ height,
587
+ " ",
588
+ heightUnit
589
+ ] }),
590
+ height != null && weight != null && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
591
+ weight != null && /* @__PURE__ */ jsxs("span", { children: [
592
+ "Weight: ",
593
+ weight,
594
+ " ",
595
+ weightUnit
596
+ ] }),
597
+ bmi != null && /* @__PURE__ */ jsxs(Fragment, { children: [
598
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" }),
599
+ /* @__PURE__ */ jsxs("span", { className: "font-semibold", children: [
600
+ "BMI: ",
601
+ bmi
602
+ ] }),
603
+ category && /* @__PURE__ */ jsx(
604
+ "span",
605
+ {
606
+ className: "text-[11px] font-semibold px-2 py-0.5 rounded-full text-white",
607
+ style: { backgroundColor: category.color },
608
+ children: category.label
609
+ }
610
+ )
611
+ ] })
612
+ ] });
613
+ }
614
+ function renderBodyDiagram(value) {
615
+ if (!Array.isArray(value) || value.length === 0) return "No regions selected";
616
+ const labels = value.map((id) => BODY_REGION_LABELS[id] ?? id);
617
+ return /* @__PURE__ */ jsx("span", { children: labels.join(", ") });
618
+ }
619
+ function renderPainScale(value) {
620
+ if (typeof value !== "number") return formatFallbackValue(value);
621
+ const face = getPainLabel(value);
622
+ return /* @__PURE__ */ jsxs("span", { className: "font-semibold", style: { color: face?.color }, children: [
623
+ value,
624
+ "/10",
625
+ face ? ` \u2014 ${face.label}` : ""
626
+ ] });
627
+ }
628
+ function renderInsurance(value) {
629
+ if (typeof value !== "object" || value === null) return formatFallbackValue(value);
630
+ const ins = value;
631
+ const textFields = [
632
+ { key: "carrierId", label: "Carrier" },
633
+ { key: "planName", label: "Plan" },
634
+ { key: "memberId", label: "Member ID" },
635
+ { key: "groupNumber", label: "Group #" },
636
+ { key: "subscriberName", label: "Subscriber" },
637
+ { key: "relationship", label: "Relationship" }
638
+ ];
639
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
640
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-3 gap-y-1 text-[13px]", children: textFields.map(({ key, label }) => {
641
+ const val = ins[key];
642
+ if (!val) return null;
643
+ return /* @__PURE__ */ jsxs("div", { children: [
644
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
645
+ label,
646
+ ": "
647
+ ] }),
648
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: String(val) })
649
+ ] }, key);
650
+ }) }),
651
+ !!(ins.frontImage || ins.backImage) && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 mt-1", children: [
652
+ typeof ins.frontImage === "string" && /* @__PURE__ */ jsx(
653
+ "img",
654
+ {
655
+ src: ins.frontImage,
656
+ alt: "Front of card",
657
+ className: "h-12 rounded border border-border"
658
+ }
659
+ ),
660
+ typeof ins.backImage === "string" && /* @__PURE__ */ jsx(
661
+ "img",
662
+ {
663
+ src: ins.backImage,
664
+ alt: "Back of card",
665
+ className: "h-12 rounded border border-border"
666
+ }
667
+ )
668
+ ] })
669
+ ] });
670
+ }
671
+ function renderPayment(value) {
672
+ if (typeof value !== "object" || value === null) return formatFallbackValue(value);
673
+ const payment = value;
674
+ const status = payment.status;
675
+ const statusConfig = {
676
+ succeeded: { bg: "#22c55e", label: "Succeeded" },
677
+ failed: { bg: "#ef4444", label: "Failed" },
678
+ processing: { bg: "#eab308", label: "Processing" }
679
+ };
680
+ const cfg = statusConfig[status];
681
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-[13px]", children: [
682
+ /* @__PURE__ */ jsx(
683
+ "span",
684
+ {
685
+ className: "text-[11px] font-semibold px-2.5 py-0.5 rounded-full text-white",
686
+ style: { backgroundColor: cfg?.bg ?? "var(--accent)" },
687
+ children: cfg?.label ?? status
688
+ }
689
+ ),
690
+ !!payment.chargeId && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: String(payment.chargeId) }),
691
+ !!payment.error && /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", children: String(payment.error) })
692
+ ] });
693
+ }
694
+ function renderLegalName(value) {
695
+ if (typeof value !== "object" || value === null) return formatFallbackValue(value);
696
+ const name = value;
697
+ const parts = [name.first, name.middle, name.last].filter(Boolean);
698
+ return /* @__PURE__ */ jsx("span", { className: "font-medium", children: parts.join(" ") || "\u2014" });
699
+ }
700
+ function renderAddress(value) {
701
+ if (typeof value !== "object" || value === null) return formatFallbackValue(value);
702
+ const addr = value;
703
+ const line1 = [addr.street, addr.street2].filter(Boolean).join(", ");
704
+ const line2 = [addr.city, addr.state, addr.zip].filter(Boolean).join(", ");
705
+ const full = [line1, line2].filter(Boolean).join(", ");
706
+ return /* @__PURE__ */ jsx("span", { children: full || "\u2014" });
707
+ }
708
+ function renderConsent(value) {
709
+ const agreed = value === true || value === "true" || value === "agreed";
710
+ return /* @__PURE__ */ jsx("span", { className: agreed ? "font-semibold text-green-500" : "font-semibold text-muted-foreground", children: agreed ? "Agreed" : "Not agreed" });
711
+ }
712
+ function renderSignature(value) {
713
+ if (typeof value !== "string") return formatFallbackValue(value);
714
+ if (value.startsWith("data:image")) {
715
+ return /* @__PURE__ */ jsx(
716
+ "img",
717
+ {
718
+ src: value,
719
+ alt: "Signature",
720
+ className: "h-15 border border-border rounded"
721
+ }
722
+ );
723
+ }
724
+ return /* @__PURE__ */ jsx("span", { children: value });
725
+ }
726
+ function formatFallbackValue(value) {
727
+ if (value == null) return "\u2014";
728
+ if (typeof value === "boolean") return value ? "Yes" : "No";
729
+ if (Array.isArray(value)) return value.map(formatFallbackValue).join(", ");
730
+ if (typeof value === "object") return JSON.stringify(value, null, 2);
731
+ return String(value);
732
+ }
733
+
734
+ // src/response-viewer/export-utils.ts
735
+ function exportToCsv(schema, responses, filename = "responses.csv") {
736
+ const questions = getExportableQuestions(schema);
737
+ const headers = ["Submitted", ...questions.map((q) => q.label), "Score"];
738
+ const rows = responses.map((r) => {
739
+ const submitted = new Date(r.submittedAt).toLocaleString();
740
+ const values = questions.map((q) => formatExportValue(r.values[q.id], q.type));
741
+ const score = r.totalScore != null ? String(r.totalScore) : "";
742
+ return [submitted, ...values, score];
743
+ });
744
+ const csvContent = [headers, ...rows].map((row) => row.map(escapeCsvField).join(",")).join("\n");
745
+ downloadBlob(csvContent, filename, "text/csv;charset=utf-8;");
746
+ }
747
+ function exportToJson(responses, filename = "responses.json") {
748
+ const content = JSON.stringify(responses, null, 2);
749
+ downloadBlob(content, filename, "application/json;charset=utf-8;");
750
+ }
751
+ function getExportableQuestions(schema) {
752
+ const questions = [];
753
+ for (const section of schema.sections) {
754
+ for (const q of section.questions) {
755
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
756
+ continue;
757
+ }
758
+ questions.push(q);
759
+ }
760
+ }
761
+ return questions;
762
+ }
763
+ function formatExportValue(value, type) {
764
+ if (value == null) return "";
765
+ switch (type) {
766
+ case "vitals_entry": {
767
+ if (typeof value !== "object" || value === null) break;
768
+ const v = value;
769
+ const parts = [];
770
+ if (v.systolicBp && v.diastolicBp) parts.push(`BP ${v.systolicBp}/${v.diastolicBp}`);
771
+ if (v.heartRate) parts.push(`HR ${v.heartRate}`);
772
+ if (v.temperature) parts.push(`${v.temperature}\xB0F`);
773
+ if (v.oxygenSaturation) parts.push(`SpO2 ${v.oxygenSaturation}%`);
774
+ return parts.join("; ");
775
+ }
776
+ case "medication_list":
777
+ if (Array.isArray(value)) {
778
+ return value.map((m) => [m.name, m.dosage, m.frequency].filter(Boolean).join(" ")).join("; ");
779
+ }
780
+ break;
781
+ case "allergy_list":
782
+ if (Array.isArray(value)) {
783
+ return value.map((a) => [a.allergen, a.severity].filter(Boolean).join(" - ")).join("; ");
784
+ }
785
+ break;
786
+ case "body_diagram":
787
+ if (Array.isArray(value)) {
788
+ return value.map((id) => BODY_REGION_LABELS[id] ?? id).join("; ");
789
+ }
790
+ break;
791
+ case "pain_scale":
792
+ return typeof value === "number" ? `${value}/10` : String(value);
793
+ case "bmi_calculator": {
794
+ if (typeof value !== "object" || value === null) break;
795
+ const d = value;
796
+ return d.bmi != null ? `BMI ${d.bmi}` : "";
797
+ }
798
+ case "payment": {
799
+ if (typeof value !== "object" || value === null) break;
800
+ return value.status ?? "";
801
+ }
802
+ case "legal_name": {
803
+ if (typeof value !== "object" || value === null) break;
804
+ const n = value;
805
+ return [n.first, n.middle, n.last].filter(Boolean).join(" ");
806
+ }
807
+ case "address": {
808
+ if (typeof value !== "object" || value === null) break;
809
+ const a = value;
810
+ return [a.street, a.city, a.state, a.zip].filter(Boolean).join(", ");
811
+ }
812
+ case "consent":
813
+ return value === true || value === "true" || value === "agreed" ? "Agreed" : "Not agreed";
814
+ case "signature":
815
+ return typeof value === "string" && value.startsWith("data:image") ? "Signed" : "";
816
+ }
817
+ if (typeof value === "boolean") return value ? "Yes" : "No";
818
+ if (Array.isArray(value)) return value.join("; ");
819
+ if (typeof value === "object") return JSON.stringify(value);
820
+ return String(value);
821
+ }
822
+ function escapeCsvField(field) {
823
+ if (field.includes(",") || field.includes('"') || field.includes("\n")) {
824
+ return `"${field.replace(/"/g, '""')}"`;
825
+ }
826
+ return field;
827
+ }
828
+ function downloadBlob(content, filename, mimeType) {
829
+ const blob = new Blob([content], { type: mimeType });
830
+ const url = URL.createObjectURL(blob);
831
+ const link = document.createElement("a");
832
+ link.href = url;
833
+ link.download = filename;
834
+ document.body.appendChild(link);
835
+ link.click();
836
+ document.body.removeChild(link);
837
+ URL.revokeObjectURL(url);
838
+ }
839
+ function ResponseViewerInner({
840
+ schema,
841
+ responses,
842
+ onResponseSelect,
843
+ height = "500px",
844
+ width = "100%"
845
+ }) {
846
+ const [viewMode, setViewMode] = useState("table");
847
+ const [selectedResponse, setSelectedResponse] = useState(null);
848
+ function handleSelect(response) {
849
+ setSelectedResponse(response);
850
+ onResponseSelect?.(response);
851
+ }
852
+ function handleBack() {
853
+ setSelectedResponse(null);
854
+ }
855
+ const questions = getAllQuestions2(schema);
856
+ function getFields(response) {
857
+ return questions.map((q) => ({
858
+ questionId: q.id,
859
+ label: q.label,
860
+ type: q.type,
861
+ value: response.values[q.id]
862
+ }));
863
+ }
864
+ const heightValue = typeof height === "number" ? `${height}px` : height;
865
+ const widthValue = typeof width === "number" ? `${width}px` : width;
866
+ return /* @__PURE__ */ jsxs(
867
+ "div",
868
+ {
869
+ className: "flex flex-col border border-border rounded-lg overflow-hidden bg-background text-foreground",
870
+ style: { height: heightValue, width: widthValue },
871
+ children: [
872
+ !selectedResponse && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center px-3 py-2 border-b border-border", children: [
873
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
874
+ responses.length,
875
+ " response",
876
+ responses.length !== 1 ? "s" : ""
877
+ ] }),
878
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
879
+ /* @__PURE__ */ jsx(
880
+ Button,
881
+ {
882
+ variant: viewMode === "table" ? "secondary" : "ghost",
883
+ size: "sm",
884
+ className: cn(
885
+ "h-7 text-xs",
886
+ viewMode === "table" && "font-semibold border border-ring"
887
+ ),
888
+ onClick: () => setViewMode("table"),
889
+ children: "Table"
890
+ }
891
+ ),
892
+ /* @__PURE__ */ jsx(
893
+ Button,
894
+ {
895
+ variant: viewMode === "card" ? "secondary" : "ghost",
896
+ size: "sm",
897
+ className: cn(
898
+ "h-7 text-xs",
899
+ viewMode === "card" && "font-semibold border border-ring"
900
+ ),
901
+ onClick: () => setViewMode("card"),
902
+ children: "Cards"
903
+ }
904
+ ),
905
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-5 mx-1" }),
906
+ /* @__PURE__ */ jsx(
907
+ Button,
908
+ {
909
+ variant: "outline",
910
+ size: "sm",
911
+ className: "h-7 text-xs",
912
+ onClick: () => exportToCsv(schema, responses),
913
+ disabled: responses.length === 0,
914
+ children: "Export CSV"
915
+ }
916
+ ),
917
+ /* @__PURE__ */ jsx(
918
+ Button,
919
+ {
920
+ variant: "outline",
921
+ size: "sm",
922
+ className: "h-7 text-xs",
923
+ onClick: () => exportToJson(responses),
924
+ disabled: responses.length === 0,
925
+ children: "Export JSON"
926
+ }
927
+ )
928
+ ] })
929
+ ] }),
930
+ /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsx(
931
+ ResponseDetail,
932
+ {
933
+ response: selectedResponse,
934
+ fields: getFields(selectedResponse),
935
+ onBack: handleBack
936
+ }
937
+ ) : viewMode === "table" ? /* @__PURE__ */ jsx(
938
+ ResponseTable,
939
+ {
940
+ schema,
941
+ responses,
942
+ onRowClick: handleSelect
943
+ }
944
+ ) : /* @__PURE__ */ jsxs("div", { className: "grid gap-3 p-3 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]", children: [
945
+ responses.map((response, idx) => /* @__PURE__ */ jsx(
946
+ ResponseCard,
947
+ {
948
+ response,
949
+ fields: getFields(response),
950
+ onClick: () => handleSelect(response)
951
+ },
952
+ response.sessionToken || idx
953
+ )),
954
+ responses.length === 0 && /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-muted-foreground", children: "No responses yet" })
955
+ ] }) })
956
+ ]
957
+ }
958
+ );
959
+ }
960
+ function getAllQuestions2(schema) {
961
+ const questions = [];
962
+ for (const section of schema.sections) {
963
+ for (const q of section.questions) {
964
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
965
+ continue;
966
+ }
967
+ questions.push(q);
968
+ }
969
+ }
970
+ return questions;
971
+ }
972
+
973
+ // src/response-viewer/ResponseViewer.tsx
974
+ var ResponseViewer = requireLicense(ResponseViewerInner, "ResponseViewer");
975
+
976
+ export { ResponseViewer };