@squaredr/fieldcraft-pro 1.1.1 → 1.3.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/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 +540 -33
- package/dist/index.mjs +3 -3
- 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 +7 -7
package/dist/index.js
CHANGED
|
@@ -3444,6 +3444,17 @@ function FormBuilderCore(props) {
|
|
|
3444
3444
|
const builderState = useBuilderState(initialSchema);
|
|
3445
3445
|
const dragDrop = useDragDrop(builderState);
|
|
3446
3446
|
const fileInputRef = react.useRef(null);
|
|
3447
|
+
const [mobilePanel, setMobilePanel] = react.useState("none");
|
|
3448
|
+
react.useEffect(() => {
|
|
3449
|
+
if (builderState.selectedItem && window.innerWidth < 768) {
|
|
3450
|
+
setMobilePanel("properties");
|
|
3451
|
+
}
|
|
3452
|
+
}, [builderState.selectedItem]);
|
|
3453
|
+
react.useEffect(() => {
|
|
3454
|
+
if (!dragDrop.activeDragItem && mobilePanel === "palette") {
|
|
3455
|
+
setMobilePanel("none");
|
|
3456
|
+
}
|
|
3457
|
+
}, [dragDrop.activeDragItem]);
|
|
3447
3458
|
react.useEffect(() => {
|
|
3448
3459
|
if (onChange && builderState.isDirty) {
|
|
3449
3460
|
onChange(builderState.schema);
|
|
@@ -3565,7 +3576,7 @@ ${details}`);
|
|
|
3565
3576
|
role: "application",
|
|
3566
3577
|
"aria-label": "Form Builder",
|
|
3567
3578
|
children: [
|
|
3568
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-12 shrink-0 grid px-4 bg-card border-b border-border", style: { gridTemplateColumns: "1fr auto 1fr" }, role: "toolbar", "aria-label": "Builder toolbar", children: [
|
|
3579
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-12 shrink-0 hidden md:grid px-4 bg-card border-b border-border", style: { gridTemplateColumns: "1fr auto 1fr" }, role: "toolbar", "aria-label": "Builder toolbar", children: [
|
|
3569
3580
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3570
3581
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3571
3582
|
fieldcraftReact.Button,
|
|
@@ -3625,23 +3636,124 @@ ${details}`);
|
|
|
3625
3636
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 14, strokeWidth: 2 }),
|
|
3626
3637
|
"Save"
|
|
3627
3638
|
] })
|
|
3639
|
+
] })
|
|
3640
|
+
] }),
|
|
3641
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 md:hidden bg-card border-b border-border", role: "toolbar", "aria-label": "Builder toolbar", children: [
|
|
3642
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-10 grid px-2", style: { gridTemplateColumns: "auto 1fr auto" }, children: [
|
|
3643
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3644
|
+
fieldcraftReact.Button,
|
|
3645
|
+
{
|
|
3646
|
+
variant: mobilePanel === "palette" ? "secondary" : "ghost",
|
|
3647
|
+
size: "icon-sm",
|
|
3648
|
+
onClick: () => setMobilePanel((p) => p === "palette" ? "none" : "palette"),
|
|
3649
|
+
title: "Toggle field palette",
|
|
3650
|
+
"aria-label": "Toggle field palette",
|
|
3651
|
+
children: mobilePanel === "palette" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, strokeWidth: 1.75 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeft, { size: 16, strokeWidth: 1.75 })
|
|
3652
|
+
}
|
|
3653
|
+
) }),
|
|
3654
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-1", children: [
|
|
3655
|
+
toolbarExtra,
|
|
3656
|
+
/* @__PURE__ */ jsxRuntime.jsxs(fieldcraftReact.Button, { onClick: handleSave, size: "sm", className: "border-0 shadow-sm fcb-glow ml-1", "aria-label": "Save form", children: [
|
|
3657
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 14, strokeWidth: 2 }),
|
|
3658
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Save" })
|
|
3659
|
+
] })
|
|
3660
|
+
] }),
|
|
3661
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3662
|
+
fieldcraftReact.Button,
|
|
3663
|
+
{
|
|
3664
|
+
variant: mobilePanel === "properties" ? "secondary" : "ghost",
|
|
3665
|
+
size: "icon-sm",
|
|
3666
|
+
onClick: () => setMobilePanel((p) => p === "properties" ? "none" : "properties"),
|
|
3667
|
+
title: "Toggle properties panel",
|
|
3668
|
+
"aria-label": "Toggle properties panel",
|
|
3669
|
+
children: mobilePanel === "properties" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, strokeWidth: 1.75 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelRight, { size: 16, strokeWidth: 1.75 })
|
|
3670
|
+
}
|
|
3671
|
+
) })
|
|
3628
3672
|
] }),
|
|
3629
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3630
|
-
"
|
|
3673
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-9 flex items-center justify-between px-2 border-t border-border/50", children: [
|
|
3674
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3675
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3676
|
+
fieldcraftReact.Button,
|
|
3677
|
+
{
|
|
3678
|
+
variant: "ghost",
|
|
3679
|
+
size: "icon-sm",
|
|
3680
|
+
onClick: builderState.undo,
|
|
3681
|
+
disabled: !builderState.canUndo,
|
|
3682
|
+
className: "disabled:opacity-30",
|
|
3683
|
+
title: "Undo (Ctrl+Z)",
|
|
3684
|
+
"aria-label": "Undo",
|
|
3685
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Undo2, { size: 15, strokeWidth: 1.75 })
|
|
3686
|
+
}
|
|
3687
|
+
),
|
|
3688
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3689
|
+
fieldcraftReact.Button,
|
|
3690
|
+
{
|
|
3691
|
+
variant: "ghost",
|
|
3692
|
+
size: "icon-sm",
|
|
3693
|
+
onClick: builderState.redo,
|
|
3694
|
+
disabled: !builderState.canRedo,
|
|
3695
|
+
className: "disabled:opacity-30",
|
|
3696
|
+
title: "Redo (Ctrl+Shift+Z)",
|
|
3697
|
+
"aria-label": "Redo",
|
|
3698
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Redo2, { size: 15, strokeWidth: 1.75 })
|
|
3699
|
+
}
|
|
3700
|
+
),
|
|
3701
|
+
builderState.isDirty && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3702
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "mx-1 h-4" }),
|
|
3703
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-primary", role: "status", children: "Unsaved" })
|
|
3704
|
+
] })
|
|
3705
|
+
] }),
|
|
3706
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3707
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3708
|
+
fieldcraftReact.Button,
|
|
3709
|
+
{
|
|
3710
|
+
variant: "ghost",
|
|
3711
|
+
size: "icon-sm",
|
|
3712
|
+
onClick: handleImport,
|
|
3713
|
+
title: "Import schema JSON",
|
|
3714
|
+
"aria-label": "Import schema",
|
|
3715
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { size: 14, strokeWidth: 1.75 })
|
|
3716
|
+
}
|
|
3717
|
+
),
|
|
3718
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3719
|
+
fieldcraftReact.Button,
|
|
3720
|
+
{
|
|
3721
|
+
variant: "ghost",
|
|
3722
|
+
size: "icon-sm",
|
|
3723
|
+
onClick: handleExport,
|
|
3724
|
+
title: "Export schema JSON",
|
|
3725
|
+
"aria-label": "Export schema",
|
|
3726
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 14, strokeWidth: 1.75 })
|
|
3727
|
+
}
|
|
3728
|
+
)
|
|
3729
|
+
] })
|
|
3730
|
+
] })
|
|
3731
|
+
] }),
|
|
3732
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3733
|
+
"input",
|
|
3734
|
+
{
|
|
3735
|
+
ref: fileInputRef,
|
|
3736
|
+
type: "file",
|
|
3737
|
+
accept: ".json,application/json",
|
|
3738
|
+
onChange: handleFileChange,
|
|
3739
|
+
className: "hidden",
|
|
3740
|
+
"aria-hidden": "true"
|
|
3741
|
+
}
|
|
3742
|
+
),
|
|
3743
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex overflow-hidden relative", children: [
|
|
3744
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsxRuntime.jsx(QuestionPalette, { questionTypes: mergedQuestionTypes, palette }) }),
|
|
3745
|
+
/* @__PURE__ */ jsxRuntime.jsx(FormCanvas, { builderState }),
|
|
3746
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsxRuntime.jsx(PropertiesPanel, { builderState }) }),
|
|
3747
|
+
mobilePanel === "palette" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 z-20 md:hidden shadow-lg", children: /* @__PURE__ */ jsxRuntime.jsx(QuestionPalette, { questionTypes: mergedQuestionTypes, palette }) }),
|
|
3748
|
+
mobilePanel === "properties" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 z-20 md:hidden shadow-lg", children: /* @__PURE__ */ jsxRuntime.jsx(PropertiesPanel, { builderState }) }),
|
|
3749
|
+
mobilePanel !== "none" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3750
|
+
"div",
|
|
3631
3751
|
{
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
accept: ".json,application/json",
|
|
3635
|
-
onChange: handleFileChange,
|
|
3636
|
-
className: "hidden",
|
|
3752
|
+
className: "absolute inset-0 z-10 bg-black/20 md:hidden",
|
|
3753
|
+
onClick: () => setMobilePanel("none"),
|
|
3637
3754
|
"aria-hidden": "true"
|
|
3638
3755
|
}
|
|
3639
3756
|
)
|
|
3640
|
-
] }),
|
|
3641
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex overflow-hidden", children: [
|
|
3642
|
-
/* @__PURE__ */ jsxRuntime.jsx(QuestionPalette, { questionTypes: mergedQuestionTypes, palette }),
|
|
3643
|
-
/* @__PURE__ */ jsxRuntime.jsx(FormCanvas, { builderState }),
|
|
3644
|
-
/* @__PURE__ */ jsxRuntime.jsx(PropertiesPanel, { builderState })
|
|
3645
3757
|
] })
|
|
3646
3758
|
]
|
|
3647
3759
|
}
|
|
@@ -4501,16 +4613,24 @@ function formatFallbackValue(value) {
|
|
|
4501
4613
|
}
|
|
4502
4614
|
|
|
4503
4615
|
// src/response-viewer/export-utils.ts
|
|
4504
|
-
function
|
|
4616
|
+
function buildCsvContent(schema, responses, options = {}) {
|
|
4617
|
+
const { dateFormat = "locale", columnLabels } = options;
|
|
4505
4618
|
const questions = getExportableQuestions(schema);
|
|
4506
|
-
const headers = [
|
|
4619
|
+
const headers = [
|
|
4620
|
+
"Submitted",
|
|
4621
|
+
...questions.map((q) => columnLabels?.[q.id] ?? q.label),
|
|
4622
|
+
"Score"
|
|
4623
|
+
];
|
|
4507
4624
|
const rows = responses.map((r) => {
|
|
4508
|
-
const submitted = new Date(r.submittedAt).toLocaleString();
|
|
4625
|
+
const submitted = dateFormat === "iso" ? new Date(r.submittedAt).toISOString() : new Date(r.submittedAt).toLocaleString();
|
|
4509
4626
|
const values = questions.map((q) => formatExportValue(r.values[q.id], q.type));
|
|
4510
4627
|
const score = r.totalScore != null ? String(r.totalScore) : "";
|
|
4511
4628
|
return [submitted, ...values, score];
|
|
4512
4629
|
});
|
|
4513
|
-
|
|
4630
|
+
return [headers, ...rows].map((row) => row.map(escapeCsvField).join(",")).join("\n");
|
|
4631
|
+
}
|
|
4632
|
+
function exportToCsv(schema, responses, filename = "responses.csv", options = {}) {
|
|
4633
|
+
const csvContent = buildCsvContent(schema, responses, options);
|
|
4514
4634
|
downloadBlob(csvContent, filename, "text/csv;charset=utf-8;");
|
|
4515
4635
|
}
|
|
4516
4636
|
function exportToJson(responses, filename = "responses.json") {
|
|
@@ -4605,15 +4725,145 @@ function downloadBlob(content, filename, mimeType) {
|
|
|
4605
4725
|
document.body.removeChild(link);
|
|
4606
4726
|
URL.revokeObjectURL(url);
|
|
4607
4727
|
}
|
|
4728
|
+
|
|
4729
|
+
// src/response-viewer/pagination-utils.ts
|
|
4730
|
+
function paginate(items, currentPage, pageSize) {
|
|
4731
|
+
const totalItems = items.length;
|
|
4732
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
4733
|
+
const page = Math.max(1, Math.min(currentPage, totalPages));
|
|
4734
|
+
const startIndex = (page - 1) * pageSize;
|
|
4735
|
+
const endIndex = Math.min(startIndex + pageSize, totalItems);
|
|
4736
|
+
const pageItems = items.slice(startIndex, endIndex);
|
|
4737
|
+
return {
|
|
4738
|
+
pageItems,
|
|
4739
|
+
currentPage: page,
|
|
4740
|
+
totalPages,
|
|
4741
|
+
startItem: totalItems === 0 ? 0 : startIndex + 1,
|
|
4742
|
+
endItem: endIndex,
|
|
4743
|
+
totalItems,
|
|
4744
|
+
showPagination: totalItems > pageSize
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
// src/response-viewer/filter-utils.ts
|
|
4749
|
+
function createEmptyFilterState() {
|
|
4750
|
+
return { filters: [], dateRange: {} };
|
|
4751
|
+
}
|
|
4752
|
+
function hasActiveFilters(state) {
|
|
4753
|
+
return state.filters.length > 0 || !!state.dateRange.from || !!state.dateRange.to;
|
|
4754
|
+
}
|
|
4755
|
+
function applyFilters(responses, state) {
|
|
4756
|
+
if (!hasActiveFilters(state)) return responses;
|
|
4757
|
+
return responses.filter((r) => {
|
|
4758
|
+
if (!matchesDateRange(r.submittedAt, state.dateRange)) return false;
|
|
4759
|
+
for (const filter of state.filters) {
|
|
4760
|
+
if (!matchesFieldFilter(r.values[filter.fieldId], filter)) return false;
|
|
4761
|
+
}
|
|
4762
|
+
return true;
|
|
4763
|
+
});
|
|
4764
|
+
}
|
|
4765
|
+
function matchesDateRange(submittedAt, range) {
|
|
4766
|
+
if (!range.from && !range.to) return true;
|
|
4767
|
+
const submitted = new Date(submittedAt);
|
|
4768
|
+
if (range.from) {
|
|
4769
|
+
const from = new Date(range.from);
|
|
4770
|
+
from.setHours(0, 0, 0, 0);
|
|
4771
|
+
if (submitted < from) return false;
|
|
4772
|
+
}
|
|
4773
|
+
if (range.to) {
|
|
4774
|
+
const to = new Date(range.to);
|
|
4775
|
+
to.setHours(23, 59, 59, 999);
|
|
4776
|
+
if (submitted > to) return false;
|
|
4777
|
+
}
|
|
4778
|
+
return true;
|
|
4779
|
+
}
|
|
4780
|
+
function matchesFieldFilter(value, filter) {
|
|
4781
|
+
switch (filter.operator) {
|
|
4782
|
+
case "equals":
|
|
4783
|
+
if (value == null) return false;
|
|
4784
|
+
return String(value).toLowerCase() === String(filter.value).toLowerCase();
|
|
4785
|
+
case "contains":
|
|
4786
|
+
if (value == null) return false;
|
|
4787
|
+
return String(value).toLowerCase().includes(String(filter.value).toLowerCase());
|
|
4788
|
+
case "gt":
|
|
4789
|
+
if (typeof value !== "number") return false;
|
|
4790
|
+
return value > Number(filter.value);
|
|
4791
|
+
case "lt":
|
|
4792
|
+
if (typeof value !== "number") return false;
|
|
4793
|
+
return value < Number(filter.value);
|
|
4794
|
+
case "is_true":
|
|
4795
|
+
return value === true || value === "true" || value === "agreed";
|
|
4796
|
+
case "is_false":
|
|
4797
|
+
return value === false || value === "false" || value == null;
|
|
4798
|
+
default:
|
|
4799
|
+
return true;
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
var PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
4803
|
+
var FIELD_FILTER_OPERATORS = {
|
|
4804
|
+
text: [
|
|
4805
|
+
{ label: "equals", value: "equals" },
|
|
4806
|
+
{ label: "contains", value: "contains" }
|
|
4807
|
+
],
|
|
4808
|
+
number: [
|
|
4809
|
+
{ label: "equals", value: "equals" },
|
|
4810
|
+
{ label: "greater than", value: "gt" },
|
|
4811
|
+
{ label: "less than", value: "lt" }
|
|
4812
|
+
],
|
|
4813
|
+
boolean: [
|
|
4814
|
+
{ label: "is true", value: "is_true" },
|
|
4815
|
+
{ label: "is false", value: "is_false" }
|
|
4816
|
+
],
|
|
4817
|
+
select: [
|
|
4818
|
+
{ label: "equals", value: "equals" }
|
|
4819
|
+
]
|
|
4820
|
+
};
|
|
4821
|
+
function getOperatorsForType(type) {
|
|
4822
|
+
if (type === "number" || type === "slider" || type === "rating" || type === "nps" || type === "opinion_scale" || type === "pain_scale") {
|
|
4823
|
+
return FIELD_FILTER_OPERATORS.number;
|
|
4824
|
+
}
|
|
4825
|
+
if (type === "boolean" || type === "consent") {
|
|
4826
|
+
return FIELD_FILTER_OPERATORS.boolean;
|
|
4827
|
+
}
|
|
4828
|
+
if (type === "single_select" || type === "dropdown" || type === "country_select") {
|
|
4829
|
+
return FIELD_FILTER_OPERATORS.select;
|
|
4830
|
+
}
|
|
4831
|
+
return FIELD_FILTER_OPERATORS.text;
|
|
4832
|
+
}
|
|
4833
|
+
function operatorNeedsValue(operator) {
|
|
4834
|
+
return operator !== "is_true" && operator !== "is_false";
|
|
4835
|
+
}
|
|
4608
4836
|
function ResponseViewerInner({
|
|
4609
4837
|
schema,
|
|
4610
4838
|
responses,
|
|
4611
4839
|
onResponseSelect,
|
|
4612
4840
|
height = "500px",
|
|
4613
|
-
width = "100%"
|
|
4841
|
+
width = "100%",
|
|
4842
|
+
filename,
|
|
4843
|
+
dateFormat,
|
|
4844
|
+
columnLabels,
|
|
4845
|
+
onExport,
|
|
4846
|
+
pageSize = 25
|
|
4614
4847
|
}) {
|
|
4615
4848
|
const [viewMode, setViewMode] = react.useState("table");
|
|
4616
4849
|
const [selectedResponse, setSelectedResponse] = react.useState(null);
|
|
4850
|
+
const [currentPage, setCurrentPage] = react.useState(1);
|
|
4851
|
+
const [effectivePageSize, setEffectivePageSize] = react.useState(pageSize);
|
|
4852
|
+
const [filterState, setFilterState] = react.useState(createEmptyFilterState);
|
|
4853
|
+
const [showFilterPanel, setShowFilterPanel] = react.useState(false);
|
|
4854
|
+
const [draftFieldId, setDraftFieldId] = react.useState("");
|
|
4855
|
+
const [draftOperator, setDraftOperator] = react.useState("contains");
|
|
4856
|
+
const [draftValue, setDraftValue] = react.useState("");
|
|
4857
|
+
react.useEffect(() => {
|
|
4858
|
+
setCurrentPage(1);
|
|
4859
|
+
}, [responses]);
|
|
4860
|
+
react.useEffect(() => {
|
|
4861
|
+
setEffectivePageSize(pageSize);
|
|
4862
|
+
setCurrentPage(1);
|
|
4863
|
+
}, [pageSize]);
|
|
4864
|
+
const isFiltered = hasActiveFilters(filterState);
|
|
4865
|
+
const filteredResponses = applyFilters(responses, filterState);
|
|
4866
|
+
const pag = paginate(filteredResponses, currentPage, effectivePageSize);
|
|
4617
4867
|
function handleSelect(response) {
|
|
4618
4868
|
setSelectedResponse(response);
|
|
4619
4869
|
onResponseSelect?.(response);
|
|
@@ -4621,6 +4871,45 @@ function ResponseViewerInner({
|
|
|
4621
4871
|
function handleBack() {
|
|
4622
4872
|
setSelectedResponse(null);
|
|
4623
4873
|
}
|
|
4874
|
+
function handlePageSizeChange(newSize) {
|
|
4875
|
+
setEffectivePageSize(Number(newSize));
|
|
4876
|
+
setCurrentPage(1);
|
|
4877
|
+
}
|
|
4878
|
+
function handleAddFilter() {
|
|
4879
|
+
if (!draftFieldId) return;
|
|
4880
|
+
if (operatorNeedsValue(draftOperator) && !draftValue) return;
|
|
4881
|
+
const newFilter = {
|
|
4882
|
+
fieldId: draftFieldId,
|
|
4883
|
+
operator: draftOperator,
|
|
4884
|
+
value: draftOperator === "gt" || draftOperator === "lt" ? Number(draftValue) : draftValue
|
|
4885
|
+
};
|
|
4886
|
+
setFilterState((prev) => ({
|
|
4887
|
+
...prev,
|
|
4888
|
+
filters: [...prev.filters, newFilter]
|
|
4889
|
+
}));
|
|
4890
|
+
setDraftFieldId("");
|
|
4891
|
+
setDraftOperator("contains");
|
|
4892
|
+
setDraftValue("");
|
|
4893
|
+
setCurrentPage(1);
|
|
4894
|
+
}
|
|
4895
|
+
function handleRemoveFilter(index) {
|
|
4896
|
+
setFilterState((prev) => ({
|
|
4897
|
+
...prev,
|
|
4898
|
+
filters: prev.filters.filter((_, i) => i !== index)
|
|
4899
|
+
}));
|
|
4900
|
+
setCurrentPage(1);
|
|
4901
|
+
}
|
|
4902
|
+
function handleDateRangeChange(field, value) {
|
|
4903
|
+
setFilterState((prev) => ({
|
|
4904
|
+
...prev,
|
|
4905
|
+
dateRange: { ...prev.dateRange, [field]: value || void 0 }
|
|
4906
|
+
}));
|
|
4907
|
+
setCurrentPage(1);
|
|
4908
|
+
}
|
|
4909
|
+
function handleClearAllFilters() {
|
|
4910
|
+
setFilterState(createEmptyFilterState());
|
|
4911
|
+
setCurrentPage(1);
|
|
4912
|
+
}
|
|
4624
4913
|
const questions = getAllQuestions2(schema);
|
|
4625
4914
|
function getFields(response) {
|
|
4626
4915
|
return questions.map((q) => ({
|
|
@@ -4638,12 +4927,8 @@ function ResponseViewerInner({
|
|
|
4638
4927
|
className: "flex flex-col border border-border rounded-lg overflow-hidden bg-background text-foreground",
|
|
4639
4928
|
style: { height: heightValue, width: widthValue },
|
|
4640
4929
|
children: [
|
|
4641
|
-
!selectedResponse && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex
|
|
4642
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4643
|
-
responses.length,
|
|
4644
|
-
" response",
|
|
4645
|
-
responses.length !== 1 ? "s" : ""
|
|
4646
|
-
] }),
|
|
4930
|
+
!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: [
|
|
4931
|
+
/* @__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" : ""}` }),
|
|
4647
4932
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
4648
4933
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4649
4934
|
fieldcraftReact.Button,
|
|
@@ -4670,15 +4955,50 @@ function ResponseViewerInner({
|
|
|
4670
4955
|
onClick: () => setViewMode("card"),
|
|
4671
4956
|
children: "Cards"
|
|
4672
4957
|
}
|
|
4958
|
+
)
|
|
4959
|
+
] }),
|
|
4960
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
|
|
4961
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
4962
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4963
|
+
fieldcraftReact.Button,
|
|
4964
|
+
{
|
|
4965
|
+
variant: showFilterPanel ? "secondary" : "outline",
|
|
4966
|
+
size: "sm",
|
|
4967
|
+
className: cn(
|
|
4968
|
+
"fc-rv-filter__button h-7 text-xs",
|
|
4969
|
+
showFilterPanel && "font-semibold border border-ring"
|
|
4970
|
+
),
|
|
4971
|
+
onClick: () => setShowFilterPanel((v) => !v),
|
|
4972
|
+
children: [
|
|
4973
|
+
"Filter",
|
|
4974
|
+
isFiltered ? ` (${filterState.filters.length + (filterState.dateRange.from || filterState.dateRange.to ? 1 : 0)})` : ""
|
|
4975
|
+
]
|
|
4976
|
+
}
|
|
4673
4977
|
),
|
|
4674
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4978
|
+
isFiltered && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4979
|
+
fieldcraftReact.Button,
|
|
4980
|
+
{
|
|
4981
|
+
variant: "ghost",
|
|
4982
|
+
size: "sm",
|
|
4983
|
+
className: "h-7 text-xs text-destructive",
|
|
4984
|
+
onClick: handleClearAllFilters,
|
|
4985
|
+
children: "Clear all"
|
|
4986
|
+
}
|
|
4987
|
+
)
|
|
4988
|
+
] }),
|
|
4989
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
|
|
4990
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
4675
4991
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4676
4992
|
fieldcraftReact.Button,
|
|
4677
4993
|
{
|
|
4678
4994
|
variant: "outline",
|
|
4679
4995
|
size: "sm",
|
|
4680
4996
|
className: "h-7 text-xs",
|
|
4681
|
-
onClick: () =>
|
|
4997
|
+
onClick: () => {
|
|
4998
|
+
const csvFilename = filename ? filename.replace(/\.\w+$/, ".csv") : "responses.csv";
|
|
4999
|
+
exportToCsv(schema, responses, csvFilename, { dateFormat, columnLabels });
|
|
5000
|
+
onExport?.("csv", responses.length);
|
|
5001
|
+
},
|
|
4682
5002
|
disabled: responses.length === 0,
|
|
4683
5003
|
children: "Export CSV"
|
|
4684
5004
|
}
|
|
@@ -4689,14 +5009,146 @@ function ResponseViewerInner({
|
|
|
4689
5009
|
variant: "outline",
|
|
4690
5010
|
size: "sm",
|
|
4691
5011
|
className: "h-7 text-xs",
|
|
4692
|
-
onClick: () =>
|
|
5012
|
+
onClick: () => {
|
|
5013
|
+
const jsonFilename = filename ? filename.replace(/\.\w+$/, ".json") : "responses.json";
|
|
5014
|
+
exportToJson(responses, jsonFilename);
|
|
5015
|
+
onExport?.("json", responses.length);
|
|
5016
|
+
},
|
|
4693
5017
|
disabled: responses.length === 0,
|
|
4694
5018
|
children: "Export JSON"
|
|
4695
5019
|
}
|
|
4696
5020
|
)
|
|
4697
5021
|
] })
|
|
4698
5022
|
] }),
|
|
4699
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5023
|
+
!selectedResponse && showFilterPanel && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-filter__panel px-3 py-2 border-b border-border bg-muted/50", children: [
|
|
5024
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-2", children: [
|
|
5025
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground w-16 shrink-0", children: "Date:" }),
|
|
5026
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5027
|
+
fieldcraftReact.Input,
|
|
5028
|
+
{
|
|
5029
|
+
type: "date",
|
|
5030
|
+
className: "h-7 text-xs w-36",
|
|
5031
|
+
value: filterState.dateRange.from ?? "",
|
|
5032
|
+
onChange: (e) => handleDateRangeChange("from", e.target.value)
|
|
5033
|
+
}
|
|
5034
|
+
),
|
|
5035
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
5036
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5037
|
+
fieldcraftReact.Input,
|
|
5038
|
+
{
|
|
5039
|
+
type: "date",
|
|
5040
|
+
className: "h-7 text-xs w-36",
|
|
5041
|
+
value: filterState.dateRange.to ?? "",
|
|
5042
|
+
onChange: (e) => handleDateRangeChange("to", e.target.value)
|
|
5043
|
+
}
|
|
5044
|
+
)
|
|
5045
|
+
] }),
|
|
5046
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
5047
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground w-16 shrink-0", children: "Field:" }),
|
|
5048
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5049
|
+
fieldcraftReact.Select,
|
|
5050
|
+
{
|
|
5051
|
+
value: draftFieldId || void 0,
|
|
5052
|
+
onValueChange: (val) => {
|
|
5053
|
+
setDraftFieldId(val);
|
|
5054
|
+
const q = questions.find((q2) => q2.id === val);
|
|
5055
|
+
if (q) {
|
|
5056
|
+
const ops = getOperatorsForType(q.type);
|
|
5057
|
+
setDraftOperator(ops[0].value);
|
|
5058
|
+
}
|
|
5059
|
+
setDraftValue("");
|
|
5060
|
+
},
|
|
5061
|
+
children: [
|
|
5062
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectTrigger, { size: "sm", className: "h-7 text-xs w-40", children: /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectValue, { placeholder: "Select field" }) }),
|
|
5063
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectContent, { children: questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectItem, { value: q.id, children: q.label }, q.id)) })
|
|
5064
|
+
]
|
|
5065
|
+
}
|
|
5066
|
+
),
|
|
5067
|
+
draftFieldId && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5068
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5069
|
+
fieldcraftReact.Select,
|
|
5070
|
+
{
|
|
5071
|
+
value: draftOperator,
|
|
5072
|
+
onValueChange: (val) => setDraftOperator(val),
|
|
5073
|
+
children: [
|
|
5074
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectTrigger, { size: "sm", className: "h-7 text-xs w-32", children: /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectValue, {}) }),
|
|
5075
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectContent, { children: getOperatorsForType(
|
|
5076
|
+
questions.find((q) => q.id === draftFieldId)?.type ?? "short_text"
|
|
5077
|
+
).map((op) => /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectItem, { value: op.value, children: op.label }, op.value)) })
|
|
5078
|
+
]
|
|
5079
|
+
}
|
|
5080
|
+
),
|
|
5081
|
+
operatorNeedsValue(draftOperator) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5082
|
+
fieldcraftReact.Input,
|
|
5083
|
+
{
|
|
5084
|
+
className: "h-7 text-xs w-32",
|
|
5085
|
+
placeholder: "Value",
|
|
5086
|
+
value: draftValue,
|
|
5087
|
+
onChange: (e) => setDraftValue(e.target.value),
|
|
5088
|
+
onKeyDown: (e) => e.key === "Enter" && handleAddFilter()
|
|
5089
|
+
}
|
|
5090
|
+
),
|
|
5091
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5092
|
+
fieldcraftReact.Button,
|
|
5093
|
+
{
|
|
5094
|
+
variant: "outline",
|
|
5095
|
+
size: "sm",
|
|
5096
|
+
className: "h-7 text-xs",
|
|
5097
|
+
onClick: handleAddFilter,
|
|
5098
|
+
children: "Add"
|
|
5099
|
+
}
|
|
5100
|
+
)
|
|
5101
|
+
] })
|
|
5102
|
+
] })
|
|
5103
|
+
] }),
|
|
5104
|
+
!selectedResponse && isFiltered && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-3 py-1.5 border-b border-border flex-wrap", children: [
|
|
5105
|
+
filterState.dateRange.from && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5106
|
+
"span",
|
|
5107
|
+
{
|
|
5108
|
+
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",
|
|
5109
|
+
onClick: () => handleDateRangeChange("from", ""),
|
|
5110
|
+
children: [
|
|
5111
|
+
"From: ",
|
|
5112
|
+
filterState.dateRange.from,
|
|
5113
|
+
" \xD7"
|
|
5114
|
+
]
|
|
5115
|
+
}
|
|
5116
|
+
),
|
|
5117
|
+
filterState.dateRange.to && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5118
|
+
"span",
|
|
5119
|
+
{
|
|
5120
|
+
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",
|
|
5121
|
+
onClick: () => handleDateRangeChange("to", ""),
|
|
5122
|
+
children: [
|
|
5123
|
+
"To: ",
|
|
5124
|
+
filterState.dateRange.to,
|
|
5125
|
+
" \xD7"
|
|
5126
|
+
]
|
|
5127
|
+
}
|
|
5128
|
+
),
|
|
5129
|
+
filterState.filters.map((f, i) => {
|
|
5130
|
+
const q = questions.find((q2) => q2.id === f.fieldId);
|
|
5131
|
+
const label = q?.label ?? f.fieldId;
|
|
5132
|
+
const opLabel = f.operator.replace("_", " ");
|
|
5133
|
+
const valueStr = operatorNeedsValue(f.operator) ? ` "${f.value}"` : "";
|
|
5134
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5135
|
+
"span",
|
|
5136
|
+
{
|
|
5137
|
+
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",
|
|
5138
|
+
onClick: () => handleRemoveFilter(i),
|
|
5139
|
+
children: [
|
|
5140
|
+
label,
|
|
5141
|
+
" ",
|
|
5142
|
+
opLabel,
|
|
5143
|
+
valueStr,
|
|
5144
|
+
" \xD7"
|
|
5145
|
+
]
|
|
5146
|
+
},
|
|
5147
|
+
i
|
|
5148
|
+
);
|
|
5149
|
+
})
|
|
5150
|
+
] }),
|
|
5151
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex-1 min-w-0 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4700
5152
|
ResponseDetail,
|
|
4701
5153
|
{
|
|
4702
5154
|
response: selectedResponse,
|
|
@@ -4707,11 +5159,11 @@ function ResponseViewerInner({
|
|
|
4707
5159
|
ResponseTable,
|
|
4708
5160
|
{
|
|
4709
5161
|
schema,
|
|
4710
|
-
responses,
|
|
5162
|
+
responses: pag.pageItems,
|
|
4711
5163
|
onRowClick: handleSelect
|
|
4712
5164
|
}
|
|
4713
5165
|
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 p-3 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]", children: [
|
|
4714
|
-
|
|
5166
|
+
pag.pageItems.map((response, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4715
5167
|
ResponseCard,
|
|
4716
5168
|
{
|
|
4717
5169
|
response,
|
|
@@ -4720,8 +5172,63 @@ function ResponseViewerInner({
|
|
|
4720
5172
|
},
|
|
4721
5173
|
response.sessionToken || idx
|
|
4722
5174
|
)),
|
|
4723
|
-
|
|
4724
|
-
] }) })
|
|
5175
|
+
filteredResponses.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-muted-foreground", children: isFiltered ? "No matching responses" : "No responses yet" })
|
|
5176
|
+
] }) }),
|
|
5177
|
+
!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: [
|
|
5178
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination__info text-xs text-muted-foreground mr-auto", children: [
|
|
5179
|
+
"Showing ",
|
|
5180
|
+
pag.startItem,
|
|
5181
|
+
"\u2013",
|
|
5182
|
+
pag.endItem,
|
|
5183
|
+
" of ",
|
|
5184
|
+
pag.totalItems,
|
|
5185
|
+
" responses"
|
|
5186
|
+
] }),
|
|
5187
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5188
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination__size-select flex items-center gap-1", children: [
|
|
5189
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Per page:" }),
|
|
5190
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5191
|
+
fieldcraftReact.Select,
|
|
5192
|
+
{
|
|
5193
|
+
value: String(effectivePageSize),
|
|
5194
|
+
onValueChange: handlePageSizeChange,
|
|
5195
|
+
children: [
|
|
5196
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectTrigger, { size: "sm", className: "h-7 w-16 text-xs", children: /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectValue, {}) }),
|
|
5197
|
+
/* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectContent, { children: PAGE_SIZE_OPTIONS.map((size) => /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.SelectItem, { value: String(size), children: size }, size)) })
|
|
5198
|
+
]
|
|
5199
|
+
}
|
|
5200
|
+
)
|
|
5201
|
+
] }),
|
|
5202
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
5203
|
+
"Page ",
|
|
5204
|
+
pag.currentPage,
|
|
5205
|
+
" of ",
|
|
5206
|
+
pag.totalPages
|
|
5207
|
+
] }),
|
|
5208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5209
|
+
fieldcraftReact.Button,
|
|
5210
|
+
{
|
|
5211
|
+
variant: "outline",
|
|
5212
|
+
size: "sm",
|
|
5213
|
+
className: "fc-rv-pagination__button h-7 text-xs",
|
|
5214
|
+
onClick: () => setCurrentPage((p) => p - 1),
|
|
5215
|
+
disabled: pag.currentPage <= 1,
|
|
5216
|
+
children: "Previous"
|
|
5217
|
+
}
|
|
5218
|
+
),
|
|
5219
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5220
|
+
fieldcraftReact.Button,
|
|
5221
|
+
{
|
|
5222
|
+
variant: "outline",
|
|
5223
|
+
size: "sm",
|
|
5224
|
+
className: "fc-rv-pagination__button h-7 text-xs",
|
|
5225
|
+
onClick: () => setCurrentPage((p) => p + 1),
|
|
5226
|
+
disabled: pag.currentPage >= pag.totalPages,
|
|
5227
|
+
children: "Next"
|
|
5228
|
+
}
|
|
5229
|
+
)
|
|
5230
|
+
] })
|
|
5231
|
+
] })
|
|
4725
5232
|
]
|
|
4726
5233
|
}
|
|
4727
5234
|
);
|
|
@@ -5146,7 +5653,7 @@ var ThemeEditor = requireLicense(ThemeEditorInner, "ThemeEditor");
|
|
|
5146
5653
|
// src/index.ts
|
|
5147
5654
|
if (typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV !== "production") {
|
|
5148
5655
|
const _fc_banner = `
|
|
5149
|
-
%c FieldCraft Pro %c v1.0
|
|
5656
|
+
%c FieldCraft Pro %c v1.3.0
|
|
5150
5657
|
|
|
5151
5658
|
%cForm Builder \xB7 Response Viewer \xB7 Theme Editor
|
|
5152
5659
|
|