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