@squaredr/fieldcraft-pro 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -36
- package/dist/{chunk-RSU3X25P.mjs → chunk-4BX7FCXH.mjs} +416 -21
- package/dist/{chunk-CJBC3KWB.mjs → chunk-GNBXIG63.mjs} +127 -15
- package/dist/form-builder/index.js +125 -13
- package/dist/form-builder/index.mjs +1 -1
- package/dist/index.js +539 -32
- package/dist/index.mjs +2 -2
- package/dist/response-viewer/index.d.mts +10 -0
- package/dist/response-viewer/index.d.ts +10 -0
- package/dist/response-viewer/index.js +414 -19
- package/dist/response-viewer/index.mjs +1 -1
- package/dist/styles.css +1 -1
- package/package.json +13 -5
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, cleanPreset, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, squaredrDarkPreset, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo } from './chunk-
|
|
2
|
-
export { ResponseViewer } from './chunk-
|
|
1
|
+
export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, cleanPreset, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, squaredrDarkPreset, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo } from './chunk-GNBXIG63.mjs';
|
|
2
|
+
export { ResponseViewer } from './chunk-4BX7FCXH.mjs';
|
|
3
3
|
export { cn } from './chunk-QQ4JZGTD.mjs';
|
|
4
4
|
export { PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner } from './chunk-7LQW5QYT.mjs';
|
|
5
5
|
export { FieldCraftProProvider, UnlicensedOverlay, isProductionEnvironment, requireLicense, useLicense, validateLicense } from './chunk-VECQKSWS.mjs';
|
|
@@ -10,6 +10,16 @@ type ResponseViewerProps = {
|
|
|
10
10
|
width?: string | number;
|
|
11
11
|
/** Custom per-type field renderers. Keys are field type strings (e.g. "vitals_entry"). */
|
|
12
12
|
fieldRenderers?: Record<string, FieldRenderer>;
|
|
13
|
+
/** Filename for CSV/JSON downloads. Defaults to "responses.csv" / "responses.json". */
|
|
14
|
+
filename?: string;
|
|
15
|
+
/** Date format for CSV export. "locale" uses toLocaleString(), "iso" uses toISOString(). */
|
|
16
|
+
dateFormat?: "locale" | "iso";
|
|
17
|
+
/** Override CSV column headers. Maps question ID to display label. */
|
|
18
|
+
columnLabels?: Record<string, string>;
|
|
19
|
+
/** Callback fired after CSV or JSON export completes. */
|
|
20
|
+
onExport?: (format: "csv" | "json", count: number) => void;
|
|
21
|
+
/** Number of responses per page in table/card views. Defaults to 25. */
|
|
22
|
+
pageSize?: 10 | 25 | 50 | 100;
|
|
13
23
|
};
|
|
14
24
|
type ViewMode = "table" | "card" | "detail";
|
|
15
25
|
type ResponseField = {
|
|
@@ -10,6 +10,16 @@ type ResponseViewerProps = {
|
|
|
10
10
|
width?: string | number;
|
|
11
11
|
/** Custom per-type field renderers. Keys are field type strings (e.g. "vitals_entry"). */
|
|
12
12
|
fieldRenderers?: Record<string, FieldRenderer>;
|
|
13
|
+
/** Filename for CSV/JSON downloads. Defaults to "responses.csv" / "responses.json". */
|
|
14
|
+
filename?: string;
|
|
15
|
+
/** Date format for CSV export. "locale" uses toLocaleString(), "iso" uses toISOString(). */
|
|
16
|
+
dateFormat?: "locale" | "iso";
|
|
17
|
+
/** Override CSV column headers. Maps question ID to display label. */
|
|
18
|
+
columnLabels?: Record<string, string>;
|
|
19
|
+
/** Callback fired after CSV or JSON export completes. */
|
|
20
|
+
onExport?: (format: "csv" | "json", count: number) => void;
|
|
21
|
+
/** Number of responses per page in table/card views. Defaults to 25. */
|
|
22
|
+
pageSize?: 10 | 25 | 50 | 100;
|
|
13
23
|
};
|
|
14
24
|
type ViewMode = "table" | "card" | "detail";
|
|
15
25
|
type ResponseField = {
|
|
@@ -1057,16 +1057,24 @@ function formatFallbackValue(value) {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
1059
|
// src/response-viewer/export-utils.ts
|
|
1060
|
-
function
|
|
1060
|
+
function buildCsvContent(schema, responses, options = {}) {
|
|
1061
|
+
const { dateFormat = "locale", columnLabels } = options;
|
|
1061
1062
|
const questions = getExportableQuestions(schema);
|
|
1062
|
-
const headers = [
|
|
1063
|
+
const headers = [
|
|
1064
|
+
"Submitted",
|
|
1065
|
+
...questions.map((q) => columnLabels?.[q.id] ?? q.label),
|
|
1066
|
+
"Score"
|
|
1067
|
+
];
|
|
1063
1068
|
const rows = responses.map((r) => {
|
|
1064
|
-
const submitted = new Date(r.submittedAt).toLocaleString();
|
|
1069
|
+
const submitted = dateFormat === "iso" ? new Date(r.submittedAt).toISOString() : new Date(r.submittedAt).toLocaleString();
|
|
1065
1070
|
const values = questions.map((q) => formatExportValue(r.values[q.id], q.type));
|
|
1066
1071
|
const score = r.totalScore != null ? String(r.totalScore) : "";
|
|
1067
1072
|
return [submitted, ...values, score];
|
|
1068
1073
|
});
|
|
1069
|
-
|
|
1074
|
+
return [headers, ...rows].map((row) => row.map(escapeCsvField).join(",")).join("\n");
|
|
1075
|
+
}
|
|
1076
|
+
function exportToCsv(schema, responses, filename = "responses.csv", options = {}) {
|
|
1077
|
+
const csvContent = buildCsvContent(schema, responses, options);
|
|
1070
1078
|
downloadBlob(csvContent, filename, "text/csv;charset=utf-8;");
|
|
1071
1079
|
}
|
|
1072
1080
|
function exportToJson(responses, filename = "responses.json") {
|
|
@@ -1161,18 +1169,148 @@ function downloadBlob(content, filename, mimeType) {
|
|
|
1161
1169
|
document.body.removeChild(link);
|
|
1162
1170
|
URL.revokeObjectURL(url);
|
|
1163
1171
|
}
|
|
1172
|
+
|
|
1173
|
+
// src/response-viewer/pagination-utils.ts
|
|
1174
|
+
function paginate(items, currentPage, pageSize) {
|
|
1175
|
+
const totalItems = items.length;
|
|
1176
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
1177
|
+
const page = Math.max(1, Math.min(currentPage, totalPages));
|
|
1178
|
+
const startIndex = (page - 1) * pageSize;
|
|
1179
|
+
const endIndex = Math.min(startIndex + pageSize, totalItems);
|
|
1180
|
+
const pageItems = items.slice(startIndex, endIndex);
|
|
1181
|
+
return {
|
|
1182
|
+
pageItems,
|
|
1183
|
+
currentPage: page,
|
|
1184
|
+
totalPages,
|
|
1185
|
+
startItem: totalItems === 0 ? 0 : startIndex + 1,
|
|
1186
|
+
endItem: endIndex,
|
|
1187
|
+
totalItems,
|
|
1188
|
+
showPagination: totalItems > pageSize
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/response-viewer/filter-utils.ts
|
|
1193
|
+
function createEmptyFilterState() {
|
|
1194
|
+
return { filters: [], dateRange: {} };
|
|
1195
|
+
}
|
|
1196
|
+
function hasActiveFilters(state) {
|
|
1197
|
+
return state.filters.length > 0 || !!state.dateRange.from || !!state.dateRange.to;
|
|
1198
|
+
}
|
|
1199
|
+
function applyFilters(responses, state) {
|
|
1200
|
+
if (!hasActiveFilters(state)) return responses;
|
|
1201
|
+
return responses.filter((r) => {
|
|
1202
|
+
if (!matchesDateRange(r.submittedAt, state.dateRange)) return false;
|
|
1203
|
+
for (const filter of state.filters) {
|
|
1204
|
+
if (!matchesFieldFilter(r.values[filter.fieldId], filter)) return false;
|
|
1205
|
+
}
|
|
1206
|
+
return true;
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
function matchesDateRange(submittedAt, range) {
|
|
1210
|
+
if (!range.from && !range.to) return true;
|
|
1211
|
+
const submitted = new Date(submittedAt);
|
|
1212
|
+
if (range.from) {
|
|
1213
|
+
const from = new Date(range.from);
|
|
1214
|
+
from.setHours(0, 0, 0, 0);
|
|
1215
|
+
if (submitted < from) return false;
|
|
1216
|
+
}
|
|
1217
|
+
if (range.to) {
|
|
1218
|
+
const to = new Date(range.to);
|
|
1219
|
+
to.setHours(23, 59, 59, 999);
|
|
1220
|
+
if (submitted > to) return false;
|
|
1221
|
+
}
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
1224
|
+
function matchesFieldFilter(value, filter) {
|
|
1225
|
+
switch (filter.operator) {
|
|
1226
|
+
case "equals":
|
|
1227
|
+
if (value == null) return false;
|
|
1228
|
+
return String(value).toLowerCase() === String(filter.value).toLowerCase();
|
|
1229
|
+
case "contains":
|
|
1230
|
+
if (value == null) return false;
|
|
1231
|
+
return String(value).toLowerCase().includes(String(filter.value).toLowerCase());
|
|
1232
|
+
case "gt":
|
|
1233
|
+
if (typeof value !== "number") return false;
|
|
1234
|
+
return value > Number(filter.value);
|
|
1235
|
+
case "lt":
|
|
1236
|
+
if (typeof value !== "number") return false;
|
|
1237
|
+
return value < Number(filter.value);
|
|
1238
|
+
case "is_true":
|
|
1239
|
+
return value === true || value === "true" || value === "agreed";
|
|
1240
|
+
case "is_false":
|
|
1241
|
+
return value === false || value === "false" || value == null;
|
|
1242
|
+
default:
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1164
1246
|
function cn(...inputs) {
|
|
1165
1247
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
1166
1248
|
}
|
|
1249
|
+
var PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
1250
|
+
var FIELD_FILTER_OPERATORS = {
|
|
1251
|
+
text: [
|
|
1252
|
+
{ label: "equals", value: "equals" },
|
|
1253
|
+
{ label: "contains", value: "contains" }
|
|
1254
|
+
],
|
|
1255
|
+
number: [
|
|
1256
|
+
{ label: "equals", value: "equals" },
|
|
1257
|
+
{ label: "greater than", value: "gt" },
|
|
1258
|
+
{ label: "less than", value: "lt" }
|
|
1259
|
+
],
|
|
1260
|
+
boolean: [
|
|
1261
|
+
{ label: "is true", value: "is_true" },
|
|
1262
|
+
{ label: "is false", value: "is_false" }
|
|
1263
|
+
],
|
|
1264
|
+
select: [
|
|
1265
|
+
{ label: "equals", value: "equals" }
|
|
1266
|
+
]
|
|
1267
|
+
};
|
|
1268
|
+
function getOperatorsForType(type) {
|
|
1269
|
+
if (type === "number" || type === "slider" || type === "rating" || type === "nps" || type === "opinion_scale" || type === "pain_scale") {
|
|
1270
|
+
return FIELD_FILTER_OPERATORS.number;
|
|
1271
|
+
}
|
|
1272
|
+
if (type === "boolean" || type === "consent") {
|
|
1273
|
+
return FIELD_FILTER_OPERATORS.boolean;
|
|
1274
|
+
}
|
|
1275
|
+
if (type === "single_select" || type === "dropdown" || type === "country_select") {
|
|
1276
|
+
return FIELD_FILTER_OPERATORS.select;
|
|
1277
|
+
}
|
|
1278
|
+
return FIELD_FILTER_OPERATORS.text;
|
|
1279
|
+
}
|
|
1280
|
+
function operatorNeedsValue(operator) {
|
|
1281
|
+
return operator !== "is_true" && operator !== "is_false";
|
|
1282
|
+
}
|
|
1167
1283
|
function ResponseViewerInner({
|
|
1168
1284
|
schema,
|
|
1169
1285
|
responses,
|
|
1170
1286
|
onResponseSelect,
|
|
1171
1287
|
height = "500px",
|
|
1172
|
-
width = "100%"
|
|
1288
|
+
width = "100%",
|
|
1289
|
+
filename,
|
|
1290
|
+
dateFormat,
|
|
1291
|
+
columnLabels,
|
|
1292
|
+
onExport,
|
|
1293
|
+
pageSize = 25
|
|
1173
1294
|
}) {
|
|
1174
1295
|
const [viewMode, setViewMode] = react.useState("table");
|
|
1175
1296
|
const [selectedResponse, setSelectedResponse] = react.useState(null);
|
|
1297
|
+
const [currentPage, setCurrentPage] = react.useState(1);
|
|
1298
|
+
const [effectivePageSize, setEffectivePageSize] = react.useState(pageSize);
|
|
1299
|
+
const [filterState, setFilterState] = react.useState(createEmptyFilterState);
|
|
1300
|
+
const [showFilterPanel, setShowFilterPanel] = react.useState(false);
|
|
1301
|
+
const [draftFieldId, setDraftFieldId] = react.useState("");
|
|
1302
|
+
const [draftOperator, setDraftOperator] = react.useState("contains");
|
|
1303
|
+
const [draftValue, setDraftValue] = react.useState("");
|
|
1304
|
+
react.useEffect(() => {
|
|
1305
|
+
setCurrentPage(1);
|
|
1306
|
+
}, [responses]);
|
|
1307
|
+
react.useEffect(() => {
|
|
1308
|
+
setEffectivePageSize(pageSize);
|
|
1309
|
+
setCurrentPage(1);
|
|
1310
|
+
}, [pageSize]);
|
|
1311
|
+
const isFiltered = hasActiveFilters(filterState);
|
|
1312
|
+
const filteredResponses = applyFilters(responses, filterState);
|
|
1313
|
+
const pag = paginate(filteredResponses, currentPage, effectivePageSize);
|
|
1176
1314
|
function handleSelect(response) {
|
|
1177
1315
|
setSelectedResponse(response);
|
|
1178
1316
|
onResponseSelect?.(response);
|
|
@@ -1180,6 +1318,45 @@ function ResponseViewerInner({
|
|
|
1180
1318
|
function handleBack() {
|
|
1181
1319
|
setSelectedResponse(null);
|
|
1182
1320
|
}
|
|
1321
|
+
function handlePageSizeChange(newSize) {
|
|
1322
|
+
setEffectivePageSize(Number(newSize));
|
|
1323
|
+
setCurrentPage(1);
|
|
1324
|
+
}
|
|
1325
|
+
function handleAddFilter() {
|
|
1326
|
+
if (!draftFieldId) return;
|
|
1327
|
+
if (operatorNeedsValue(draftOperator) && !draftValue) return;
|
|
1328
|
+
const newFilter = {
|
|
1329
|
+
fieldId: draftFieldId,
|
|
1330
|
+
operator: draftOperator,
|
|
1331
|
+
value: draftOperator === "gt" || draftOperator === "lt" ? Number(draftValue) : draftValue
|
|
1332
|
+
};
|
|
1333
|
+
setFilterState((prev) => ({
|
|
1334
|
+
...prev,
|
|
1335
|
+
filters: [...prev.filters, newFilter]
|
|
1336
|
+
}));
|
|
1337
|
+
setDraftFieldId("");
|
|
1338
|
+
setDraftOperator("contains");
|
|
1339
|
+
setDraftValue("");
|
|
1340
|
+
setCurrentPage(1);
|
|
1341
|
+
}
|
|
1342
|
+
function handleRemoveFilter(index) {
|
|
1343
|
+
setFilterState((prev) => ({
|
|
1344
|
+
...prev,
|
|
1345
|
+
filters: prev.filters.filter((_, i) => i !== index)
|
|
1346
|
+
}));
|
|
1347
|
+
setCurrentPage(1);
|
|
1348
|
+
}
|
|
1349
|
+
function handleDateRangeChange(field, value) {
|
|
1350
|
+
setFilterState((prev) => ({
|
|
1351
|
+
...prev,
|
|
1352
|
+
dateRange: { ...prev.dateRange, [field]: value || void 0 }
|
|
1353
|
+
}));
|
|
1354
|
+
setCurrentPage(1);
|
|
1355
|
+
}
|
|
1356
|
+
function handleClearAllFilters() {
|
|
1357
|
+
setFilterState(createEmptyFilterState());
|
|
1358
|
+
setCurrentPage(1);
|
|
1359
|
+
}
|
|
1183
1360
|
const questions = getAllQuestions2(schema);
|
|
1184
1361
|
function getFields(response) {
|
|
1185
1362
|
return questions.map((q) => ({
|
|
@@ -1197,12 +1374,8 @@ function ResponseViewerInner({
|
|
|
1197
1374
|
className: "flex flex-col border border-border rounded-lg overflow-hidden bg-background text-foreground",
|
|
1198
1375
|
style: { height: heightValue, width: widthValue },
|
|
1199
1376
|
children: [
|
|
1200
|
-
!selectedResponse && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex
|
|
1201
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1202
|
-
responses.length,
|
|
1203
|
-
" response",
|
|
1204
|
-
responses.length !== 1 ? "s" : ""
|
|
1205
|
-
] }),
|
|
1377
|
+
!selectedResponse && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-b border-border", children: [
|
|
1378
|
+
/* @__PURE__ */ jsxRuntime.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" : ""}` }),
|
|
1206
1379
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1207
1380
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1208
1381
|
fieldcraftReact.Button,
|
|
@@ -1229,15 +1402,50 @@ function ResponseViewerInner({
|
|
|
1229
1402
|
onClick: () => setViewMode("card"),
|
|
1230
1403
|
children: "Cards"
|
|
1231
1404
|
}
|
|
1405
|
+
)
|
|
1406
|
+
] }),
|
|
1407
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
|
|
1408
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1409
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1410
|
+
fieldcraftReact.Button,
|
|
1411
|
+
{
|
|
1412
|
+
variant: showFilterPanel ? "secondary" : "outline",
|
|
1413
|
+
size: "sm",
|
|
1414
|
+
className: cn(
|
|
1415
|
+
"fc-rv-filter__button h-7 text-xs",
|
|
1416
|
+
showFilterPanel && "font-semibold border border-ring"
|
|
1417
|
+
),
|
|
1418
|
+
onClick: () => setShowFilterPanel((v) => !v),
|
|
1419
|
+
children: [
|
|
1420
|
+
"Filter",
|
|
1421
|
+
isFiltered ? ` (${filterState.filters.length + (filterState.dateRange.from || filterState.dateRange.to ? 1 : 0)})` : ""
|
|
1422
|
+
]
|
|
1423
|
+
}
|
|
1232
1424
|
),
|
|
1233
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1425
|
+
isFiltered && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1426
|
+
fieldcraftReact.Button,
|
|
1427
|
+
{
|
|
1428
|
+
variant: "ghost",
|
|
1429
|
+
size: "sm",
|
|
1430
|
+
className: "h-7 text-xs text-destructive",
|
|
1431
|
+
onClick: handleClearAllFilters,
|
|
1432
|
+
children: "Clear all"
|
|
1433
|
+
}
|
|
1434
|
+
)
|
|
1435
|
+
] }),
|
|
1436
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
|
|
1437
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1234
1438
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1235
1439
|
fieldcraftReact.Button,
|
|
1236
1440
|
{
|
|
1237
1441
|
variant: "outline",
|
|
1238
1442
|
size: "sm",
|
|
1239
1443
|
className: "h-7 text-xs",
|
|
1240
|
-
onClick: () =>
|
|
1444
|
+
onClick: () => {
|
|
1445
|
+
const csvFilename = filename ? filename.replace(/\.\w+$/, ".csv") : "responses.csv";
|
|
1446
|
+
exportToCsv(schema, responses, csvFilename, { dateFormat, columnLabels });
|
|
1447
|
+
onExport?.("csv", responses.length);
|
|
1448
|
+
},
|
|
1241
1449
|
disabled: responses.length === 0,
|
|
1242
1450
|
children: "Export CSV"
|
|
1243
1451
|
}
|
|
@@ -1248,14 +1456,146 @@ function ResponseViewerInner({
|
|
|
1248
1456
|
variant: "outline",
|
|
1249
1457
|
size: "sm",
|
|
1250
1458
|
className: "h-7 text-xs",
|
|
1251
|
-
onClick: () =>
|
|
1459
|
+
onClick: () => {
|
|
1460
|
+
const jsonFilename = filename ? filename.replace(/\.\w+$/, ".json") : "responses.json";
|
|
1461
|
+
exportToJson(responses, jsonFilename);
|
|
1462
|
+
onExport?.("json", responses.length);
|
|
1463
|
+
},
|
|
1252
1464
|
disabled: responses.length === 0,
|
|
1253
1465
|
children: "Export JSON"
|
|
1254
1466
|
}
|
|
1255
1467
|
)
|
|
1256
1468
|
] })
|
|
1257
1469
|
] }),
|
|
1258
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1470
|
+
!selectedResponse && showFilterPanel && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-filter__panel px-3 py-2 border-b border-border bg-muted/50", children: [
|
|
1471
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-2", children: [
|
|
1472
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground w-16 shrink-0", children: "Date:" }),
|
|
1473
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1474
|
+
fieldcraftReact.Input,
|
|
1475
|
+
{
|
|
1476
|
+
type: "date",
|
|
1477
|
+
className: "h-7 text-xs w-36",
|
|
1478
|
+
value: filterState.dateRange.from ?? "",
|
|
1479
|
+
onChange: (e) => handleDateRangeChange("from", e.target.value)
|
|
1480
|
+
}
|
|
1481
|
+
),
|
|
1482
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
1483
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1484
|
+
fieldcraftReact.Input,
|
|
1485
|
+
{
|
|
1486
|
+
type: "date",
|
|
1487
|
+
className: "h-7 text-xs w-36",
|
|
1488
|
+
value: filterState.dateRange.to ?? "",
|
|
1489
|
+
onChange: (e) => handleDateRangeChange("to", e.target.value)
|
|
1490
|
+
}
|
|
1491
|
+
)
|
|
1492
|
+
] }),
|
|
1493
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1494
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground w-16 shrink-0", children: "Field:" }),
|
|
1495
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1496
|
+
fieldcraftReact.Select,
|
|
1497
|
+
{
|
|
1498
|
+
value: draftFieldId || void 0,
|
|
1499
|
+
onValueChange: (val) => {
|
|
1500
|
+
setDraftFieldId(val);
|
|
1501
|
+
const q = questions.find((q2) => q2.id === val);
|
|
1502
|
+
if (q) {
|
|
1503
|
+
const ops = getOperatorsForType(q.type);
|
|
1504
|
+
setDraftOperator(ops[0].value);
|
|
1505
|
+
}
|
|
1506
|
+
setDraftValue("");
|
|
1507
|
+
},
|
|
1508
|
+
children: [
|
|
1509
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectTrigger, { size: "sm", className: "h-7 text-xs w-40", children: /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectValue, { placeholder: "Select field" }) }),
|
|
1510
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectContent, { children: questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectItem, { value: q.id, children: q.label }, q.id)) })
|
|
1511
|
+
]
|
|
1512
|
+
}
|
|
1513
|
+
),
|
|
1514
|
+
draftFieldId && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1515
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1516
|
+
fieldcraftReact.Select,
|
|
1517
|
+
{
|
|
1518
|
+
value: draftOperator,
|
|
1519
|
+
onValueChange: (val) => setDraftOperator(val),
|
|
1520
|
+
children: [
|
|
1521
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectTrigger, { size: "sm", className: "h-7 text-xs w-32", children: /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectValue, {}) }),
|
|
1522
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectContent, { children: getOperatorsForType(
|
|
1523
|
+
questions.find((q) => q.id === draftFieldId)?.type ?? "short_text"
|
|
1524
|
+
).map((op) => /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectItem, { value: op.value, children: op.label }, op.value)) })
|
|
1525
|
+
]
|
|
1526
|
+
}
|
|
1527
|
+
),
|
|
1528
|
+
operatorNeedsValue(draftOperator) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1529
|
+
fieldcraftReact.Input,
|
|
1530
|
+
{
|
|
1531
|
+
className: "h-7 text-xs w-32",
|
|
1532
|
+
placeholder: "Value",
|
|
1533
|
+
value: draftValue,
|
|
1534
|
+
onChange: (e) => setDraftValue(e.target.value),
|
|
1535
|
+
onKeyDown: (e) => e.key === "Enter" && handleAddFilter()
|
|
1536
|
+
}
|
|
1537
|
+
),
|
|
1538
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1539
|
+
fieldcraftReact.Button,
|
|
1540
|
+
{
|
|
1541
|
+
variant: "outline",
|
|
1542
|
+
size: "sm",
|
|
1543
|
+
className: "h-7 text-xs",
|
|
1544
|
+
onClick: handleAddFilter,
|
|
1545
|
+
children: "Add"
|
|
1546
|
+
}
|
|
1547
|
+
)
|
|
1548
|
+
] })
|
|
1549
|
+
] })
|
|
1550
|
+
] }),
|
|
1551
|
+
!selectedResponse && isFiltered && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-3 py-1.5 border-b border-border flex-wrap", children: [
|
|
1552
|
+
filterState.dateRange.from && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1553
|
+
"span",
|
|
1554
|
+
{
|
|
1555
|
+
className: "fc-rv-filter__chip inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-accent text-xs text-foreground cursor-pointer hover:bg-destructive/20",
|
|
1556
|
+
onClick: () => handleDateRangeChange("from", ""),
|
|
1557
|
+
children: [
|
|
1558
|
+
"From: ",
|
|
1559
|
+
filterState.dateRange.from,
|
|
1560
|
+
" \xD7"
|
|
1561
|
+
]
|
|
1562
|
+
}
|
|
1563
|
+
),
|
|
1564
|
+
filterState.dateRange.to && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1565
|
+
"span",
|
|
1566
|
+
{
|
|
1567
|
+
className: "fc-rv-filter__chip inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-accent text-xs text-foreground cursor-pointer hover:bg-destructive/20",
|
|
1568
|
+
onClick: () => handleDateRangeChange("to", ""),
|
|
1569
|
+
children: [
|
|
1570
|
+
"To: ",
|
|
1571
|
+
filterState.dateRange.to,
|
|
1572
|
+
" \xD7"
|
|
1573
|
+
]
|
|
1574
|
+
}
|
|
1575
|
+
),
|
|
1576
|
+
filterState.filters.map((f, i) => {
|
|
1577
|
+
const q = questions.find((q2) => q2.id === f.fieldId);
|
|
1578
|
+
const label = q?.label ?? f.fieldId;
|
|
1579
|
+
const opLabel = f.operator.replace("_", " ");
|
|
1580
|
+
const valueStr = operatorNeedsValue(f.operator) ? ` "${f.value}"` : "";
|
|
1581
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1582
|
+
"span",
|
|
1583
|
+
{
|
|
1584
|
+
className: "fc-rv-filter__chip inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-accent text-xs text-foreground cursor-pointer hover:bg-destructive/20",
|
|
1585
|
+
onClick: () => handleRemoveFilter(i),
|
|
1586
|
+
children: [
|
|
1587
|
+
label,
|
|
1588
|
+
" ",
|
|
1589
|
+
opLabel,
|
|
1590
|
+
valueStr,
|
|
1591
|
+
" \xD7"
|
|
1592
|
+
]
|
|
1593
|
+
},
|
|
1594
|
+
i
|
|
1595
|
+
);
|
|
1596
|
+
})
|
|
1597
|
+
] }),
|
|
1598
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex-1 min-w-0 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1259
1599
|
ResponseDetail,
|
|
1260
1600
|
{
|
|
1261
1601
|
response: selectedResponse,
|
|
@@ -1266,11 +1606,11 @@ function ResponseViewerInner({
|
|
|
1266
1606
|
ResponseTable,
|
|
1267
1607
|
{
|
|
1268
1608
|
schema,
|
|
1269
|
-
responses,
|
|
1609
|
+
responses: pag.pageItems,
|
|
1270
1610
|
onRowClick: handleSelect
|
|
1271
1611
|
}
|
|
1272
1612
|
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 p-3 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]", children: [
|
|
1273
|
-
|
|
1613
|
+
pag.pageItems.map((response, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1274
1614
|
ResponseCard,
|
|
1275
1615
|
{
|
|
1276
1616
|
response,
|
|
@@ -1279,8 +1619,63 @@ function ResponseViewerInner({
|
|
|
1279
1619
|
},
|
|
1280
1620
|
response.sessionToken || idx
|
|
1281
1621
|
)),
|
|
1282
|
-
|
|
1283
|
-
] }) })
|
|
1622
|
+
filteredResponses.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-muted-foreground", children: isFiltered ? "No matching responses" : "No responses yet" })
|
|
1623
|
+
] }) }),
|
|
1624
|
+
!selectedResponse && pag.showPagination && /* @__PURE__ */ jsxRuntime.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: [
|
|
1625
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination__info text-xs text-muted-foreground mr-auto", children: [
|
|
1626
|
+
"Showing ",
|
|
1627
|
+
pag.startItem,
|
|
1628
|
+
"\u2013",
|
|
1629
|
+
pag.endItem,
|
|
1630
|
+
" of ",
|
|
1631
|
+
pag.totalItems,
|
|
1632
|
+
" responses"
|
|
1633
|
+
] }),
|
|
1634
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1635
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination__size-select flex items-center gap-1", children: [
|
|
1636
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Per page:" }),
|
|
1637
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1638
|
+
fieldcraftReact.Select,
|
|
1639
|
+
{
|
|
1640
|
+
value: String(effectivePageSize),
|
|
1641
|
+
onValueChange: handlePageSizeChange,
|
|
1642
|
+
children: [
|
|
1643
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectTrigger, { size: "sm", className: "h-7 w-16 text-xs", children: /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectValue, {}) }),
|
|
1644
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectContent, { children: PAGE_SIZE_OPTIONS.map((size) => /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectItem, { value: String(size), children: size }, size)) })
|
|
1645
|
+
]
|
|
1646
|
+
}
|
|
1647
|
+
)
|
|
1648
|
+
] }),
|
|
1649
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
1650
|
+
"Page ",
|
|
1651
|
+
pag.currentPage,
|
|
1652
|
+
" of ",
|
|
1653
|
+
pag.totalPages
|
|
1654
|
+
] }),
|
|
1655
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1656
|
+
fieldcraftReact.Button,
|
|
1657
|
+
{
|
|
1658
|
+
variant: "outline",
|
|
1659
|
+
size: "sm",
|
|
1660
|
+
className: "fc-rv-pagination__button h-7 text-xs",
|
|
1661
|
+
onClick: () => setCurrentPage((p) => p - 1),
|
|
1662
|
+
disabled: pag.currentPage <= 1,
|
|
1663
|
+
children: "Previous"
|
|
1664
|
+
}
|
|
1665
|
+
),
|
|
1666
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1667
|
+
fieldcraftReact.Button,
|
|
1668
|
+
{
|
|
1669
|
+
variant: "outline",
|
|
1670
|
+
size: "sm",
|
|
1671
|
+
className: "fc-rv-pagination__button h-7 text-xs",
|
|
1672
|
+
onClick: () => setCurrentPage((p) => p + 1),
|
|
1673
|
+
disabled: pag.currentPage >= pag.totalPages,
|
|
1674
|
+
children: "Next"
|
|
1675
|
+
}
|
|
1676
|
+
)
|
|
1677
|
+
] })
|
|
1678
|
+
] })
|
|
1284
1679
|
]
|
|
1285
1680
|
}
|
|
1286
1681
|
);
|