@micha.bigler/survey-renderer 0.1.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/SurveyCompletionLayout.js +26 -0
- package/dist/SurveyDiscoverySelect.js +271 -0
- package/dist/SurveyFilteredPlaceSelect.js +74 -0
- package/dist/SurveyGeoSelect.js +361 -0
- package/dist/SurveyMapSelect.js +5 -0
- package/dist/SurveyQuestionHeader.js +50 -0
- package/dist/SurveyRenderer.js +985 -0
- package/dist/discoveryDatasets.js +17 -0
- package/dist/discoveryEngine.js +160 -0
- package/dist/geoDatasets.js +17 -0
- package/dist/i18n/surveyRendererTranslations.js +242 -0
- package/dist/imageDownscale.js +64 -0
- package/dist/index.js +11 -0
- package/dist/localizeSurvey.js +24 -0
- package/dist/questionDefaults.js +13 -0
- package/dist/questionUiConfig.js +43 -0
- package/dist/stagedGroups.js +258 -0
- package/dist/types.js +22 -0
- package/package.json +36 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Button, Paper, Stack, Typography } from "@mui/material";
|
|
4
|
+
const SurveyCompletionLayout = ({ actionButtons = [], body = "", headline = "", primaryCard = null, resourceCard = null, rootId = "survey-completion-layout", }) => (_jsx(Paper, { id: rootId, elevation: 0, sx: {
|
|
5
|
+
border: "1px solid rgba(20, 83, 45, 0.08)",
|
|
6
|
+
boxShadow: "none",
|
|
7
|
+
p: { xs: 3, md: 4 },
|
|
8
|
+
}, children: _jsxs(Stack, { spacing: 3, children: [_jsxs(Stack, { spacing: 1.25, children: [_jsx(Typography, { variant: "h3", children: headline }), body ? (_jsx(Typography, { color: "text.secondary", sx: { maxWidth: 980, whiteSpace: "pre-line" }, children: body })) : null] }), primaryCard || resourceCard ? (_jsxs(Box, { sx: {
|
|
9
|
+
display: "grid",
|
|
10
|
+
gap: 2,
|
|
11
|
+
gridTemplateColumns: {
|
|
12
|
+
xs: "1fr",
|
|
13
|
+
md: primaryCard && resourceCard ? "minmax(0, 1.6fr) minmax(0, 1fr)" : "1fr",
|
|
14
|
+
},
|
|
15
|
+
}, children: [primaryCard ? (_jsx(Paper, { elevation: 0, sx: {
|
|
16
|
+
backgroundColor: "rgba(255, 255, 255, 0.26)",
|
|
17
|
+
border: "1px solid rgba(17, 17, 17, 0.08)",
|
|
18
|
+
borderRadius: 3,
|
|
19
|
+
p: { xs: 2, md: 2.5 },
|
|
20
|
+
}, children: _jsxs(Stack, { spacing: 1.5, children: [primaryCard.title ? _jsx(Typography, { variant: "h5", children: primaryCard.title }) : null, primaryCard.body ? (_jsx(Typography, { color: "text.secondary", sx: { maxWidth: 920, whiteSpace: "pre-line" }, children: primaryCard.body })) : null, primaryCard.note ? (_jsx(Typography, { color: "text.secondary", sx: { maxWidth: 920, whiteSpace: "pre-line" }, children: primaryCard.note })) : null, Array.isArray(primaryCard.actions) && primaryCard.actions.length > 0 ? (_jsx(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1.5, children: primaryCard.actions.map((action) => (_jsx(Button, { component: action.href ? "a" : "button", href: action.href, onClick: action.onClick, rel: action.href ? "noreferrer" : undefined, sx: { minHeight: 52, px: 3 }, target: action.href ? action.target || "_self" : undefined, variant: action.variant || "contained", children: action.label }, action.key))) })) : null] }) })) : null, resourceCard ? (_jsx(Paper, { elevation: 0, sx: {
|
|
21
|
+
backgroundColor: "rgba(16, 84, 43, 0.04)",
|
|
22
|
+
border: "1px solid rgba(17, 17, 17, 0.08)",
|
|
23
|
+
borderRadius: 3,
|
|
24
|
+
p: { xs: 2, md: 2.5 },
|
|
25
|
+
}, children: _jsxs(Stack, { spacing: 1.25, children: [resourceCard.title ? _jsx(Typography, { variant: "h6", children: resourceCard.title }) : null, resourceCard.body ? (_jsx(Typography, { color: "text.secondary", sx: { whiteSpace: "pre-line" }, children: resourceCard.body })) : null, resourceCard.action ? (_jsx(Button, { component: "a", href: resourceCard.action.href, rel: "noreferrer", sx: { alignSelf: "flex-start", minHeight: 48, px: 2.5 }, target: "_blank", variant: "outlined", children: resourceCard.action.label })) : null] }) })) : null] })) : null, actionButtons.length > 0 ? (_jsx(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1.5, children: actionButtons.map((action) => (_jsx(Button, { component: action.href ? "a" : "button", href: action.href, onClick: action.onClick, rel: action.href ? "noreferrer" : undefined, sx: { minHeight: 56, px: 3 }, target: action.href ? action.target || "_self" : undefined, variant: action.variant || "outlined", children: action.label }, action.key))) })) : null] }) }));
|
|
26
|
+
export default SurveyCompletionLayout;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { createElement as _createElement } from "react";
|
|
13
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
14
|
+
import React from "react";
|
|
15
|
+
import CheckRoundedIcon from "@mui/icons-material/CheckRounded";
|
|
16
|
+
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
|
|
17
|
+
import { Alert, Autocomplete, Box, Button, Chip, Paper, Stack, TextField, Typography } from "@mui/material";
|
|
18
|
+
import { useTranslation } from "react-i18next";
|
|
19
|
+
import { getDiscoveryDataset } from "./discoveryDatasets";
|
|
20
|
+
import { buildDiscoveryDeck, buildDiscoveryItems, filterDiscoveryItemsByGate, resolveDiscoveryStrategy, serializeDiscoveryValue, } from "./discoveryEngine";
|
|
21
|
+
import SurveyGeoSelect from "./SurveyGeoSelect";
|
|
22
|
+
import SurveyQuestionHeader from "./SurveyQuestionHeader";
|
|
23
|
+
const SWIPE_THRESHOLD = 110;
|
|
24
|
+
function DiscoveryCard({ active, item, onSkip, onKeep }) {
|
|
25
|
+
const [dragX, setDragX] = React.useState(0);
|
|
26
|
+
const [dragging, setDragging] = React.useState(false);
|
|
27
|
+
const [imageFailed, setImageFailed] = React.useState(false);
|
|
28
|
+
const startXRef = React.useRef(0);
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
setDragX(0);
|
|
31
|
+
setDragging(false);
|
|
32
|
+
setImageFailed(false);
|
|
33
|
+
}, [item === null || item === void 0 ? void 0 : item.imageUrl, item === null || item === void 0 ? void 0 : item.value]);
|
|
34
|
+
const rotation = dragX / 16;
|
|
35
|
+
const showImage = Boolean(item === null || item === void 0 ? void 0 : item.imageUrl) && !imageFailed;
|
|
36
|
+
const handlePointerDown = (event) => {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
if (!active) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
startXRef.current = event.clientX;
|
|
42
|
+
setDragging(true);
|
|
43
|
+
(_b = (_a = event.currentTarget).setPointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, event.pointerId);
|
|
44
|
+
};
|
|
45
|
+
const handlePointerMove = (event) => {
|
|
46
|
+
if (!dragging || !active) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
setDragX(event.clientX - startXRef.current);
|
|
50
|
+
};
|
|
51
|
+
const handlePointerEnd = () => {
|
|
52
|
+
if (!active) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const delta = dragX;
|
|
56
|
+
setDragging(false);
|
|
57
|
+
setDragX(0);
|
|
58
|
+
if (delta >= SWIPE_THRESHOLD) {
|
|
59
|
+
onKeep(item.value);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (delta <= -SWIPE_THRESHOLD) {
|
|
63
|
+
onSkip(item.value);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (!item) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return (_jsx(Paper, { elevation: 0, onPointerCancel: handlePointerEnd, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerEnd, sx: {
|
|
70
|
+
border: "1px solid rgba(17, 17, 17, 0.08)",
|
|
71
|
+
borderRadius: 4,
|
|
72
|
+
cursor: active ? "grab" : "default",
|
|
73
|
+
overflow: "hidden",
|
|
74
|
+
position: "relative",
|
|
75
|
+
touchAction: "pan-y",
|
|
76
|
+
transform: active ? `translateX(${dragX}px) rotate(${rotation}deg)` : "scale(1)",
|
|
77
|
+
transition: dragging ? "none" : "transform 220ms ease, opacity 220ms ease",
|
|
78
|
+
userSelect: "none",
|
|
79
|
+
}, children: _jsxs(Box, { sx: {
|
|
80
|
+
aspectRatio: "16 / 9",
|
|
81
|
+
alignItems: "flex-end",
|
|
82
|
+
background: "#f7eef3",
|
|
83
|
+
color: "#171117",
|
|
84
|
+
display: "flex",
|
|
85
|
+
minHeight: { xs: 260, sm: 320 },
|
|
86
|
+
p: { xs: 1.25, sm: 2 },
|
|
87
|
+
position: "relative",
|
|
88
|
+
width: "100%",
|
|
89
|
+
}, children: [showImage ? (_jsx(Box, { alt: item.imageAlt || item.label, component: "img", onError: () => setImageFailed(true), src: item.imageUrl, sx: {
|
|
90
|
+
height: "100%",
|
|
91
|
+
inset: 0,
|
|
92
|
+
objectFit: "cover",
|
|
93
|
+
position: "absolute",
|
|
94
|
+
width: "100%",
|
|
95
|
+
} })) : null, _jsx(Box, { sx: {
|
|
96
|
+
background: showImage
|
|
97
|
+
? "linear-gradient(180deg, rgba(10,10,10,0.05) 0%, rgba(10,10,10,0.72) 100%)"
|
|
98
|
+
: "none",
|
|
99
|
+
inset: 0,
|
|
100
|
+
position: "absolute",
|
|
101
|
+
} }), _jsx(Stack, { justifyContent: "flex-end", spacing: 1, sx: { position: "relative", width: "100%", zIndex: 1 }, children: _jsx(Typography, { sx: {
|
|
102
|
+
color: showImage ? "#ffffff" : "#171117",
|
|
103
|
+
fontSize: { xs: 28, sm: 34 },
|
|
104
|
+
fontWeight: 700,
|
|
105
|
+
lineHeight: 1.05,
|
|
106
|
+
}, children: item.label }) })] }) }));
|
|
107
|
+
}
|
|
108
|
+
function buildNextQueue(items, state, strategy) {
|
|
109
|
+
return buildDiscoveryDeck(items, state, strategy);
|
|
110
|
+
}
|
|
111
|
+
function selectedGateLabels(gateOptions, gateValues) {
|
|
112
|
+
const selectedValueKeys = new Set((gateValues || []).map(serializeDiscoveryValue));
|
|
113
|
+
return (gateOptions || [])
|
|
114
|
+
.filter((option) => selectedValueKeys.has(serializeDiscoveryValue(option.value)))
|
|
115
|
+
.map((option) => option.label || "");
|
|
116
|
+
}
|
|
117
|
+
const SurveyDiscoverySelect = ({ allOptionsByName, errorText, hint, label, onChange, options, uiConfig, value }) => {
|
|
118
|
+
const { t } = useTranslation();
|
|
119
|
+
const dataset = React.useMemo(() => getDiscoveryDataset(uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.datasetKey), [uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.datasetKey]);
|
|
120
|
+
const gatingConfig = (uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.gating) || null;
|
|
121
|
+
const hasGateStep = (gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.type) === "geo-select";
|
|
122
|
+
const strategy = React.useMemo(() => resolveDiscoveryStrategy((dataset === null || dataset === void 0 ? void 0 : dataset.strategy) || {}, (uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.strategy) || {}, {
|
|
123
|
+
deckSize: uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.deckSize,
|
|
124
|
+
maxSelections: uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.maxSelections,
|
|
125
|
+
randomShare: uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.randomShare,
|
|
126
|
+
}), [dataset === null || dataset === void 0 ? void 0 : dataset.strategy, uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.deckSize, uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.maxSelections, uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.randomShare, uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.strategy]);
|
|
127
|
+
const items = React.useMemo(() => buildDiscoveryItems(options, dataset), [dataset, options]);
|
|
128
|
+
const gateOptions = React.useMemo(() => (hasGateStep ? (allOptionsByName === null || allOptionsByName === void 0 ? void 0 : allOptionsByName[gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.sourceQuestionName]) || [] : []), [allOptionsByName, gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.sourceQuestionName, hasGateStep]);
|
|
129
|
+
const [gateValues, setGateValues] = React.useState([]);
|
|
130
|
+
const [seenValues, setSeenValues] = React.useState([]);
|
|
131
|
+
const [dismissedValues, setDismissedValues] = React.useState([]);
|
|
132
|
+
const [queue, setQueue] = React.useState([]);
|
|
133
|
+
const [queueCursor, setQueueCursor] = React.useState(0);
|
|
134
|
+
const preservedNextValueKeyRef = React.useRef("");
|
|
135
|
+
const previousGateKeyRef = React.useRef("");
|
|
136
|
+
const selectedValues = Array.isArray(value) ? value : [];
|
|
137
|
+
const selectedValueKeys = React.useMemo(() => selectedValues.map(serializeDiscoveryValue), [selectedValues]);
|
|
138
|
+
const seenValueKeys = React.useMemo(() => seenValues.map(serializeDiscoveryValue), [seenValues]);
|
|
139
|
+
const dismissedValueKeys = React.useMemo(() => dismissedValues.map(serializeDiscoveryValue), [dismissedValues]);
|
|
140
|
+
const gateLabels = React.useMemo(() => selectedGateLabels(gateOptions, gateValues), [gateOptions, gateValues]);
|
|
141
|
+
const filteredItems = React.useMemo(() => {
|
|
142
|
+
if (!hasGateStep) {
|
|
143
|
+
return items;
|
|
144
|
+
}
|
|
145
|
+
return filterDiscoveryItemsByGate(items, gateLabels, gatingConfig);
|
|
146
|
+
}, [gateLabels, gatingConfig, hasGateStep, items]);
|
|
147
|
+
const filteredOptionValueKeys = React.useMemo(() => new Set(filteredItems.map((item) => item.valueKey)), [filteredItems]);
|
|
148
|
+
const filteredOptions = React.useMemo(() => options.filter((option) => filteredOptionValueKeys.has(serializeDiscoveryValue(option.value))), [filteredOptionValueKeys, options]);
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
setGateValues([]);
|
|
151
|
+
}, [gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.datasetKey, gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.sourceQuestionName]);
|
|
152
|
+
React.useEffect(() => {
|
|
153
|
+
if (!hasGateStep) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const gateKey = gateValues.map(serializeDiscoveryValue).sort().join("|");
|
|
157
|
+
if (gateKey === previousGateKeyRef.current) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
previousGateKeyRef.current = gateKey;
|
|
161
|
+
preservedNextValueKeyRef.current = "";
|
|
162
|
+
setSeenValues([]);
|
|
163
|
+
setDismissedValues([]);
|
|
164
|
+
setQueue([]);
|
|
165
|
+
setQueueCursor(0);
|
|
166
|
+
const allowedValueKeys = new Set(filteredItems.map((item) => item.valueKey));
|
|
167
|
+
const nextSelectedValues = selectedValues.filter((entry) => allowedValueKeys.has(serializeDiscoveryValue(entry)));
|
|
168
|
+
if (nextSelectedValues.length !== selectedValues.length) {
|
|
169
|
+
onChange(nextSelectedValues);
|
|
170
|
+
}
|
|
171
|
+
}, [filteredItems, gateValues, hasGateStep, onChange, selectedValues]);
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
const nextQueue = buildNextQueue(filteredItems, {
|
|
174
|
+
dismissedValues,
|
|
175
|
+
seenValues,
|
|
176
|
+
selectedValues,
|
|
177
|
+
}, strategy);
|
|
178
|
+
const preservedNextValueKey = preservedNextValueKeyRef.current;
|
|
179
|
+
if (preservedNextValueKey) {
|
|
180
|
+
const preservedIndex = nextQueue.findIndex((item) => item.valueKey === preservedNextValueKey);
|
|
181
|
+
if (preservedIndex > 0) {
|
|
182
|
+
const [preservedItem] = nextQueue.splice(preservedIndex, 1);
|
|
183
|
+
nextQueue.unshift(preservedItem);
|
|
184
|
+
}
|
|
185
|
+
preservedNextValueKeyRef.current = "";
|
|
186
|
+
}
|
|
187
|
+
setQueue(nextQueue);
|
|
188
|
+
setQueueCursor(0);
|
|
189
|
+
}, [dismissedValues, filteredItems, seenValues, selectedValues, strategy]);
|
|
190
|
+
const currentItem = queue[queueCursor] || null;
|
|
191
|
+
const previewItems = queue.slice(queueCursor + 1, queueCursor + 3);
|
|
192
|
+
const selectedItems = filteredItems.filter((item) => selectedValueKeys.includes(item.valueKey));
|
|
193
|
+
const maxReached = selectedValues.length >= strategy.maxSelections;
|
|
194
|
+
const commitSelection = React.useCallback((nextValues) => {
|
|
195
|
+
onChange(nextValues.slice(0, strategy.maxSelections));
|
|
196
|
+
}, [onChange, strategy.maxSelections]);
|
|
197
|
+
const markSeen = React.useCallback((nextValue) => {
|
|
198
|
+
setSeenValues((current) => (current.includes(nextValue) ? current : [...current, nextValue]));
|
|
199
|
+
}, []);
|
|
200
|
+
const moveForward = React.useCallback(() => {
|
|
201
|
+
var _a;
|
|
202
|
+
preservedNextValueKeyRef.current = ((_a = queue[queueCursor + 1]) === null || _a === void 0 ? void 0 : _a.valueKey) || "";
|
|
203
|
+
setQueueCursor((current) => current + 1);
|
|
204
|
+
}, [queue, queueCursor]);
|
|
205
|
+
const handleSkip = React.useCallback((nextValue) => {
|
|
206
|
+
markSeen(nextValue);
|
|
207
|
+
setDismissedValues((current) => (current.includes(nextValue) ? current : [...current, nextValue]));
|
|
208
|
+
moveForward();
|
|
209
|
+
}, [markSeen, moveForward]);
|
|
210
|
+
const handleKeep = React.useCallback((nextValue) => {
|
|
211
|
+
markSeen(nextValue);
|
|
212
|
+
if (!selectedValueKeys.includes(serializeDiscoveryValue(nextValue)) && selectedValues.length < strategy.maxSelections) {
|
|
213
|
+
commitSelection([...selectedValues, nextValue]);
|
|
214
|
+
}
|
|
215
|
+
moveForward();
|
|
216
|
+
}, [commitSelection, markSeen, moveForward, selectedValueKeys, selectedValues, strategy.maxSelections]);
|
|
217
|
+
const handleManualSelection = (_event, nextOptions) => {
|
|
218
|
+
const nextValues = nextOptions.map((option) => option.value).slice(0, strategy.maxSelections);
|
|
219
|
+
commitSelection(nextValues);
|
|
220
|
+
};
|
|
221
|
+
const hasRemainingDiscoverable = filteredItems.some((item) => item.discoverable &&
|
|
222
|
+
!selectedValueKeys.includes(item.valueKey) &&
|
|
223
|
+
!dismissedValueKeys.includes(item.valueKey) &&
|
|
224
|
+
!seenValueKeys.includes(item.valueKey));
|
|
225
|
+
return (_jsxs(Stack, { spacing: 1.5, children: [_jsx(SurveyQuestionHeader, { errorText: errorText, hint: hint, label: label }), hasGateStep ? (_jsx(SurveyGeoSelect, { hint: t("surveyRenderer.DISCOVERY_GATE_HINT"), label: t("surveyRenderer.DISCOVERY_GATE_LABEL", { max: (gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.maxSelections) || 1 }), maxSelections: (gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.maxSelections) || 1, multiple: true, onChange: setGateValues, options: gateOptions, uiConfig: {
|
|
226
|
+
autocompletePlaceholderKey: (gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.autocompletePlaceholderKey) || "surveyRenderer.DISCOVERY_GATE_PLACEHOLDER",
|
|
227
|
+
datasetKey: gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.datasetKey,
|
|
228
|
+
emptyMessageKey: (gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.emptyMessageKey) || "surveyRenderer.DISCOVERY_GATE_EMPTY",
|
|
229
|
+
featureLabelProperty: gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.featureLabelProperty,
|
|
230
|
+
loadingMessageKey: (gatingConfig === null || gatingConfig === void 0 ? void 0 : gatingConfig.loadingMessageKey) || "surveyRenderer.MAP_LOADING",
|
|
231
|
+
}, value: gateValues })) : null, !hasGateStep || gateValues.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Stack, { spacing: 1.5, children: [_jsxs(Stack, { alignItems: { xs: "flex-start", sm: "center" }, direction: { xs: "column", sm: "row" }, justifyContent: "space-between", spacing: 1, children: [_jsx(Box, {}), _jsx(Typography, { color: "text.secondary", variant: "body2", children: t("surveyRenderer.DISCOVERY_HELP") })] }), maxReached ? (_jsx(Alert, { severity: "info", children: t("surveyRenderer.DISCOVERY_MAX_REACHED", { max: strategy.maxSelections }) })) : currentItem ? (_jsxs(Box, { sx: { position: "relative" }, children: [previewItems[1] ? (_jsx(Box, { sx: { left: 0, px: { xs: 1.25, sm: 3 }, position: "absolute", right: 0, top: 22, zIndex: 0 }, children: _jsx(Paper, { elevation: 0, sx: { backgroundColor: "rgba(255, 243, 249, 0.6)", borderRadius: 4, height: 22 } }) })) : null, previewItems[0] ? (_jsx(Box, { sx: { left: 0, px: { xs: 0.75, sm: 1.5 }, position: "absolute", right: 0, top: 12, zIndex: 0 }, children: _jsx(Paper, { elevation: 0, sx: { backgroundColor: "rgba(255, 243, 249, 0.78)", borderRadius: 4, height: 24 } }) })) : null, _jsx(Box, { sx: { position: "relative", zIndex: 1 }, children: _jsx(DiscoveryCard, { active: true, item: currentItem, onKeep: handleKeep, onSkip: handleSkip }) })] })) : (_jsx(Alert, { severity: hasRemainingDiscoverable ? "info" : "success", children: hasRemainingDiscoverable ? t("surveyRenderer.DISCOVERY_REFRESH") : t("surveyRenderer.DISCOVERY_DONE") })), _jsxs(Stack, { direction: "row", spacing: 1.25, sx: {
|
|
232
|
+
display: { xs: "none", sm: "flex" },
|
|
233
|
+
}, children: [_jsx(Button, { disabled: !currentItem || maxReached, onClick: () => currentItem && handleSkip(currentItem.value), startIcon: _jsx(CloseRoundedIcon, {}), sx: {
|
|
234
|
+
backgroundColor: "#f5bfd8",
|
|
235
|
+
color: "#7a2455",
|
|
236
|
+
flex: 1,
|
|
237
|
+
minHeight: 52,
|
|
238
|
+
"&:hover": {
|
|
239
|
+
backgroundColor: "#eeabc9",
|
|
240
|
+
},
|
|
241
|
+
}, variant: "contained", children: t("surveyRenderer.DISCOVERY_SKIP") }), _jsx(Button, { disabled: !currentItem || maxReached, onClick: () => currentItem && handleKeep(currentItem.value), startIcon: _jsx(CheckRoundedIcon, {}), sx: { flex: 1, minHeight: 52 }, variant: "contained", children: t("surveyRenderer.DISCOVERY_SAVE") })] })] }), _jsx(Autocomplete, { fullWidth: true, getOptionLabel: (option) => option.label || "", isOptionEqualToValue: (option, candidate) => option.value === candidate.value, multiple: true, onChange: handleManualSelection, options: filteredOptions, renderTags: (tagValue, getTagProps) => tagValue.map((option, index) => {
|
|
242
|
+
const _a = getTagProps({ index }), { key } = _a, tagProps = __rest(_a, ["key"]);
|
|
243
|
+
return (_createElement(Chip, Object.assign({}, tagProps, { key: key, label: option.label, sx: {
|
|
244
|
+
backgroundColor: "#ffffff",
|
|
245
|
+
border: "1px solid rgba(15, 23, 42, 0.10)",
|
|
246
|
+
borderRadius: 999,
|
|
247
|
+
fontSize: 14,
|
|
248
|
+
fontWeight: 600,
|
|
249
|
+
height: 34,
|
|
250
|
+
px: 0.5,
|
|
251
|
+
".MuiChip-deleteIcon": {
|
|
252
|
+
color: "rgba(15, 23, 42, 0.52)",
|
|
253
|
+
fontSize: 18,
|
|
254
|
+
"&:hover": {
|
|
255
|
+
color: "#111111",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
} })));
|
|
259
|
+
}), renderInput: (params) => (_jsx(TextField, Object.assign({}, params, { placeholder: t("surveyRenderer.DISCOVERY_AUTOCOMPLETE_PLACEHOLDER"), sx: {
|
|
260
|
+
".MuiAutocomplete-tag": {
|
|
261
|
+
margin: "4px 6px 4px 0",
|
|
262
|
+
},
|
|
263
|
+
".MuiOutlinedInput-root": {
|
|
264
|
+
backgroundColor: "rgba(255, 243, 249, 0.96)",
|
|
265
|
+
borderRadius: 3,
|
|
266
|
+
paddingBottom: "8px !important",
|
|
267
|
+
paddingTop: "8px !important",
|
|
268
|
+
},
|
|
269
|
+
} }))), value: selectedItems })] })) : (_jsx(Alert, { severity: "info", children: t("surveyRenderer.DISCOVERY_GATE_REQUIRED") }))] }));
|
|
270
|
+
};
|
|
271
|
+
export default SurveyDiscoverySelect;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import SearchRoundedIcon from "@mui/icons-material/SearchRounded";
|
|
4
|
+
import { Alert, Box, InputAdornment, Stack, TextField, Typography } from "@mui/material";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
6
|
+
import { getDiscoveryDataset } from "./discoveryDatasets";
|
|
7
|
+
import { buildDiscoveryItems, filterDiscoveryItemsByGate, serializeDiscoveryValue } from "./discoveryEngine";
|
|
8
|
+
import SurveyQuestionHeader from "./SurveyQuestionHeader";
|
|
9
|
+
function FilteredChoiceList({ maxSelections, onChange, options, selectedValues }) {
|
|
10
|
+
const selectedValueKeys = new Set((selectedValues || []).map(serializeDiscoveryValue));
|
|
11
|
+
return (_jsx(Stack, { spacing: 1.1, children: options.map((option) => {
|
|
12
|
+
const isSelected = selectedValueKeys.has(serializeDiscoveryValue(option.value));
|
|
13
|
+
return (_jsx(Box, { onClick: () => {
|
|
14
|
+
if (isSelected) {
|
|
15
|
+
onChange(selectedValues.filter((entry) => serializeDiscoveryValue(entry) !== serializeDiscoveryValue(option.value)));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if ((selectedValues || []).length >= maxSelections) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
onChange([...(selectedValues || []), option.value]);
|
|
22
|
+
}, sx: {
|
|
23
|
+
backgroundColor: "rgba(255, 243, 249, 0.96)",
|
|
24
|
+
border: "1px solid",
|
|
25
|
+
borderColor: isSelected ? "primary.main" : "divider",
|
|
26
|
+
borderRadius: 2,
|
|
27
|
+
cursor: "pointer",
|
|
28
|
+
p: 1.35,
|
|
29
|
+
transition: "border-color 150ms ease, background-color 150ms ease",
|
|
30
|
+
bgcolor: isSelected ? "rgba(13, 174, 209, 0.16)" : "rgba(255, 243, 249, 0.96)",
|
|
31
|
+
}, children: _jsxs(Stack, { spacing: 0.25, children: [_jsx(Typography, { children: option.label }), option.hint ? (_jsx(Typography, { color: "text.secondary", sx: { whiteSpace: "pre-line" }, variant: "body2", children: option.hint })) : null] }) }, option.value));
|
|
32
|
+
}) }));
|
|
33
|
+
}
|
|
34
|
+
const SurveyFilteredPlaceSelect = ({ allOptionsByName, answers, errorText, hint, label, onChange, options, uiConfig, value, }) => {
|
|
35
|
+
const { t } = useTranslation();
|
|
36
|
+
const dataset = React.useMemo(() => getDiscoveryDataset(uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.datasetKey), [uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.datasetKey]);
|
|
37
|
+
const sourceQuestionName = (uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.sourceQuestionName) || "";
|
|
38
|
+
const filterKey = (uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.filterKey) || "quarters";
|
|
39
|
+
const maxSelections = Math.max(1, (uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.maxSelections) || 5);
|
|
40
|
+
const sourceOptions = (allOptionsByName === null || allOptionsByName === void 0 ? void 0 : allOptionsByName[sourceQuestionName]) || [];
|
|
41
|
+
const sourceValues = Array.isArray(answers === null || answers === void 0 ? void 0 : answers[sourceQuestionName]) ? answers[sourceQuestionName] : [];
|
|
42
|
+
const selectedSourceValueKeys = React.useMemo(() => new Set(sourceValues.map(serializeDiscoveryValue)), [sourceValues]);
|
|
43
|
+
const selectedSourceLabels = React.useMemo(() => sourceOptions
|
|
44
|
+
.filter((option) => selectedSourceValueKeys.has(serializeDiscoveryValue(option.value)))
|
|
45
|
+
.map((option) => option.label || ""), [selectedSourceValueKeys, sourceOptions]);
|
|
46
|
+
const items = React.useMemo(() => buildDiscoveryItems(options, dataset), [dataset, options]);
|
|
47
|
+
const filteredItems = React.useMemo(() => filterDiscoveryItemsByGate(items, selectedSourceLabels, { filterKey }), [filterKey, items, selectedSourceLabels]);
|
|
48
|
+
const filteredOptionValueKeys = React.useMemo(() => new Set(filteredItems.map((item) => item.valueKey)), [filteredItems]);
|
|
49
|
+
const filteredOptions = React.useMemo(() => options.filter((option) => filteredOptionValueKeys.has(serializeDiscoveryValue(option.value))), [filteredOptionValueKeys, options]);
|
|
50
|
+
const [searchValue, setSearchValue] = React.useState("");
|
|
51
|
+
const selectedValues = Array.isArray(value) ? value : [];
|
|
52
|
+
const visibleOptions = React.useMemo(() => {
|
|
53
|
+
const normalizedSearch = searchValue.trim().toLowerCase();
|
|
54
|
+
if (!normalizedSearch) {
|
|
55
|
+
return filteredOptions;
|
|
56
|
+
}
|
|
57
|
+
return filteredOptions.filter((option) => (option.label || "").toLowerCase().includes(normalizedSearch));
|
|
58
|
+
}, [filteredOptions, searchValue]);
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
const allowedValues = new Set(filteredOptions.map((option) => serializeDiscoveryValue(option.value)));
|
|
61
|
+
const nextSelectedValues = selectedValues.filter((entry) => allowedValues.has(serializeDiscoveryValue(entry)));
|
|
62
|
+
if (nextSelectedValues.length !== selectedValues.length) {
|
|
63
|
+
onChange(nextSelectedValues);
|
|
64
|
+
}
|
|
65
|
+
}, [filteredOptions, onChange, selectedValues]);
|
|
66
|
+
return (_jsxs(Stack, { spacing: 1.5, children: [_jsx(SurveyQuestionHeader, { errorText: errorText, hint: hint, label: label }), selectedSourceLabels.length === 0 ? (_jsx(Alert, { severity: "info", children: t("surveyRenderer.FILTERED_SELECT_REQUIRED") })) : null, selectedSourceLabels.length > 0 ? (_jsxs(_Fragment, { children: [selectedValues.length >= maxSelections ? (_jsx(Alert, { severity: "info", children: t("surveyRenderer.FILTERED_SELECT_MAX_REACHED", { max: maxSelections }) })) : null, !(uiConfig === null || uiConfig === void 0 ? void 0 : uiConfig.hideSearch) && (_jsx(TextField, { fullWidth: true, onChange: (event) => setSearchValue(event.target.value), placeholder: t("surveyRenderer.FILTERED_SELECT_SEARCH_PLACEHOLDER"), sx: {
|
|
67
|
+
".MuiOutlinedInput-root": {
|
|
68
|
+
backgroundColor: "rgba(255, 243, 249, 0.96)",
|
|
69
|
+
},
|
|
70
|
+
}, value: searchValue, InputProps: {
|
|
71
|
+
startAdornment: (_jsx(InputAdornment, { position: "start", children: _jsx(SearchRoundedIcon, { fontSize: "small" }) })),
|
|
72
|
+
} })), visibleOptions.length > 0 ? (_jsx(FilteredChoiceList, { maxSelections: maxSelections, onChange: onChange, options: visibleOptions, selectedValues: selectedValues })) : (_jsx(Alert, { severity: "info", children: t("surveyRenderer.FILTERED_SELECT_EMPTY") }))] })) : null] }));
|
|
73
|
+
};
|
|
74
|
+
export default SurveyFilteredPlaceSelect;
|