@welshare/questionnaire 0.1.1 → 0.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.
Files changed (96) hide show
  1. package/README.md +151 -0
  2. package/dist/esm/components/bmi-form.d.ts +68 -0
  3. package/dist/esm/components/bmi-form.d.ts.map +1 -0
  4. package/dist/esm/components/bmi-form.js +138 -0
  5. package/dist/esm/components/question-renderer.d.ts +6 -1
  6. package/dist/esm/components/question-renderer.d.ts.map +1 -1
  7. package/dist/esm/components/question-renderer.js +49 -14
  8. package/dist/esm/components/questions/decimal-question.d.ts +8 -1
  9. package/dist/esm/components/questions/decimal-question.d.ts.map +1 -1
  10. package/dist/esm/components/questions/decimal-question.js +19 -1
  11. package/dist/esm/components/questions/multiple-choice-question.d.ts.map +1 -1
  12. package/dist/esm/components/questions/multiple-choice-question.js +2 -2
  13. package/dist/esm/contexts/questionnaire-context.d.ts.map +1 -1
  14. package/dist/esm/contexts/questionnaire-context.js +3 -2
  15. package/dist/esm/index.d.ts +6 -2
  16. package/dist/esm/index.d.ts.map +1 -1
  17. package/dist/esm/index.js +5 -1
  18. package/dist/esm/lib/bmi-helpers.d.ts +50 -0
  19. package/dist/esm/lib/bmi-helpers.d.ts.map +1 -0
  20. package/dist/esm/lib/bmi-helpers.js +69 -0
  21. package/dist/esm/lib/constants.d.ts +94 -0
  22. package/dist/esm/lib/constants.d.ts.map +1 -0
  23. package/dist/esm/lib/constants.js +93 -0
  24. package/dist/esm/lib/questionnaire-utils.d.ts +21 -1
  25. package/dist/esm/lib/questionnaire-utils.d.ts.map +1 -1
  26. package/dist/esm/lib/questionnaire-utils.js +85 -4
  27. package/dist/esm/types/fhir.d.ts +1 -0
  28. package/dist/esm/types/fhir.d.ts.map +1 -1
  29. package/dist/esm/types/index.d.ts +25 -0
  30. package/dist/esm/types/index.d.ts.map +1 -1
  31. package/dist/styles.css +108 -0
  32. package/package.json +17 -6
  33. package/dist/node_modules/@welshare/questionnaire/.tshy/build.json +0 -8
  34. package/dist/node_modules/@welshare/questionnaire/.tshy/esm.json +0 -16
  35. package/dist/node_modules/@welshare/questionnaire/LICENSE +0 -7
  36. package/dist/node_modules/@welshare/questionnaire/README.md +0 -173
  37. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.d.ts +0 -44
  38. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.d.ts.map +0 -1
  39. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.js +0 -28
  40. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.d.ts +0 -80
  41. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.d.ts.map +0 -1
  42. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.js +0 -159
  43. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.d.ts +0 -15
  44. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.d.ts.map +0 -1
  45. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.js +0 -19
  46. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.d.ts +0 -19
  47. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.d.ts.map +0 -1
  48. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.js +0 -23
  49. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.d.ts +0 -12
  50. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.d.ts.map +0 -1
  51. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.js +0 -7
  52. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.d.ts +0 -18
  53. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.d.ts.map +0 -1
  54. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.js +0 -24
  55. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.d.ts +0 -20
  56. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.d.ts.map +0 -1
  57. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.js +0 -39
  58. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.d.ts +0 -12
  59. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.d.ts.map +0 -1
  60. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.js +0 -7
  61. package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.d.ts +0 -41
  62. package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.d.ts.map +0 -1
  63. package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.js +0 -350
  64. package/dist/node_modules/@welshare/questionnaire/dist/esm/index.d.ts +0 -7
  65. package/dist/node_modules/@welshare/questionnaire/dist/esm/index.d.ts.map +0 -1
  66. package/dist/node_modules/@welshare/questionnaire/dist/esm/index.js +0 -6
  67. package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.d.ts +0 -33
  68. package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.d.ts.map +0 -1
  69. package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.js +0 -99
  70. package/dist/node_modules/@welshare/questionnaire/dist/esm/package.json +0 -3
  71. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.d.ts +0 -117
  72. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.d.ts.map +0 -1
  73. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.js +0 -3
  74. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.d.ts +0 -51
  75. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.d.ts.map +0 -1
  76. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.js +0 -1
  77. package/dist/node_modules/@welshare/questionnaire/dist/styles.css +0 -467
  78. package/dist/node_modules/@welshare/questionnaire/dist/tokens.css +0 -130
  79. package/dist/node_modules/@welshare/questionnaire/package.json +0 -85
  80. package/dist/node_modules/@welshare/questionnaire/src/components/debug-section.tsx +0 -116
  81. package/dist/node_modules/@welshare/questionnaire/src/components/question-renderer.tsx +0 -368
  82. package/dist/node_modules/@welshare/questionnaire/src/components/questionnaire-styles.css +0 -467
  83. package/dist/node_modules/@welshare/questionnaire/src/components/questionnaire-tokens.css +0 -130
  84. package/dist/node_modules/@welshare/questionnaire/src/components/questions/boolean-question.tsx +0 -72
  85. package/dist/node_modules/@welshare/questionnaire/src/components/questions/choice-question.tsx +0 -68
  86. package/dist/node_modules/@welshare/questionnaire/src/components/questions/decimal-question.tsx +0 -32
  87. package/dist/node_modules/@welshare/questionnaire/src/components/questions/integer-question.tsx +0 -87
  88. package/dist/node_modules/@welshare/questionnaire/src/components/questions/multiple-choice-question.tsx +0 -119
  89. package/dist/node_modules/@welshare/questionnaire/src/components/questions/string-question.tsx +0 -31
  90. package/dist/node_modules/@welshare/questionnaire/src/contexts/questionnaire-context.tsx +0 -499
  91. package/dist/node_modules/@welshare/questionnaire/src/index.ts +0 -41
  92. package/dist/node_modules/@welshare/questionnaire/src/lib/__tests__/questionnaire-utils.test.ts +0 -578
  93. package/dist/node_modules/@welshare/questionnaire/src/lib/questionnaire-utils.ts +0 -122
  94. package/dist/node_modules/@welshare/questionnaire/src/types/fhir.ts +0 -126
  95. package/dist/node_modules/@welshare/questionnaire/src/types/index.ts +0 -44
  96. package/dist/node_modules/@welshare/questionnaire/tsconfig.json +0 -16
@@ -1,85 +0,0 @@
1
- {
2
- "name": "@welshare/questionnaire",
3
- "version": "0.1.1",
4
- "description": "FHIR Questionnaire components for React with state management and validation",
5
- "keywords": [
6
- "react",
7
- "fhir",
8
- "questionnaire",
9
- "survey",
10
- "forms",
11
- "healthcare"
12
- ],
13
- "author": "Welshare UG (haftungsbeschränkt)",
14
- "license": "MIT",
15
- "repository": {
16
- "type": "git",
17
- "url": "https://github.com/welshare/surveys-monorepo.git",
18
- "directory": "packages/welshare-questionnaire"
19
- },
20
- "bugs": {
21
- "url": "https://github.com/welshare/surveys-monorepo/issues"
22
- },
23
- "homepage": "https://welshare.health",
24
- "type": "module",
25
- "scripts": {
26
- "lint": "eslint . --max-warnings 25",
27
- "build": "tshy && npm run build:css",
28
- "build:css": "cp src/components/questionnaire-tokens.css dist/tokens.css && cp src/components/questionnaire-styles.css dist/styles.css",
29
- "build:clean": "rm -rf ./dist",
30
- "test": "vitest",
31
- "test:run": "vitest run",
32
- "test:coverage": "vitest run --coverage",
33
- "prepublishOnly": "npm run build:clean && npm run build"
34
- },
35
- "peerDependencies": {
36
- "react": "^19",
37
- "react-dom": "^19"
38
- },
39
- "devDependencies": {
40
- "@types/node": "^22",
41
- "@types/react": "^19",
42
- "@workspace/eslint-config": "workspace:*",
43
- "@workspace/typescript-config": "workspace:*",
44
- "tshy": "^3.0.2",
45
- "typescript": "^5.7.3",
46
- "vitest": "^3.2.4",
47
- "@vitest/ui": "^3.2.4",
48
- "@vitest/coverage-v8": "^3.2.4",
49
- "jsdom": "^26.1.0"
50
- },
51
- "tshy": {
52
- "dialects": [
53
- "esm"
54
- ],
55
- "exclude": [
56
- "src/**/__tests__/**"
57
- ],
58
- "exports": {
59
- "./package.json": "./package.json",
60
- "./tokens.css": "./dist/tokens.css",
61
- "./styles.css": "./dist/styles.css",
62
- ".": "./src/index.ts"
63
- }
64
- },
65
- "module": "./dist/esm/index.js",
66
- "exports": {
67
- "./package.json": "./package.json",
68
- "./tokens.css": "./dist/tokens.css",
69
- "./styles.css": "./dist/styles.css",
70
- ".": {
71
- "import": {
72
- "types": "./dist/esm/index.d.ts",
73
- "default": "./dist/esm/index.js"
74
- }
75
- }
76
- },
77
- "files": [
78
- "dist/**/*",
79
- "README.md",
80
- "LICENSE"
81
- ],
82
- "engines": {
83
- "node": "^22.0.0"
84
- }
85
- }
@@ -1,116 +0,0 @@
1
- import { useState } from "react";
2
- import type {
3
- QuestionnaireItem,
4
- QuestionnaireResponseAnswer,
5
- Coding,
6
- } from "../types/fhir.js";
7
-
8
- export interface DebugSectionProps {
9
- /**
10
- * The questionnaire item to display debug information for
11
- */
12
- item: QuestionnaireItem;
13
- /**
14
- * The current answer (for single-answer questions)
15
- */
16
- currentAnswer?: QuestionnaireResponseAnswer;
17
- /**
18
- * The current answers (for multi-answer questions)
19
- */
20
- currentAnswers?: QuestionnaireResponseAnswer[];
21
- /**
22
- * Optional LOINC code to display
23
- */
24
- loincCode?: Coding;
25
- /**
26
- * Additional CSS class names
27
- */
28
- className?: string;
29
- }
30
-
31
- /**
32
- * DebugSection component displays debug information for a questionnaire item.
33
- *
34
- * This component is designed to be reusable and can be moved to the shared package.
35
- * It displays:
36
- * - LOINC code link (if available)
37
- * - Collapsible FHIR question definition
38
- * - Collapsible selected answer(s) in FHIR format
39
- *
40
- * @example
41
- * ```tsx
42
- * <DebugSection
43
- * item={questionItem}
44
- * currentAnswer={answer}
45
- * currentAnswers={answers}
46
- * loincCode={loincCode}
47
- * />
48
- * ```
49
- */
50
- export const DebugSection = ({
51
- item,
52
- currentAnswer,
53
- currentAnswers = [],
54
- loincCode,
55
- className = "",
56
- }: DebugSectionProps) => {
57
- const [isQuestionDefOpen, setIsQuestionDefOpen] = useState(false);
58
- const [isAnswersOpen, setIsAnswersOpen] = useState(false);
59
-
60
- return (
61
- <div className={`wq-debug-section ${className}`}>
62
- {loincCode && loincCode.code && (
63
- <div className="wq-loinc-link-section">
64
- <a
65
- href={`https://loinc.org/${loincCode.code}`}
66
- target="_blank"
67
- rel="noopener noreferrer"
68
- className="wq-loinc-link"
69
- >
70
- 📋 LOINC Specification: {loincCode.code}
71
- </a>
72
- </div>
73
- )}
74
-
75
- {/* Collapsible FHIR Question Definition */}
76
- <div className="wq-debug-collapsible">
77
- <button
78
- className="wq-debug-collapsible-header"
79
- onClick={() => setIsQuestionDefOpen(!isQuestionDefOpen)}
80
- type="button"
81
- >
82
- <span className={`chevron ${isQuestionDefOpen ? "open" : ""}`}>
83
-
84
- </span>
85
- <span className="wq-debug-title">FHIR Question Definition</span>
86
- </button>
87
- {isQuestionDefOpen && (
88
- <pre className="wq-debug-json">{JSON.stringify(item, null, 2)}</pre>
89
- )}
90
- </div>
91
-
92
- {/* Collapsible Selected Answer(s) */}
93
- {(currentAnswer || currentAnswers.length > 0) && (
94
- <div className="wq-debug-collapsible">
95
- <button
96
- className="wq-debug-collapsible-header"
97
- onClick={() => setIsAnswersOpen(!isAnswersOpen)}
98
- type="button"
99
- >
100
- <span className={`chevron ${isAnswersOpen ? "open" : ""}`}>▶</span>
101
- <span className="wq-debug-title">
102
- Selected Answer(s) (FHIR Format)
103
- </span>
104
- </button>
105
- {isAnswersOpen && (
106
- <pre className="wq-debug-json">
107
- {item.repeats
108
- ? JSON.stringify(currentAnswers, null, 2)
109
- : JSON.stringify(currentAnswer, null, 2)}
110
- </pre>
111
- )}
112
- </div>
113
- )}
114
- </div>
115
- );
116
- };
@@ -1,368 +0,0 @@
1
- import { type ReactNode } from "react";
2
- import { useQuestionnaire } from "../contexts/questionnaire-context.js";
3
- import type { QuestionnaireItem } from "../types/fhir.js";
4
- import { DebugSection } from "./debug-section.js";
5
- import { ChoiceQuestion } from "./questions/choice-question.js";
6
- import { MultipleChoiceQuestion } from "./questions/multiple-choice-question.js";
7
- import { IntegerQuestion } from "./questions/integer-question.js";
8
- import { DecimalQuestion } from "./questions/decimal-question.js";
9
- import { StringQuestion } from "./questions/string-question.js";
10
- import { BooleanQuestion } from "./questions/boolean-question.js";
11
- import { RadioInputProps, CheckboxInputProps } from "../types/index.js";
12
-
13
- export interface QuestionRendererProps {
14
- item: QuestionnaireItem;
15
- /**
16
- * Additional class names for the question container
17
- * Will be appended to the default wq-question-container class
18
- */
19
- className?: string;
20
- /**
21
- * Additional class names for input elements
22
- * Will be appended to the default wq-question-input class
23
- */
24
- inputClassName?: string;
25
- /**
26
- * Additional class names for choice options
27
- * Will be appended to the default wq-choice-option class
28
- */
29
- choiceClassName?: string;
30
- /**
31
- * Custom renderer for radio button inputs.
32
- * When provided, this function will be called to render each radio option,
33
- * giving clients full control over styling and structure.
34
- *
35
- * @example
36
- * ```tsx
37
- * <QuestionRenderer
38
- * item={item}
39
- * renderRadioInput={(props) => (
40
- * <div className="custom-radio-wrapper">
41
- * <input
42
- * type="radio"
43
- * checked={props.checked}
44
- * onChange={props.onChange}
45
- * className="custom-radio"
46
- * />
47
- * <label>{props.label}</label>
48
- * </div>
49
- * )}
50
- * />
51
- * ```
52
- */
53
- renderRadioInput?: (props: RadioInputProps) => ReactNode;
54
- /**
55
- * Custom renderer for checkbox inputs.
56
- * When provided, this function will be called to render each checkbox option,
57
- * giving clients full control over styling and structure.
58
- *
59
- * @example
60
- * ```tsx
61
- * <QuestionRenderer
62
- * item={item}
63
- * renderCheckboxInput={(props) => (
64
- * <div className="custom-checkbox-wrapper">
65
- * <input
66
- * type="checkbox"
67
- * checked={props.checked}
68
- * onChange={props.onChange}
69
- * className="custom-checkbox"
70
- * />
71
- * <label>{props.label}</label>
72
- * </div>
73
- * )}
74
- * />
75
- * ```
76
- */
77
- renderCheckboxInput?: (props: CheckboxInputProps) => ReactNode;
78
- }
79
-
80
- /**
81
- * Wrapper component that combines the library's QuestionRenderer with debug functionality.
82
- *
83
- * This component:
84
- * - Renders the library's QuestionRenderer with custom radio/checkbox renderers
85
- * - Adds DebugSection below it when debug mode is enabled
86
- *
87
- * Uses custom input renderers to maintain the app's design system styling.
88
- */
89
- export const QuestionRenderer = (rendererProps: QuestionRendererProps) => {
90
- const { getAnswer, getAnswers, debugMode } = useQuestionnaire();
91
-
92
- const { item } = rendererProps;
93
- const currentAnswer = getAnswer(item.linkId);
94
- const currentAnswers = getAnswers(item.linkId);
95
-
96
- // Find LOINC code if present
97
- const loincCode = item.code?.find(
98
- (coding) => coding.system === "http://loinc.org"
99
- );
100
-
101
- return (
102
- <div>
103
- <QuestionRendererInternal {...rendererProps} />
104
- {debugMode && (
105
- <DebugSection
106
- item={item}
107
- currentAnswer={currentAnswer}
108
- currentAnswers={currentAnswers}
109
- loincCode={loincCode}
110
- />
111
- )}
112
- </div>
113
- );
114
- };
115
-
116
- /**
117
- * QuestionRenderer component renders different types of questionnaire items
118
- * Supports: choice, integer, decimal, string, text, boolean
119
- * Features:
120
- * - Single and multiple choice (radio/checkbox)
121
- * - Slider controls for integer inputs
122
- * - Exclusive option support for multi-select
123
- * - Auto-populated (hidden) fields
124
- * - Validation error display
125
- */
126
- const QuestionRendererInternal = ({
127
- item,
128
- className = "",
129
- inputClassName = "",
130
- choiceClassName = "",
131
- renderRadioInput,
132
- renderCheckboxInput,
133
- }: QuestionRendererProps) => {
134
- const {
135
- updateAnswer,
136
- updateMultipleAnswers,
137
- getAnswer,
138
- getAnswers,
139
- hasValidationError,
140
- } = useQuestionnaire();
141
-
142
- const currentAnswer = getAnswer(item.linkId);
143
- const currentAnswers = getAnswers(item.linkId);
144
- const hasError = hasValidationError(item.linkId);
145
-
146
- // Check if this field should be hidden (auto-populated)
147
- const isAutoPopulated = item.extension?.some(
148
- (ext) =>
149
- ext.url ===
150
- "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden" &&
151
- ext.valueBoolean === true
152
- );
153
-
154
- // Don't render auto-populated fields
155
- if (isAutoPopulated) {
156
- return null;
157
- }
158
-
159
- const handleChoiceChange = (
160
- valueCoding: { system?: string; code?: string; display?: string },
161
- valueInteger?: number
162
- ) => {
163
- updateAnswer(item.linkId, { valueCoding, valueInteger });
164
- };
165
-
166
- const handleMultipleChoiceToggle = (
167
- valueCoding: { system?: string; code?: string; display?: string },
168
- valueInteger?: number
169
- ) => {
170
- const isSelected = currentAnswers.some(
171
- (answer) => answer.valueCoding?.code === valueCoding.code
172
- );
173
-
174
- // Check if there's an exclusive option extension
175
- const exclusiveOptionExt = item.extension?.find(
176
- (ext) =>
177
- ext.url ===
178
- "http://codes.welshare.app/StructureDefinition/questionnaire-exclusive-option"
179
- );
180
- const exclusiveOptionCode = exclusiveOptionExt?.valueString;
181
-
182
- let newAnswers;
183
- if (isSelected) {
184
- // Remove the answer
185
- newAnswers = currentAnswers.filter(
186
- (answer) => answer.valueCoding?.code !== valueCoding.code
187
- );
188
- } else {
189
- // Check if we're selecting the exclusive option
190
- if (exclusiveOptionCode && valueCoding.code === exclusiveOptionCode) {
191
- // Clear all other answers and only keep this one
192
- newAnswers = [{ valueCoding, valueInteger }];
193
- } else {
194
- // Check if exclusive option is currently selected
195
- const exclusiveSelected = currentAnswers.some(
196
- (answer) => answer.valueCoding?.code === exclusiveOptionCode
197
- );
198
-
199
- if (exclusiveSelected) {
200
- // Remove exclusive option and add new selection
201
- newAnswers = currentAnswers.filter(
202
- (answer) => answer.valueCoding?.code !== exclusiveOptionCode
203
- );
204
- newAnswers = [...newAnswers, { valueCoding, valueInteger }];
205
- } else {
206
- // Normal behavior: Add the answer (respecting maxAnswers limit)
207
- const maxAnswers = item.maxAnswers || Number.MAX_SAFE_INTEGER;
208
- if (currentAnswers.length < maxAnswers) {
209
- newAnswers = [...currentAnswers, { valueCoding, valueInteger }];
210
- } else {
211
- // Already at max, don't add
212
- return;
213
- }
214
- }
215
- }
216
- }
217
-
218
- updateMultipleAnswers(item.linkId, newAnswers);
219
- };
220
-
221
- const handleIntegerChange = (value: string) => {
222
- // Allow clearing the controlled input
223
- if (value === "") {
224
- updateAnswer(item.linkId, {});
225
- return;
226
- }
227
- const numValue = parseInt(value);
228
- if (!isNaN(numValue)) {
229
- updateAnswer(item.linkId, { valueInteger: numValue });
230
- }
231
- };
232
-
233
- const handleDecimalChange = (value: string) => {
234
- // Allow clearing the controlled input
235
- if (value === "") {
236
- updateAnswer(item.linkId, {});
237
- return;
238
- }
239
- const numValue = parseFloat(value);
240
- if (!isNaN(numValue)) {
241
- updateAnswer(item.linkId, { valueDecimal: numValue });
242
- }
243
- };
244
-
245
- const handleStringChange = (value: string) => {
246
- updateAnswer(item.linkId, { valueString: value });
247
- };
248
-
249
- const handleBooleanChange = (value: boolean) => {
250
- updateAnswer(item.linkId, { valueBoolean: value });
251
- };
252
-
253
- // Check if this field should use a slider control
254
- const getSliderConfig = () => {
255
- const sliderExt = item.extension?.find(
256
- (ext) =>
257
- ext.url ===
258
- "http://codes.welshare.app/StructureDefinition/questionnaire-slider-control"
259
- );
260
-
261
- if (!sliderExt?.extension) return null;
262
-
263
- const minValue =
264
- sliderExt.extension.find((e) => e.url === "minValue")?.valueInteger ?? 0;
265
- const maxValue =
266
- sliderExt.extension.find((e) => e.url === "maxValue")?.valueInteger ??
267
- 100;
268
- const step =
269
- sliderExt.extension.find((e) => e.url === "step")?.valueInteger ?? 1;
270
- const unit =
271
- sliderExt.extension.find((e) => e.url === "unit")?.valueString ?? "";
272
-
273
- return { minValue, maxValue, step, unit };
274
- };
275
-
276
- const sliderConfig = getSliderConfig();
277
-
278
- const renderQuestion = () => {
279
- switch (item.type) {
280
- case "choice":
281
- // Multi-select with checkboxes when repeats is true
282
- if (item.repeats) {
283
- return (
284
- <MultipleChoiceQuestion
285
- item={item}
286
- currentAnswers={currentAnswers}
287
- onMultipleChoiceToggle={handleMultipleChoiceToggle}
288
- choiceClassName={choiceClassName}
289
- renderCheckboxInput={renderCheckboxInput}
290
- />
291
- );
292
- }
293
-
294
- // Single-select with radio buttons (default)
295
- return (
296
- <ChoiceQuestion
297
- item={item}
298
- currentAnswer={currentAnswer}
299
- onChoiceChange={handleChoiceChange}
300
- choiceClassName={choiceClassName}
301
- renderRadioInput={renderRadioInput}
302
- />
303
- );
304
-
305
- case "integer":
306
- return (
307
- <IntegerQuestion
308
- item={item}
309
- currentAnswer={currentAnswer}
310
- onIntegerChange={handleIntegerChange}
311
- inputClassName={inputClassName}
312
- sliderConfig={sliderConfig}
313
- />
314
- );
315
-
316
- case "decimal":
317
- return (
318
- <DecimalQuestion
319
- item={item}
320
- currentAnswer={currentAnswer}
321
- onDecimalChange={handleDecimalChange}
322
- inputClassName={inputClassName}
323
- />
324
- );
325
-
326
- case "string":
327
- case "text":
328
- return (
329
- <StringQuestion
330
- item={item}
331
- currentAnswer={currentAnswer}
332
- onStringChange={handleStringChange}
333
- inputClassName={inputClassName}
334
- />
335
- );
336
-
337
- case "boolean":
338
- return (
339
- <BooleanQuestion
340
- item={item}
341
- currentAnswer={currentAnswer}
342
- onBooleanChange={handleBooleanChange}
343
- choiceClassName={choiceClassName}
344
- renderRadioInput={renderRadioInput}
345
- />
346
- );
347
-
348
- default:
349
- return (
350
- <div className="wq-unsupported-type">
351
- Unsupported question type: {item.type}
352
- </div>
353
- );
354
- }
355
- };
356
-
357
- return (
358
- <div
359
- className={`wq-question-container ${hasError ? "wq-has-error" : ""} ${className}`}
360
- >
361
- <div className="wq-question-text">
362
- {item.text}
363
- {item.required && <span className="wq-required-indicator">*</span>}
364
- </div>
365
- {renderQuestion()}
366
- </div>
367
- );
368
- };