@squaredr/fieldcraft-pro 1.3.0 → 1.4.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.
@@ -1,7 +1,7 @@
1
1
  import { cn } from './chunk-QQ4JZGTD.mjs';
2
2
  import { requireLicense } from './chunk-VECQKSWS.mjs';
3
- import { useState, useEffect } from 'react';
4
- import { Button, Separator, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@squaredr/fieldcraft-react';
3
+ import { useState, useEffect, useMemo } from 'react';
4
+ import { Input, Button, Separator, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@squaredr/fieldcraft-react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
 
7
7
  // src/response-viewer/clinical-display-data.ts
@@ -140,10 +140,30 @@ function getScoreSeverity(instrumentKey, score) {
140
140
  if (!thresholds) return void 0;
141
141
  return thresholds.find((t) => score >= t.min && score <= t.max);
142
142
  }
143
- function ResponseTable({ schema, responses, onRowClick }) {
143
+ function ResponseTable({
144
+ schema,
145
+ responses,
146
+ onRowClick,
147
+ selectable,
148
+ selectedIds,
149
+ onToggleSelect,
150
+ onSelectAll
151
+ }) {
144
152
  const questions = getAllQuestions(schema);
153
+ const allPageSelected = selectable && responses.length > 0 && responses.every(
154
+ (r) => r.sessionToken && selectedIds?.has(r.sessionToken)
155
+ );
145
156
  return /* @__PURE__ */ jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-[13px]", children: [
146
157
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "bg-muted", children: [
158
+ selectable && /* @__PURE__ */ jsx("th", { className: "px-3 py-2 w-8 border-b-2 border-border", children: /* @__PURE__ */ jsx(
159
+ "input",
160
+ {
161
+ type: "checkbox",
162
+ checked: allPageSelected,
163
+ onChange: () => onSelectAll?.(),
164
+ className: "accent-primary"
165
+ }
166
+ ) }),
147
167
  /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-semibold text-foreground border-b-2 border-border whitespace-nowrap", children: "Submitted" }),
148
168
  questions.map((q) => /* @__PURE__ */ jsx(
149
169
  "th",
@@ -162,6 +182,19 @@ function ResponseTable({ schema, responses, onRowClick }) {
162
182
  onClick: () => onRowClick?.(response),
163
183
  className: onRowClick ? "cursor-pointer border-b border-border hover:bg-accent transition-colors" : "border-b border-border",
164
184
  children: [
185
+ selectable && /* @__PURE__ */ jsx("td", { className: "px-3 py-2 w-8", children: /* @__PURE__ */ jsx(
186
+ "input",
187
+ {
188
+ type: "checkbox",
189
+ checked: !!(response.sessionToken && selectedIds?.has(response.sessionToken)),
190
+ onChange: (e) => {
191
+ e.stopPropagation();
192
+ if (response.sessionToken) onToggleSelect?.(response.sessionToken);
193
+ },
194
+ onClick: (e) => e.stopPropagation(),
195
+ className: "accent-primary"
196
+ }
197
+ ) }),
165
198
  /* @__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
199
  questions.map((q) => /* @__PURE__ */ jsx(
167
200
  "td",
@@ -179,7 +212,7 @@ function ResponseTable({ schema, responses, onRowClick }) {
179
212
  responses.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
180
213
  "td",
181
214
  {
182
- colSpan: questions.length + 2,
215
+ colSpan: questions.length + 2 + (selectable ? 1 : 0),
183
216
  className: "px-3 py-8 text-center text-muted-foreground",
184
217
  children: "No responses yet"
185
218
  }
@@ -267,15 +300,37 @@ function formatCellValue(value, type) {
267
300
  if (typeof value === "object") return JSON.stringify(value);
268
301
  return String(value);
269
302
  }
270
- function ResponseCard({ response, fields, onClick }) {
303
+ function ResponseCard({
304
+ response,
305
+ fields,
306
+ onClick,
307
+ selectable,
308
+ selected,
309
+ onToggleSelect
310
+ }) {
271
311
  return /* @__PURE__ */ jsxs(
272
312
  "div",
273
313
  {
274
314
  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",
315
+ 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") + (selected ? " ring-2 ring-primary" : ""),
276
316
  children: [
277
317
  /* @__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() }),
318
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
319
+ selectable && /* @__PURE__ */ jsx(
320
+ "input",
321
+ {
322
+ type: "checkbox",
323
+ checked: !!selected,
324
+ onChange: (e) => {
325
+ e.stopPropagation();
326
+ onToggleSelect?.();
327
+ },
328
+ onClick: (e) => e.stopPropagation(),
329
+ className: "accent-primary"
330
+ }
331
+ ),
332
+ /* @__PURE__ */ jsx("span", { children: new Date(response.submittedAt).toLocaleString() })
333
+ ] }),
279
334
  response.completionTimeMs != null && /* @__PURE__ */ jsxs("span", { children: [
280
335
  Math.round(response.completionTimeMs / 1e3),
281
336
  "s"
@@ -366,18 +421,19 @@ function formatCardValue(value, type) {
366
421
  if (typeof value === "object") return JSON.stringify(value);
367
422
  return String(value);
368
423
  }
369
- function ResponseDetail({ response, fields, onBack }) {
424
+ function ResponseDetail({ response, fields, onBack, onDelete }) {
370
425
  return /* @__PURE__ */ jsxs("div", { children: [
371
426
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-5 pb-3 border-b border-border", children: [
372
427
  onBack && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onBack, children: "Back" }),
373
- /* @__PURE__ */ jsxs("div", { children: [
428
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
374
429
  /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground", children: "Response Detail" }),
375
430
  /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
376
431
  "Submitted ",
377
432
  new Date(response.submittedAt).toLocaleString(),
378
433
  response.completionTimeMs != null && ` \u2014 ${Math.round(response.completionTimeMs / 1e3)}s`
379
434
  ] })
380
- ] })
435
+ ] }),
436
+ onDelete && /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: onDelete, children: "Delete" })
381
437
  ] }),
382
438
  /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: fields.map((field) => /* @__PURE__ */ jsxs(
383
439
  "div",
@@ -730,6 +786,491 @@ function formatFallbackValue(value) {
730
786
  if (typeof value === "object") return JSON.stringify(value, null, 2);
731
787
  return String(value);
732
788
  }
789
+ function TimelineView({ responses, questions, onSelect }) {
790
+ const grouped = useMemo(() => groupByDay(responses), [responses]);
791
+ if (responses.length === 0) {
792
+ return /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-muted-foreground text-sm", children: "No responses to display." });
793
+ }
794
+ return /* @__PURE__ */ jsx("div", { className: "p-4 space-y-6", children: grouped.map((group) => /* @__PURE__ */ jsxs("div", { children: [
795
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
796
+ /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" }),
797
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground shrink-0", children: group.dateLabel }),
798
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground shrink-0", children: [
799
+ "(",
800
+ group.responses.length,
801
+ ")"
802
+ ] }),
803
+ /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" })
804
+ ] }),
805
+ /* @__PURE__ */ jsx("div", { className: "space-y-2 ml-4 border-l-2 border-border/50 pl-4", children: group.responses.map((response, idx) => /* @__PURE__ */ jsx(
806
+ TimelineEntry,
807
+ {
808
+ response,
809
+ questions,
810
+ onView: () => onSelect(response)
811
+ },
812
+ response.sessionToken || idx
813
+ )) })
814
+ ] }, group.dateLabel)) });
815
+ }
816
+ function TimelineEntry({
817
+ response,
818
+ questions,
819
+ onView
820
+ }) {
821
+ const time = new Date(response.submittedAt).toLocaleTimeString([], {
822
+ hour: "2-digit",
823
+ minute: "2-digit"
824
+ });
825
+ const preview = [];
826
+ for (const q of questions) {
827
+ if (preview.length >= 3) break;
828
+ const val = response.values[q.id];
829
+ if (val != null && val !== "") {
830
+ preview.push({ questionId: q.id, label: q.label, type: q.type, value: val });
831
+ }
832
+ }
833
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex items-start gap-3 group", children: [
834
+ /* @__PURE__ */ jsx("div", { className: "absolute -left-[21px] top-2.5 w-2 h-2 rounded-full bg-border group-hover:bg-primary transition-colors" }),
835
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 rounded border border-border bg-card p-3 hover:border-primary/40 transition-colors", children: [
836
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
837
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: time }),
838
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
839
+ response.totalScore != null && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
840
+ "Score: ",
841
+ response.totalScore
842
+ ] }),
843
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "h-6 text-xs", onClick: onView, children: "View" })
844
+ ] })
845
+ ] }),
846
+ preview.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: preview.map((field) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
847
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
848
+ field.label,
849
+ ": "
850
+ ] }),
851
+ /* @__PURE__ */ jsx("span", { className: "text-foreground", children: formatPreviewValue(field.value, field.type) })
852
+ ] }, field.questionId)) })
853
+ ] })
854
+ ] });
855
+ }
856
+ function groupByDay(responses) {
857
+ const sorted = [...responses].sort(
858
+ (a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime()
859
+ );
860
+ const groups = /* @__PURE__ */ new Map();
861
+ for (const r of sorted) {
862
+ const date = new Date(r.submittedAt).toLocaleDateString(void 0, {
863
+ weekday: "long",
864
+ year: "numeric",
865
+ month: "long",
866
+ day: "numeric"
867
+ });
868
+ const existing = groups.get(date);
869
+ if (existing) {
870
+ existing.push(r);
871
+ } else {
872
+ groups.set(date, [r]);
873
+ }
874
+ }
875
+ return Array.from(groups.entries()).map(([dateLabel, responses2]) => ({
876
+ dateLabel,
877
+ responses: responses2
878
+ }));
879
+ }
880
+ function formatPreviewValue(value, type) {
881
+ if (value == null) return "\u2014";
882
+ if (typeof value === "boolean") return value ? "Yes" : "No";
883
+ if (Array.isArray(value)) return value.length > 0 ? value.slice(0, 2).join(", ") + (value.length > 2 ? "..." : "") : "\u2014";
884
+ if (typeof value === "object") {
885
+ if (type === "legal_name") {
886
+ const n = value;
887
+ return [n.first, n.last].filter(Boolean).join(" ") || "\u2014";
888
+ }
889
+ return "...";
890
+ }
891
+ const str = String(value);
892
+ return str.length > 40 ? str.slice(0, 40) + "..." : str;
893
+ }
894
+
895
+ // src/response-viewer/stats-utils.ts
896
+ function computeStats(responses, schema) {
897
+ if (responses.length === 0) {
898
+ return {
899
+ totalCount: 0,
900
+ completionRate: 0,
901
+ avgCompletionTimeMs: null,
902
+ dateRange: null,
903
+ fieldSummaries: []
904
+ };
905
+ }
906
+ const questions = getStatsQuestions(schema);
907
+ const requiredIds = new Set(questions.filter((q) => q.required).map((q) => q.id));
908
+ let completedCount = 0;
909
+ for (const r of responses) {
910
+ let allFilled = true;
911
+ for (const id of requiredIds) {
912
+ if (r.values[id] == null || r.values[id] === "") {
913
+ allFilled = false;
914
+ break;
915
+ }
916
+ }
917
+ if (allFilled) completedCount++;
918
+ }
919
+ const completionRate = requiredIds.size > 0 ? completedCount / responses.length : 1;
920
+ const completionTimes = responses.map((r) => r.completionTimeMs).filter((t) => t != null && t > 0);
921
+ const avgCompletionTimeMs = completionTimes.length > 0 ? completionTimes.reduce((a, b) => a + b, 0) / completionTimes.length : null;
922
+ const timestamps = responses.map((r) => new Date(r.submittedAt).getTime());
923
+ const earliest = new Date(Math.min(...timestamps)).toISOString();
924
+ const latest = new Date(Math.max(...timestamps)).toISOString();
925
+ const fieldSummaries = questions.map((q) => computeFieldSummary(q, responses));
926
+ return {
927
+ totalCount: responses.length,
928
+ completionRate,
929
+ avgCompletionTimeMs,
930
+ dateRange: { earliest, latest },
931
+ fieldSummaries
932
+ };
933
+ }
934
+ function computeFieldSummary(question, responses) {
935
+ const values = responses.map((r) => r.values[question.id]).filter((v) => v != null && v !== "");
936
+ const filledCount = values.length;
937
+ const filledRate = responses.length > 0 ? filledCount / responses.length : 0;
938
+ const base = {
939
+ fieldId: question.id,
940
+ label: question.label,
941
+ type: question.type,
942
+ filledCount,
943
+ filledRate
944
+ };
945
+ if (isNumericType(question.type)) {
946
+ const nums = values.map((v) => typeof v === "number" ? v : Number(v)).filter((n) => !isNaN(n));
947
+ if (nums.length > 0) {
948
+ return {
949
+ ...base,
950
+ kind: "numeric",
951
+ mean: nums.reduce((a, b) => a + b, 0) / nums.length,
952
+ min: Math.min(...nums),
953
+ max: Math.max(...nums)
954
+ };
955
+ }
956
+ return { ...base, kind: "other" };
957
+ }
958
+ if (isCategoricalType(question.type)) {
959
+ const counts = /* @__PURE__ */ new Map();
960
+ for (const v of values) {
961
+ const key = String(v);
962
+ counts.set(key, (counts.get(key) ?? 0) + 1);
963
+ }
964
+ let mode = "";
965
+ let modeCount = 0;
966
+ for (const [key, count] of counts) {
967
+ if (count > modeCount) {
968
+ mode = key;
969
+ modeCount = count;
970
+ }
971
+ }
972
+ return {
973
+ ...base,
974
+ kind: "categorical",
975
+ mode,
976
+ modeCount,
977
+ uniqueCount: counts.size
978
+ };
979
+ }
980
+ return { ...base, kind: "other" };
981
+ }
982
+ function isNumericType(type) {
983
+ return [
984
+ "number",
985
+ "slider",
986
+ "rating",
987
+ "nps",
988
+ "opinion_scale",
989
+ "pain_scale"
990
+ ].includes(type);
991
+ }
992
+ function isCategoricalType(type) {
993
+ return [
994
+ "single_select",
995
+ "dropdown",
996
+ "country_select",
997
+ "boolean",
998
+ "consent",
999
+ "multi_select",
1000
+ "checkbox_group"
1001
+ ].includes(type);
1002
+ }
1003
+ function getStatsQuestions(schema) {
1004
+ const questions = [];
1005
+ for (const section of schema.sections) {
1006
+ for (const q of section.questions) {
1007
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
1008
+ continue;
1009
+ }
1010
+ questions.push(q);
1011
+ }
1012
+ }
1013
+ return questions;
1014
+ }
1015
+ function StatsPanel({ schema, responses, onClose }) {
1016
+ const stats = useMemo(() => computeStats(responses, schema), [responses, schema]);
1017
+ if (stats.totalCount === 0) {
1018
+ return /* @__PURE__ */ jsx("div", { className: "px-4 py-3 text-sm text-muted-foreground", children: "No responses to compute statistics." });
1019
+ }
1020
+ return /* @__PURE__ */ jsxs("div", { className: "border-b border-border bg-muted/30", children: [
1021
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border/50", children: [
1022
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: "Statistics" }),
1023
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "h-6 text-xs", onClick: onClose, children: "Close" })
1024
+ ] }),
1025
+ /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
1026
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3", children: [
1027
+ /* @__PURE__ */ jsx(
1028
+ StatCard,
1029
+ {
1030
+ label: "Total Responses",
1031
+ value: String(stats.totalCount)
1032
+ }
1033
+ ),
1034
+ /* @__PURE__ */ jsx(
1035
+ StatCard,
1036
+ {
1037
+ label: "Completion Rate",
1038
+ value: `${Math.round(stats.completionRate * 100)}%`
1039
+ }
1040
+ ),
1041
+ /* @__PURE__ */ jsx(
1042
+ StatCard,
1043
+ {
1044
+ label: "Avg. Completion Time",
1045
+ value: formatTime(stats.avgCompletionTimeMs)
1046
+ }
1047
+ ),
1048
+ /* @__PURE__ */ jsx(
1049
+ StatCard,
1050
+ {
1051
+ label: "Date Range",
1052
+ value: formatDateRange(stats.dateRange)
1053
+ }
1054
+ )
1055
+ ] }),
1056
+ stats.fieldSummaries.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3 grid grid-cols-2 sm:grid-cols-3 gap-2", children: stats.fieldSummaries.slice(0, 6).map((f) => /* @__PURE__ */ jsxs(
1057
+ "div",
1058
+ {
1059
+ className: "px-2.5 py-2 rounded border border-border bg-background text-xs",
1060
+ children: [
1061
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground truncate mb-1", children: f.label }),
1062
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
1063
+ /* @__PURE__ */ jsxs("span", { className: "text-foreground font-medium", children: [
1064
+ Math.round(f.filledRate * 100),
1065
+ "% filled"
1066
+ ] }),
1067
+ f.kind === "numeric" && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1068
+ "avg ",
1069
+ f.mean.toFixed(1),
1070
+ " (",
1071
+ f.min,
1072
+ "\u2013",
1073
+ f.max,
1074
+ ")"
1075
+ ] }),
1076
+ f.kind === "categorical" && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground truncate", children: [
1077
+ "top: ",
1078
+ f.mode,
1079
+ " (",
1080
+ f.modeCount,
1081
+ ")"
1082
+ ] })
1083
+ ] })
1084
+ ]
1085
+ },
1086
+ f.fieldId
1087
+ )) })
1088
+ ] })
1089
+ ] });
1090
+ }
1091
+ function StatCard({ label, value }) {
1092
+ return /* @__PURE__ */ jsxs("div", { className: "px-3 py-2 rounded border border-border bg-background", children: [
1093
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground mb-0.5", children: label }),
1094
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground", children: value })
1095
+ ] });
1096
+ }
1097
+ function formatTime(ms) {
1098
+ if (ms == null) return "\u2014";
1099
+ const seconds = Math.round(ms / 1e3);
1100
+ if (seconds < 60) return `${seconds}s`;
1101
+ const minutes = Math.floor(seconds / 60);
1102
+ const remainingSeconds = seconds % 60;
1103
+ return `${minutes}m ${remainingSeconds}s`;
1104
+ }
1105
+ function formatDateRange(range) {
1106
+ if (!range) return "\u2014";
1107
+ const fmt = (iso) => new Date(iso).toLocaleDateString();
1108
+ return `${fmt(range.earliest)} \u2013 ${fmt(range.latest)}`;
1109
+ }
1110
+
1111
+ // src/response-viewer/chart-utils.ts
1112
+ function computeFieldDistribution(responses, fieldId) {
1113
+ const counts = /* @__PURE__ */ new Map();
1114
+ let total = 0;
1115
+ for (const r of responses) {
1116
+ const raw = r.values[fieldId];
1117
+ if (raw == null || raw === "") continue;
1118
+ total++;
1119
+ if (typeof raw === "boolean") {
1120
+ const key = raw ? "Yes" : "No";
1121
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1122
+ } else if (Array.isArray(raw)) {
1123
+ for (const item of raw) {
1124
+ const key = String(item);
1125
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1126
+ }
1127
+ } else {
1128
+ const key = String(raw);
1129
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1130
+ }
1131
+ }
1132
+ if (total === 0) return [];
1133
+ const entries = [];
1134
+ for (const [value, count] of counts) {
1135
+ entries.push({
1136
+ value,
1137
+ count,
1138
+ percentage: count / total * 100
1139
+ });
1140
+ }
1141
+ entries.sort((a, b) => b.count - a.count);
1142
+ return entries;
1143
+ }
1144
+ function getChartableFieldIds(schema) {
1145
+ const chartable = [];
1146
+ const chartTypes = /* @__PURE__ */ new Set([
1147
+ "single_select",
1148
+ "dropdown",
1149
+ "country_select",
1150
+ "boolean",
1151
+ "consent",
1152
+ "multi_select",
1153
+ "checkbox_group",
1154
+ "rating",
1155
+ "nps",
1156
+ "opinion_scale",
1157
+ "pain_scale"
1158
+ ]);
1159
+ for (const section of schema.sections) {
1160
+ for (const q of section.questions) {
1161
+ if (chartTypes.has(q.type)) {
1162
+ chartable.push({ id: q.id, label: q.label, type: q.type });
1163
+ }
1164
+ }
1165
+ }
1166
+ return chartable;
1167
+ }
1168
+ function ChartPanel({ schema, responses, onClose }) {
1169
+ const chartableFields = useMemo(() => getChartableFieldIds(schema), [schema]);
1170
+ const [selectedFieldId, setSelectedFieldId] = useState(chartableFields[0]?.id ?? "");
1171
+ const distribution = useMemo(() => {
1172
+ if (!selectedFieldId) return [];
1173
+ return computeFieldDistribution(responses, selectedFieldId);
1174
+ }, [responses, selectedFieldId]);
1175
+ const selectedField = chartableFields.find((f) => f.id === selectedFieldId);
1176
+ const maxCount = distribution.length > 0 ? Math.max(...distribution.map((d) => d.count)) : 1;
1177
+ if (chartableFields.length === 0) {
1178
+ return /* @__PURE__ */ jsxs("div", { className: "px-4 py-6 border-b border-border bg-muted/30 text-center", children: [
1179
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No chartable fields found in this schema." }),
1180
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Charts work with select, dropdown, boolean, rating, and similar field types." })
1181
+ ] });
1182
+ }
1183
+ return /* @__PURE__ */ jsxs("div", { className: "border-b border-border bg-muted/30", children: [
1184
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border/50", children: [
1185
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1186
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: "Field Distribution" }),
1187
+ /* @__PURE__ */ jsx(
1188
+ "select",
1189
+ {
1190
+ value: selectedFieldId,
1191
+ onChange: (e) => setSelectedFieldId(e.target.value),
1192
+ className: "text-xs bg-background border border-border rounded px-2 py-1 text-foreground",
1193
+ children: chartableFields.map((f) => /* @__PURE__ */ jsx("option", { value: f.id, children: f.label }, f.id))
1194
+ }
1195
+ )
1196
+ ] }),
1197
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "h-6 text-xs", onClick: onClose, children: "Close" })
1198
+ ] }),
1199
+ /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
1200
+ distribution.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground text-center py-4", children: "No data for this field." }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1201
+ distribution.slice(0, 10).map((entry) => /* @__PURE__ */ jsx(BarRow, { entry, maxCount }, entry.value)),
1202
+ distribution.length > 10 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-2", children: [
1203
+ "+",
1204
+ distribution.length - 10,
1205
+ " more values"
1206
+ ] })
1207
+ ] }),
1208
+ selectedField && /* @__PURE__ */ jsxs("div", { className: "mt-3 text-[11px] text-muted-foreground", children: [
1209
+ responses.length,
1210
+ " response",
1211
+ responses.length !== 1 ? "s" : "",
1212
+ " \xB7 ",
1213
+ distribution.length,
1214
+ " unique value",
1215
+ distribution.length !== 1 ? "s" : ""
1216
+ ] })
1217
+ ] })
1218
+ ] });
1219
+ }
1220
+ function BarRow({ entry, maxCount }) {
1221
+ const widthPct = maxCount > 0 ? entry.count / maxCount * 100 : 0;
1222
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1223
+ /* @__PURE__ */ jsx("div", { className: "w-24 shrink-0 text-xs text-foreground truncate", title: entry.value, children: entry.value }),
1224
+ /* @__PURE__ */ jsx("div", { className: "flex-1 h-5 bg-background rounded overflow-hidden border border-border/50", children: /* @__PURE__ */ jsx(
1225
+ "div",
1226
+ {
1227
+ className: "h-full bg-primary/60 rounded transition-all",
1228
+ style: { width: `${widthPct}%` }
1229
+ }
1230
+ ) }),
1231
+ /* @__PURE__ */ jsxs("div", { className: "w-16 shrink-0 text-xs text-muted-foreground text-right", children: [
1232
+ entry.count,
1233
+ " (",
1234
+ Math.round(entry.percentage),
1235
+ "%)"
1236
+ ] })
1237
+ ] });
1238
+ }
1239
+ function ConfirmDialog({
1240
+ title,
1241
+ message,
1242
+ confirmLabel = "Confirm",
1243
+ cancelLabel = "Cancel",
1244
+ variant = "default",
1245
+ onConfirm,
1246
+ onCancel
1247
+ }) {
1248
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
1249
+ /* @__PURE__ */ jsx(
1250
+ "div",
1251
+ {
1252
+ className: "absolute inset-0 bg-black/50",
1253
+ onClick: onCancel
1254
+ }
1255
+ ),
1256
+ /* @__PURE__ */ jsxs("div", { className: "relative bg-background border border-border rounded-lg shadow-lg p-6 max-w-sm w-full mx-4", children: [
1257
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground mb-2", children: title }),
1258
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: message }),
1259
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1260
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onCancel, children: cancelLabel }),
1261
+ /* @__PURE__ */ jsx(
1262
+ Button,
1263
+ {
1264
+ variant: variant === "destructive" ? "destructive" : "default",
1265
+ size: "sm",
1266
+ onClick: onConfirm,
1267
+ children: confirmLabel
1268
+ }
1269
+ )
1270
+ ] })
1271
+ ] })
1272
+ ] });
1273
+ }
733
1274
 
734
1275
  // src/response-viewer/export-utils.ts
735
1276
  function buildCsvContent(schema, responses, options = {}) {
@@ -864,6 +1405,84 @@ function paginate(items, currentPage, pageSize) {
864
1405
  };
865
1406
  }
866
1407
 
1408
+ // src/response-viewer/search-utils.ts
1409
+ function searchResponses(responses, query, schema) {
1410
+ if (!query.trim()) return responses;
1411
+ const lowerQuery = query.toLowerCase().trim();
1412
+ const questions = getSearchableQuestions(schema);
1413
+ return responses.filter((response) => {
1414
+ if (new Date(response.submittedAt).toLocaleString().toLowerCase().includes(lowerQuery)) {
1415
+ return true;
1416
+ }
1417
+ if (response.sessionToken?.toLowerCase().includes(lowerQuery)) {
1418
+ return true;
1419
+ }
1420
+ for (const q of questions) {
1421
+ const value = response.values[q.id];
1422
+ if (value == null) continue;
1423
+ const stringified = stringifyValue(value, q.type);
1424
+ if (stringified.toLowerCase().includes(lowerQuery)) {
1425
+ return true;
1426
+ }
1427
+ }
1428
+ if (response.totalScore != null && String(response.totalScore).includes(lowerQuery)) {
1429
+ return true;
1430
+ }
1431
+ return false;
1432
+ });
1433
+ }
1434
+ function getSearchableQuestions(schema) {
1435
+ const questions = [];
1436
+ for (const section of schema.sections) {
1437
+ for (const q of section.questions) {
1438
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
1439
+ continue;
1440
+ }
1441
+ questions.push(q);
1442
+ }
1443
+ }
1444
+ return questions;
1445
+ }
1446
+ function stringifyValue(value, type) {
1447
+ if (value == null) return "";
1448
+ switch (type) {
1449
+ case "vitals_entry": {
1450
+ if (typeof value !== "object" || value === null) break;
1451
+ const v = value;
1452
+ const parts = [];
1453
+ if (v.systolicBp && v.diastolicBp) parts.push(`BP ${v.systolicBp}/${v.diastolicBp}`);
1454
+ if (v.heartRate) parts.push(`HR ${v.heartRate}`);
1455
+ if (v.temperature) parts.push(`${v.temperature}`);
1456
+ if (v.oxygenSaturation) parts.push(`SpO2 ${v.oxygenSaturation}`);
1457
+ return parts.join(" ");
1458
+ }
1459
+ case "medication_list":
1460
+ if (Array.isArray(value)) {
1461
+ return value.map((m) => [m.name, m.dosage, m.frequency].filter(Boolean).join(" ")).join(" ");
1462
+ }
1463
+ break;
1464
+ case "allergy_list":
1465
+ if (Array.isArray(value)) {
1466
+ return value.map((a) => [a.allergen, a.severity].filter(Boolean).join(" ")).join(" ");
1467
+ }
1468
+ break;
1469
+ case "legal_name": {
1470
+ if (typeof value !== "object" || value === null) break;
1471
+ const n = value;
1472
+ return [n.first, n.middle, n.last].filter(Boolean).join(" ");
1473
+ }
1474
+ case "address": {
1475
+ if (typeof value !== "object" || value === null) break;
1476
+ const a = value;
1477
+ return [a.street, a.city, a.state, a.zip].filter(Boolean).join(" ");
1478
+ }
1479
+ }
1480
+ if (typeof value === "boolean") return value ? "Yes" : "No";
1481
+ if (Array.isArray(value)) return value.map(String).join(" ");
1482
+ if (typeof value === "object") return JSON.stringify(value);
1483
+ return String(value);
1484
+ }
1485
+
867
1486
  // src/response-viewer/filter-utils.ts
868
1487
  function createEmptyFilterState() {
869
1488
  return { filters: [], dateRange: {} };
@@ -962,7 +1581,11 @@ function ResponseViewerInner({
962
1581
  dateFormat,
963
1582
  columnLabels,
964
1583
  onExport,
965
- pageSize = 25
1584
+ pageSize = 25,
1585
+ onDelete,
1586
+ onBulkDelete,
1587
+ onBulkExport,
1588
+ selectable = false
966
1589
  }) {
967
1590
  const [viewMode, setViewMode] = useState("table");
968
1591
  const [selectedResponse, setSelectedResponse] = useState(null);
@@ -970,6 +1593,11 @@ function ResponseViewerInner({
970
1593
  const [effectivePageSize, setEffectivePageSize] = useState(pageSize);
971
1594
  const [filterState, setFilterState] = useState(createEmptyFilterState);
972
1595
  const [showFilterPanel, setShowFilterPanel] = useState(false);
1596
+ const [searchQuery, setSearchQuery] = useState("");
1597
+ const [showStats, setShowStats] = useState(false);
1598
+ const [showCharts, setShowCharts] = useState(false);
1599
+ const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
1600
+ const [confirmDialog, setConfirmDialog] = useState(null);
973
1601
  const [draftFieldId, setDraftFieldId] = useState("");
974
1602
  const [draftOperator, setDraftOperator] = useState("contains");
975
1603
  const [draftValue, setDraftValue] = useState("");
@@ -980,9 +1608,18 @@ function ResponseViewerInner({
980
1608
  setEffectivePageSize(pageSize);
981
1609
  setCurrentPage(1);
982
1610
  }, [pageSize]);
1611
+ useEffect(() => {
1612
+ setSelectedIds(/* @__PURE__ */ new Set());
1613
+ }, [responses]);
1614
+ const searchedResponses = useMemo(
1615
+ () => searchResponses(responses, searchQuery, schema),
1616
+ [responses, searchQuery, schema]
1617
+ );
983
1618
  const isFiltered = hasActiveFilters(filterState);
984
- const filteredResponses = applyFilters(responses, filterState);
1619
+ const filteredResponses = applyFilters(searchedResponses, filterState);
985
1620
  const pag = paginate(filteredResponses, currentPage, effectivePageSize);
1621
+ const questions = getAllQuestions2(schema);
1622
+ const isSelectable = selectable && (!!onBulkDelete || !!onBulkExport);
986
1623
  function handleSelect(response) {
987
1624
  setSelectedResponse(response);
988
1625
  onResponseSelect?.(response);
@@ -1029,7 +1666,59 @@ function ResponseViewerInner({
1029
1666
  setFilterState(createEmptyFilterState());
1030
1667
  setCurrentPage(1);
1031
1668
  }
1032
- const questions = getAllQuestions2(schema);
1669
+ function handleToggleSelect(token) {
1670
+ setSelectedIds((prev) => {
1671
+ const next = new Set(prev);
1672
+ if (next.has(token)) {
1673
+ next.delete(token);
1674
+ } else {
1675
+ next.add(token);
1676
+ }
1677
+ return next;
1678
+ });
1679
+ }
1680
+ function handleSelectAll() {
1681
+ const currentTokens = pag.pageItems.map((r) => r.sessionToken).filter(Boolean);
1682
+ const allSelected = currentTokens.every((t) => selectedIds.has(t));
1683
+ setSelectedIds((prev) => {
1684
+ const next = new Set(prev);
1685
+ if (allSelected) {
1686
+ currentTokens.forEach((t) => next.delete(t));
1687
+ } else {
1688
+ currentTokens.forEach((t) => next.add(t));
1689
+ }
1690
+ return next;
1691
+ });
1692
+ }
1693
+ function handleBulkDelete() {
1694
+ if (!onBulkDelete || selectedIds.size === 0) return;
1695
+ setConfirmDialog({
1696
+ title: "Delete selected responses",
1697
+ message: `Are you sure you want to delete ${selectedIds.size} response${selectedIds.size !== 1 ? "s" : ""}? This cannot be undone.`,
1698
+ onConfirm: () => {
1699
+ onBulkDelete(Array.from(selectedIds));
1700
+ setSelectedIds(/* @__PURE__ */ new Set());
1701
+ setConfirmDialog(null);
1702
+ }
1703
+ });
1704
+ }
1705
+ function handleBulkExport() {
1706
+ if (!onBulkExport || selectedIds.size === 0) return;
1707
+ const selected = responses.filter((r) => r.sessionToken && selectedIds.has(r.sessionToken));
1708
+ onBulkExport(selected);
1709
+ }
1710
+ function handleDeleteSingle(token) {
1711
+ if (!onDelete) return;
1712
+ setConfirmDialog({
1713
+ title: "Delete response",
1714
+ message: "Are you sure you want to delete this response? This cannot be undone.",
1715
+ onConfirm: () => {
1716
+ onDelete(token);
1717
+ setSelectedResponse(null);
1718
+ setConfirmDialog(null);
1719
+ }
1720
+ });
1721
+ }
1033
1722
  function getFields(response) {
1034
1723
  return questions.map((q) => ({
1035
1724
  questionId: q.id,
@@ -1040,6 +1729,19 @@ function ResponseViewerInner({
1040
1729
  }
1041
1730
  const heightValue = typeof height === "number" ? `${height}px` : height;
1042
1731
  const widthValue = typeof width === "number" ? `${width}px` : width;
1732
+ const countText = (() => {
1733
+ const hasSearch = searchQuery.trim().length > 0;
1734
+ if (hasSearch && isFiltered) {
1735
+ return `${filteredResponses.length} of ${responses.length} responses (searched + filtered)`;
1736
+ }
1737
+ if (hasSearch) {
1738
+ return `${searchedResponses.length} result${searchedResponses.length !== 1 ? "s" : ""} for "${searchQuery}"`;
1739
+ }
1740
+ if (isFiltered) {
1741
+ return `Showing ${filteredResponses.length} of ${responses.length} responses (filtered)`;
1742
+ }
1743
+ return `${responses.length} response${responses.length !== 1 ? "s" : ""}`;
1744
+ })();
1043
1745
  return /* @__PURE__ */ jsxs(
1044
1746
  "div",
1045
1747
  {
@@ -1047,7 +1749,20 @@ function ResponseViewerInner({
1047
1749
  style: { height: heightValue, width: widthValue },
1048
1750
  children: [
1049
1751
  !selectedResponse && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-b border-border", children: [
1050
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mr-auto", children: isFiltered ? `Showing ${filteredResponses.length} of ${responses.length} responses (filtered)` : `${responses.length} response${responses.length !== 1 ? "s" : ""}` }),
1752
+ /* @__PURE__ */ jsx(
1753
+ Input,
1754
+ {
1755
+ type: "text",
1756
+ placeholder: "Search responses...",
1757
+ className: "h-7 text-xs w-40",
1758
+ value: searchQuery,
1759
+ onChange: (e) => {
1760
+ setSearchQuery(e.target.value);
1761
+ setCurrentPage(1);
1762
+ }
1763
+ }
1764
+ ),
1765
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mr-auto", children: countText }),
1051
1766
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1052
1767
  /* @__PURE__ */ jsx(
1053
1768
  Button,
@@ -1074,6 +1789,48 @@ function ResponseViewerInner({
1074
1789
  onClick: () => setViewMode("card"),
1075
1790
  children: "Cards"
1076
1791
  }
1792
+ ),
1793
+ /* @__PURE__ */ jsx(
1794
+ Button,
1795
+ {
1796
+ variant: viewMode === "timeline" ? "secondary" : "ghost",
1797
+ size: "sm",
1798
+ className: cn(
1799
+ "h-7 text-xs",
1800
+ viewMode === "timeline" && "font-semibold border border-ring"
1801
+ ),
1802
+ onClick: () => setViewMode("timeline"),
1803
+ children: "Timeline"
1804
+ }
1805
+ )
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
1808
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1809
+ /* @__PURE__ */ jsx(
1810
+ Button,
1811
+ {
1812
+ variant: showStats ? "secondary" : "ghost",
1813
+ size: "sm",
1814
+ className: cn(
1815
+ "h-7 text-xs",
1816
+ showStats && "font-semibold border border-ring"
1817
+ ),
1818
+ onClick: () => setShowStats((v) => !v),
1819
+ children: "Stats"
1820
+ }
1821
+ ),
1822
+ /* @__PURE__ */ jsx(
1823
+ Button,
1824
+ {
1825
+ variant: showCharts ? "secondary" : "ghost",
1826
+ size: "sm",
1827
+ className: cn(
1828
+ "h-7 text-xs",
1829
+ showCharts && "font-semibold border border-ring"
1830
+ ),
1831
+ onClick: () => setShowCharts((v) => !v),
1832
+ children: "Charts"
1833
+ }
1077
1834
  )
1078
1835
  ] }),
1079
1836
  /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
@@ -1139,6 +1896,26 @@ function ResponseViewerInner({
1139
1896
  )
1140
1897
  ] })
1141
1898
  ] }),
1899
+ !selectedResponse && isSelectable && selectedIds.size > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border bg-accent/50", children: [
1900
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground", children: [
1901
+ selectedIds.size,
1902
+ " selected"
1903
+ ] }),
1904
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
1905
+ onBulkExport && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "h-6 text-xs", onClick: handleBulkExport, children: "Export Selected" }),
1906
+ onBulkDelete && /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", className: "h-6 text-xs", onClick: handleBulkDelete, children: "Delete Selected" }),
1907
+ /* @__PURE__ */ jsx(
1908
+ Button,
1909
+ {
1910
+ variant: "ghost",
1911
+ size: "sm",
1912
+ className: "h-6 text-xs",
1913
+ onClick: () => setSelectedIds(/* @__PURE__ */ new Set()),
1914
+ children: "Clear"
1915
+ }
1916
+ )
1917
+ ] })
1918
+ ] }),
1142
1919
  !selectedResponse && showFilterPanel && /* @__PURE__ */ jsxs("div", { className: "fc-rv-filter__panel px-3 py-2 border-b border-border bg-muted/50", children: [
1143
1920
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-2", children: [
1144
1921
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground w-16 shrink-0", children: "Date:" }),
@@ -1267,19 +2044,47 @@ function ResponseViewerInner({
1267
2044
  );
1268
2045
  })
1269
2046
  ] }),
1270
- /* @__PURE__ */ jsx("div", { className: cn("flex-1 min-w-0 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsx(
2047
+ !selectedResponse && showStats && /* @__PURE__ */ jsx(
2048
+ StatsPanel,
2049
+ {
2050
+ schema,
2051
+ responses: filteredResponses,
2052
+ onClose: () => setShowStats(false)
2053
+ }
2054
+ ),
2055
+ !selectedResponse && showCharts && /* @__PURE__ */ jsx(
2056
+ ChartPanel,
2057
+ {
2058
+ schema,
2059
+ responses: filteredResponses,
2060
+ onClose: () => setShowCharts(false)
2061
+ }
2062
+ ),
2063
+ /* @__PURE__ */ jsx("div", { className: cn("flex-1 min-w-0 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
1271
2064
  ResponseDetail,
1272
2065
  {
1273
2066
  response: selectedResponse,
1274
2067
  fields: getFields(selectedResponse),
1275
- onBack: handleBack
2068
+ onBack: handleBack,
2069
+ onDelete: onDelete && selectedResponse.sessionToken ? () => handleDeleteSingle(selectedResponse.sessionToken) : void 0
2070
+ }
2071
+ ) }) : viewMode === "timeline" ? /* @__PURE__ */ jsx(
2072
+ TimelineView,
2073
+ {
2074
+ responses: filteredResponses,
2075
+ questions,
2076
+ onSelect: handleSelect
1276
2077
  }
1277
2078
  ) : viewMode === "table" ? /* @__PURE__ */ jsx(
1278
2079
  ResponseTable,
1279
2080
  {
1280
2081
  schema,
1281
2082
  responses: pag.pageItems,
1282
- onRowClick: handleSelect
2083
+ onRowClick: handleSelect,
2084
+ selectable: isSelectable,
2085
+ selectedIds,
2086
+ onToggleSelect: handleToggleSelect,
2087
+ onSelectAll: handleSelectAll
1283
2088
  }
1284
2089
  ) : /* @__PURE__ */ jsxs("div", { className: "grid gap-3 p-3 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]", children: [
1285
2090
  pag.pageItems.map((response, idx) => /* @__PURE__ */ jsx(
@@ -1287,13 +2092,16 @@ function ResponseViewerInner({
1287
2092
  {
1288
2093
  response,
1289
2094
  fields: getFields(response),
1290
- onClick: () => handleSelect(response)
2095
+ onClick: () => handleSelect(response),
2096
+ selectable: isSelectable,
2097
+ selected: !!(response.sessionToken && selectedIds.has(response.sessionToken)),
2098
+ onToggleSelect: response.sessionToken ? () => handleToggleSelect(response.sessionToken) : void 0
1291
2099
  },
1292
2100
  response.sessionToken || idx
1293
2101
  )),
1294
- filteredResponses.length === 0 && /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-muted-foreground", children: isFiltered ? "No matching responses" : "No responses yet" })
2102
+ filteredResponses.length === 0 && /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-muted-foreground", children: isFiltered || searchQuery.trim() ? "No matching responses" : "No responses yet" })
1295
2103
  ] }) }),
1296
- !selectedResponse && pag.showPagination && /* @__PURE__ */ jsxs("div", { className: "fc-rv-pagination flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-t border-border", children: [
2104
+ !selectedResponse && viewMode !== "timeline" && pag.showPagination && /* @__PURE__ */ jsxs("div", { className: "fc-rv-pagination flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-t border-border", children: [
1297
2105
  /* @__PURE__ */ jsxs("div", { className: "fc-rv-pagination__info text-xs text-muted-foreground mr-auto", children: [
1298
2106
  "Showing ",
1299
2107
  pag.startItem,
@@ -1347,7 +2155,18 @@ function ResponseViewerInner({
1347
2155
  }
1348
2156
  )
1349
2157
  ] })
1350
- ] })
2158
+ ] }),
2159
+ confirmDialog && /* @__PURE__ */ jsx(
2160
+ ConfirmDialog,
2161
+ {
2162
+ title: confirmDialog.title,
2163
+ message: confirmDialog.message,
2164
+ confirmLabel: "Delete",
2165
+ variant: "destructive",
2166
+ onConfirm: confirmDialog.onConfirm,
2167
+ onCancel: () => setConfirmDialog(null)
2168
+ }
2169
+ )
1351
2170
  ]
1352
2171
  }
1353
2172
  );