@tantainnovative/ndpr-toolkit 1.0.4 → 1.0.5
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/eslint.config.mjs +16 -0
- package/next.config.js +15 -0
- package/next.config.ts +62 -0
- package/package.json +1 -1
- package/packages/ndpr-toolkit/README.md +467 -0
- package/packages/ndpr-toolkit/dist/components/breach/BreachNotificationManager.d.ts +62 -0
- package/packages/ndpr-toolkit/dist/components/breach/BreachReportForm.d.ts +66 -0
- package/packages/ndpr-toolkit/dist/components/breach/BreachRiskAssessment.d.ts +50 -0
- package/packages/ndpr-toolkit/dist/components/breach/RegulatoryReportGenerator.d.ts +94 -0
- package/packages/ndpr-toolkit/dist/components/consent/ConsentBanner.d.ts +79 -0
- package/packages/ndpr-toolkit/dist/components/consent/ConsentManager.d.ts +73 -0
- package/packages/ndpr-toolkit/dist/components/consent/ConsentStorage.d.ts +41 -0
- package/packages/ndpr-toolkit/dist/components/dpia/DPIAQuestionnaire.d.ts +70 -0
- package/packages/ndpr-toolkit/dist/components/dpia/DPIAReport.d.ts +40 -0
- package/packages/ndpr-toolkit/dist/components/dpia/StepIndicator.d.ts +64 -0
- package/packages/ndpr-toolkit/dist/components/dsr/DSRDashboard.d.ts +58 -0
- package/packages/ndpr-toolkit/dist/components/dsr/DSRRequestForm.d.ts +74 -0
- package/packages/ndpr-toolkit/dist/components/dsr/DSRTracker.d.ts +56 -0
- package/packages/ndpr-toolkit/dist/components/policy/PolicyExporter.d.ts +65 -0
- package/packages/ndpr-toolkit/dist/components/policy/PolicyGenerator.d.ts +54 -0
- package/packages/ndpr-toolkit/dist/components/policy/PolicyPreview.d.ts +71 -0
- package/packages/ndpr-toolkit/dist/hooks/useBreach.d.ts +97 -0
- package/packages/ndpr-toolkit/dist/hooks/useConsent.d.ts +63 -0
- package/packages/ndpr-toolkit/dist/hooks/useDPIA.d.ts +92 -0
- package/packages/ndpr-toolkit/dist/hooks/useDSR.d.ts +72 -0
- package/packages/ndpr-toolkit/dist/hooks/usePrivacyPolicy.d.ts +87 -0
- package/packages/ndpr-toolkit/dist/index.d.ts +31 -0
- package/packages/ndpr-toolkit/dist/index.esm.js +2 -0
- package/packages/ndpr-toolkit/dist/index.esm.js.map +1 -0
- package/packages/ndpr-toolkit/dist/index.js +2 -0
- package/packages/ndpr-toolkit/dist/index.js.map +1 -0
- package/packages/ndpr-toolkit/dist/setupTests.d.ts +2 -0
- package/packages/ndpr-toolkit/dist/types/breach.d.ts +239 -0
- package/packages/ndpr-toolkit/dist/types/consent.d.ts +95 -0
- package/packages/ndpr-toolkit/dist/types/dpia.d.ts +196 -0
- package/packages/ndpr-toolkit/dist/types/dsr.d.ts +162 -0
- package/packages/ndpr-toolkit/dist/types/privacy.d.ts +204 -0
- package/packages/ndpr-toolkit/dist/utils/breach.d.ts +14 -0
- package/packages/ndpr-toolkit/dist/utils/consent.d.ts +10 -0
- package/packages/ndpr-toolkit/dist/utils/dpia.d.ts +12 -0
- package/packages/ndpr-toolkit/dist/utils/dsr.d.ts +11 -0
- package/packages/ndpr-toolkit/dist/utils/privacy.d.ts +12 -0
- package/packages/ndpr-toolkit/package-lock.json +8197 -0
- package/packages/ndpr-toolkit/package.json +71 -0
- package/packages/ndpr-toolkit/rollup.config.js +34 -0
- package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +701 -0
- package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +631 -0
- package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +569 -0
- package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +496 -0
- package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +270 -0
- package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +217 -0
- package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +206 -0
- package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +342 -0
- package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +373 -0
- package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +174 -0
- package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +717 -0
- package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +476 -0
- package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +620 -0
- package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +541 -0
- package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +454 -0
- package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +333 -0
- package/packages/ndpr-toolkit/src/hooks/useBreach.ts +409 -0
- package/packages/ndpr-toolkit/src/hooks/useConsent.ts +263 -0
- package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +457 -0
- package/packages/ndpr-toolkit/src/hooks/useDSR.ts +236 -0
- package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +428 -0
- package/packages/ndpr-toolkit/src/index.ts +44 -0
- package/packages/ndpr-toolkit/src/setupTests.ts +5 -0
- package/packages/ndpr-toolkit/src/types/breach.ts +283 -0
- package/packages/ndpr-toolkit/src/types/consent.ts +111 -0
- package/packages/ndpr-toolkit/src/types/dpia.ts +236 -0
- package/packages/ndpr-toolkit/src/types/dsr.ts +192 -0
- package/packages/ndpr-toolkit/src/types/index.ts +42 -0
- package/packages/ndpr-toolkit/src/types/privacy.ts +246 -0
- package/packages/ndpr-toolkit/src/utils/breach.ts +122 -0
- package/packages/ndpr-toolkit/src/utils/consent.ts +51 -0
- package/packages/ndpr-toolkit/src/utils/dpia.ts +104 -0
- package/packages/ndpr-toolkit/src/utils/dsr.ts +77 -0
- package/packages/ndpr-toolkit/src/utils/privacy.ts +100 -0
- package/packages/ndpr-toolkit/tsconfig.json +23 -0
- package/postcss.config.mjs +5 -0
- package/src/app/accessibility.css +70 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +123 -0
- package/src/app/layout.tsx +37 -0
- package/src/app/ndpr-demos/breach/page.tsx +354 -0
- package/src/app/ndpr-demos/consent/page.tsx +366 -0
- package/src/app/ndpr-demos/dpia/page.tsx +495 -0
- package/src/app/ndpr-demos/dsr/page.tsx +280 -0
- package/src/app/ndpr-demos/page.tsx +73 -0
- package/src/app/ndpr-demos/policy/page.tsx +771 -0
- package/src/app/page.tsx +452 -0
- package/src/components/ErrorBoundary.tsx +90 -0
- package/src/components/breach-notification/BreachNotificationForm.tsx +479 -0
- package/src/components/consent/ConsentBanner.tsx +193 -0
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +530 -0
- package/src/components/dpia/DPIAQuestionnaire.tsx +523 -0
- package/src/components/privacy-policy/PolicyGenerator.tsx +1062 -0
- package/src/components/privacy-policy/data.ts +98 -0
- package/src/components/privacy-policy/shared/CheckboxField.tsx +38 -0
- package/src/components/privacy-policy/shared/CheckboxGroup.tsx +85 -0
- package/src/components/privacy-policy/shared/FormField.tsx +79 -0
- package/src/components/privacy-policy/shared/StepIndicator.tsx +86 -0
- package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +361 -0
- package/src/components/privacy-policy/steps/DataCollectionStep.tsx +231 -0
- package/src/components/privacy-policy/steps/DataSharingStep.tsx +418 -0
- package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +202 -0
- package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +226 -0
- package/src/components/ui/Badge.tsx +46 -0
- package/src/components/ui/Button.tsx +59 -0
- package/src/components/ui/Card.tsx +92 -0
- package/src/components/ui/Checkbox.tsx +57 -0
- package/src/components/ui/FormField.tsx +50 -0
- package/src/components/ui/Input.tsx +38 -0
- package/src/components/ui/Loading.tsx +201 -0
- package/src/components/ui/Select.tsx +42 -0
- package/src/components/ui/TextArea.tsx +38 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/hooks/useConsent.ts +70 -0
- package/src/hooks/useLoadingState.ts +85 -0
- package/src/lib/consentService.ts +144 -0
- package/src/lib/dpiaQuestions.ts +188 -0
- package/src/lib/requestService.ts +79 -0
- package/src/lib/sanitize.ts +108 -0
- package/src/lib/storage.ts +222 -0
- package/src/lib/utils.ts +6 -0
- package/src/types/html-to-docx.d.ts +30 -0
- package/src/types/index.ts +77 -0
- package/tailwind.config.ts +65 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { DPIASection, DPIAQuestion } from '../../types/dpia';
|
|
3
|
+
|
|
4
|
+
export interface DPIAQuestionnaireProps {
|
|
5
|
+
/**
|
|
6
|
+
* Sections of the DPIA questionnaire
|
|
7
|
+
*/
|
|
8
|
+
sections: DPIASection[];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Current answers to the questionnaire
|
|
12
|
+
*/
|
|
13
|
+
answers: Record<string, any>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Callback function called when an answer is updated
|
|
17
|
+
*/
|
|
18
|
+
onAnswerChange: (questionId: string, value: any) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Current section index
|
|
22
|
+
*/
|
|
23
|
+
currentSectionIndex: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Callback function called when user navigates to the next section
|
|
27
|
+
*/
|
|
28
|
+
onNextSection?: () => void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Callback function called when user navigates to the previous section
|
|
32
|
+
*/
|
|
33
|
+
onPrevSection?: () => void;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validation errors for the current section
|
|
37
|
+
*/
|
|
38
|
+
validationErrors?: Record<string, string>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether the questionnaire is in read-only mode
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
readOnly?: boolean;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Custom CSS class for the questionnaire
|
|
48
|
+
*/
|
|
49
|
+
className?: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Custom CSS class for the buttons
|
|
53
|
+
*/
|
|
54
|
+
buttonClassName?: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Text for the next button
|
|
58
|
+
* @default "Next"
|
|
59
|
+
*/
|
|
60
|
+
nextButtonText?: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Text for the previous button
|
|
64
|
+
* @default "Previous"
|
|
65
|
+
*/
|
|
66
|
+
prevButtonText?: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Text for the submit button (shown on the last section)
|
|
70
|
+
* @default "Submit"
|
|
71
|
+
*/
|
|
72
|
+
submitButtonText?: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether to show a progress indicator
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
showProgress?: boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Current progress percentage (0-100)
|
|
82
|
+
*/
|
|
83
|
+
progress?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const DPIAQuestionnaire: React.FC<DPIAQuestionnaireProps> = ({
|
|
87
|
+
sections,
|
|
88
|
+
answers,
|
|
89
|
+
onAnswerChange,
|
|
90
|
+
currentSectionIndex,
|
|
91
|
+
onNextSection,
|
|
92
|
+
onPrevSection,
|
|
93
|
+
validationErrors = {},
|
|
94
|
+
readOnly = false,
|
|
95
|
+
className = "",
|
|
96
|
+
buttonClassName = "",
|
|
97
|
+
nextButtonText = "Next",
|
|
98
|
+
prevButtonText = "Previous",
|
|
99
|
+
submitButtonText = "Submit",
|
|
100
|
+
showProgress = true,
|
|
101
|
+
progress
|
|
102
|
+
}) => {
|
|
103
|
+
const currentSection = sections[currentSectionIndex];
|
|
104
|
+
const isLastSection = currentSectionIndex === sections.length - 1;
|
|
105
|
+
|
|
106
|
+
// Check if a question should be shown based on its conditions
|
|
107
|
+
const shouldShowQuestion = (question: DPIAQuestion): boolean => {
|
|
108
|
+
if (!question.showWhen) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return question.showWhen.every(condition => {
|
|
113
|
+
const answer = answers[condition.questionId];
|
|
114
|
+
|
|
115
|
+
switch (condition.operator) {
|
|
116
|
+
case 'equals':
|
|
117
|
+
return answer === condition.value;
|
|
118
|
+
case 'contains':
|
|
119
|
+
return Array.isArray(answer) ? answer.includes(condition.value) : false;
|
|
120
|
+
case 'greaterThan':
|
|
121
|
+
return typeof answer === 'number' ? answer > condition.value : false;
|
|
122
|
+
case 'lessThan':
|
|
123
|
+
return typeof answer === 'number' ? answer < condition.value : false;
|
|
124
|
+
default:
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Render a question based on its type
|
|
131
|
+
const renderQuestion = (question: DPIAQuestion) => {
|
|
132
|
+
if (!shouldShowQuestion(question)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const error = validationErrors[question.id];
|
|
137
|
+
const value = answers[question.id];
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div key={question.id} className="mb-6">
|
|
141
|
+
<div className="mb-2">
|
|
142
|
+
<label htmlFor={question.id} className="block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
143
|
+
{question.text}
|
|
144
|
+
{question.required && <span className="text-red-500 ml-1">*</span>}
|
|
145
|
+
</label>
|
|
146
|
+
{question.guidance && (
|
|
147
|
+
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{question.guidance}</p>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{question.type === 'text' && (
|
|
152
|
+
<input
|
|
153
|
+
type="text"
|
|
154
|
+
id={question.id}
|
|
155
|
+
value={value || ''}
|
|
156
|
+
onChange={e => onAnswerChange(question.id, e.target.value)}
|
|
157
|
+
disabled={readOnly}
|
|
158
|
+
className={`w-full px-3 py-2 border rounded-md ${
|
|
159
|
+
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
|
|
160
|
+
} focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100`}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{question.type === 'textarea' && (
|
|
165
|
+
<textarea
|
|
166
|
+
id={question.id}
|
|
167
|
+
value={value || ''}
|
|
168
|
+
onChange={e => onAnswerChange(question.id, e.target.value)}
|
|
169
|
+
disabled={readOnly}
|
|
170
|
+
rows={4}
|
|
171
|
+
className={`w-full px-3 py-2 border rounded-md ${
|
|
172
|
+
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
|
|
173
|
+
} focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100`}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
{question.type === 'select' && question.options && (
|
|
178
|
+
<select
|
|
179
|
+
id={question.id}
|
|
180
|
+
value={value || ''}
|
|
181
|
+
onChange={e => onAnswerChange(question.id, e.target.value)}
|
|
182
|
+
disabled={readOnly}
|
|
183
|
+
className={`w-full px-3 py-2 border rounded-md ${
|
|
184
|
+
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
|
|
185
|
+
} focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100`}
|
|
186
|
+
>
|
|
187
|
+
<option value="">Select an option</option>
|
|
188
|
+
{question.options.map(option => (
|
|
189
|
+
<option key={option.value} value={option.value}>
|
|
190
|
+
{option.label}
|
|
191
|
+
</option>
|
|
192
|
+
))}
|
|
193
|
+
</select>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{question.type === 'radio' && question.options && (
|
|
197
|
+
<div className="space-y-2">
|
|
198
|
+
{question.options.map(option => (
|
|
199
|
+
<div key={option.value} className="flex items-center">
|
|
200
|
+
<input
|
|
201
|
+
type="radio"
|
|
202
|
+
id={`${question.id}_${option.value}`}
|
|
203
|
+
name={question.id}
|
|
204
|
+
value={option.value}
|
|
205
|
+
checked={value === option.value}
|
|
206
|
+
onChange={() => onAnswerChange(question.id, option.value)}
|
|
207
|
+
disabled={readOnly}
|
|
208
|
+
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 dark:border-gray-600"
|
|
209
|
+
/>
|
|
210
|
+
<label
|
|
211
|
+
htmlFor={`${question.id}_${option.value}`}
|
|
212
|
+
className="ml-2 block text-sm text-gray-900 dark:text-gray-100"
|
|
213
|
+
>
|
|
214
|
+
{option.label}
|
|
215
|
+
{option.riskLevel && (
|
|
216
|
+
<span className={`ml-2 text-xs px-2 py-1 rounded ${
|
|
217
|
+
option.riskLevel === 'low' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' :
|
|
218
|
+
option.riskLevel === 'medium' ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' :
|
|
219
|
+
'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
|
220
|
+
}`}>
|
|
221
|
+
{option.riskLevel.charAt(0).toUpperCase() + option.riskLevel.slice(1)} Risk
|
|
222
|
+
</span>
|
|
223
|
+
)}
|
|
224
|
+
</label>
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{question.type === 'checkbox' && question.options && (
|
|
231
|
+
<div className="space-y-2">
|
|
232
|
+
{question.options.map(option => (
|
|
233
|
+
<div key={option.value} className="flex items-center">
|
|
234
|
+
<input
|
|
235
|
+
type="checkbox"
|
|
236
|
+
id={`${question.id}_${option.value}`}
|
|
237
|
+
value={option.value}
|
|
238
|
+
checked={Array.isArray(value) ? value.includes(option.value) : false}
|
|
239
|
+
onChange={e => {
|
|
240
|
+
const currentValues = Array.isArray(value) ? [...value] : [];
|
|
241
|
+
if (e.target.checked) {
|
|
242
|
+
onAnswerChange(question.id, [...currentValues, option.value]);
|
|
243
|
+
} else {
|
|
244
|
+
onAnswerChange(question.id, currentValues.filter(v => v !== option.value));
|
|
245
|
+
}
|
|
246
|
+
}}
|
|
247
|
+
disabled={readOnly}
|
|
248
|
+
className="h-4 w-4 rounded text-blue-600 focus:ring-blue-500 border-gray-300 dark:border-gray-600"
|
|
249
|
+
/>
|
|
250
|
+
<label
|
|
251
|
+
htmlFor={`${question.id}_${option.value}`}
|
|
252
|
+
className="ml-2 block text-sm text-gray-900 dark:text-gray-100"
|
|
253
|
+
>
|
|
254
|
+
{option.label}
|
|
255
|
+
</label>
|
|
256
|
+
</div>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{question.type === 'scale' && (
|
|
262
|
+
<div>
|
|
263
|
+
<div className="flex justify-between mb-2">
|
|
264
|
+
{question.scaleLabels && Object.entries(question.scaleLabels).map(([scaleValue, label]) => (
|
|
265
|
+
<div key={scaleValue} className="text-xs text-gray-500 dark:text-gray-400 text-center" style={{ width: `${100 / Object.keys(question.scaleLabels || {}).length}%` }}>
|
|
266
|
+
{label}
|
|
267
|
+
</div>
|
|
268
|
+
))}
|
|
269
|
+
</div>
|
|
270
|
+
<input
|
|
271
|
+
type="range"
|
|
272
|
+
id={question.id}
|
|
273
|
+
min={question.minValue || 1}
|
|
274
|
+
max={question.maxValue || 5}
|
|
275
|
+
value={value || (question.minValue || 1)}
|
|
276
|
+
onChange={e => onAnswerChange(question.id, parseInt(e.target.value, 10))}
|
|
277
|
+
disabled={readOnly}
|
|
278
|
+
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
|
279
|
+
/>
|
|
280
|
+
<div className="mt-1 text-sm text-gray-500 dark:text-gray-400 text-center">
|
|
281
|
+
Selected value: {value || (question.minValue || 1)}
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{error && <p className="mt-1 text-sm text-red-500">{error}</p>}
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
if (!currentSection) {
|
|
292
|
+
return <div>No section found.</div>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 ${className}`}>
|
|
297
|
+
{showProgress && (
|
|
298
|
+
<div className="mb-6">
|
|
299
|
+
<div className="flex justify-between text-sm text-gray-500 dark:text-gray-400 mb-1">
|
|
300
|
+
<span>Section {currentSectionIndex + 1} of {sections.length}</span>
|
|
301
|
+
<span>{progress !== undefined ? `${progress}% Complete` : ''}</span>
|
|
302
|
+
</div>
|
|
303
|
+
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
|
|
304
|
+
<div
|
|
305
|
+
className="bg-blue-600 h-2.5 rounded-full"
|
|
306
|
+
style={{ width: `${progress !== undefined ? progress : ((currentSectionIndex + 1) / sections.length) * 100}%` }}
|
|
307
|
+
></div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
<h2 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">{currentSection.title}</h2>
|
|
313
|
+
{currentSection.description && (
|
|
314
|
+
<p className="mb-6 text-gray-600 dark:text-gray-300">{currentSection.description}</p>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
<div className="space-y-6">
|
|
318
|
+
{currentSection.questions.map(question => renderQuestion(question))}
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div className="mt-8 flex justify-between">
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
onClick={onPrevSection}
|
|
325
|
+
disabled={currentSectionIndex === 0 || readOnly}
|
|
326
|
+
className={`px-4 py-2 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed ${buttonClassName}`}
|
|
327
|
+
>
|
|
328
|
+
{prevButtonText}
|
|
329
|
+
</button>
|
|
330
|
+
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
333
|
+
onClick={onNextSection}
|
|
334
|
+
disabled={readOnly}
|
|
335
|
+
className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed ${buttonClassName}`}
|
|
336
|
+
>
|
|
337
|
+
{isLastSection ? submitButtonText : nextButtonText}
|
|
338
|
+
</button>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
};
|