@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,373 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DPIAResult, DPIASection, DPIARisk } from '../../types/dpia';
|
|
3
|
+
|
|
4
|
+
export interface DPIAReportProps {
|
|
5
|
+
/**
|
|
6
|
+
* The DPIA result to display
|
|
7
|
+
*/
|
|
8
|
+
result: DPIAResult;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The sections of the DPIA questionnaire
|
|
12
|
+
*/
|
|
13
|
+
sections: DPIASection[];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether to show the full report or just a summary
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
showFullReport?: boolean;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether to allow printing the report
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
allowPrint?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether to allow exporting the report as PDF
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
allowExport?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Callback function called when the report is exported
|
|
35
|
+
*/
|
|
36
|
+
onExport?: (format: 'pdf' | 'docx' | 'html') => void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Custom CSS class for the report container
|
|
40
|
+
*/
|
|
41
|
+
className?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Custom CSS class for the buttons
|
|
45
|
+
*/
|
|
46
|
+
buttonClassName?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const DPIAReport: React.FC<DPIAReportProps> = ({
|
|
50
|
+
result,
|
|
51
|
+
sections,
|
|
52
|
+
showFullReport = true,
|
|
53
|
+
allowPrint = true,
|
|
54
|
+
allowExport = true,
|
|
55
|
+
onExport,
|
|
56
|
+
className = '',
|
|
57
|
+
buttonClassName = ''
|
|
58
|
+
}) => {
|
|
59
|
+
// Format a date from timestamp
|
|
60
|
+
const formatDate = (timestamp: number): string => {
|
|
61
|
+
return new Date(timestamp).toLocaleDateString('en-GB', {
|
|
62
|
+
day: 'numeric',
|
|
63
|
+
month: 'long',
|
|
64
|
+
year: 'numeric'
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Get the section title by ID
|
|
69
|
+
const getSectionTitle = (sectionId: string): string => {
|
|
70
|
+
const section = sections.find(s => s.id === sectionId);
|
|
71
|
+
return section?.title || 'Unknown Section';
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Get the question text by ID
|
|
75
|
+
const getQuestionText = (questionId: string): string => {
|
|
76
|
+
let questionText = 'Unknown Question';
|
|
77
|
+
|
|
78
|
+
sections.forEach(section => {
|
|
79
|
+
const question = section.questions.find(q => q.id === questionId);
|
|
80
|
+
if (question) {
|
|
81
|
+
questionText = question.text;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return questionText;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Get the answer text for a question
|
|
89
|
+
const getAnswerText = (questionId: string): string => {
|
|
90
|
+
const answer = result.answers[questionId];
|
|
91
|
+
|
|
92
|
+
if (answer === undefined || answer === null) {
|
|
93
|
+
return 'Not answered';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof answer === 'boolean') {
|
|
97
|
+
return answer ? 'Yes' : 'No';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (Array.isArray(answer)) {
|
|
101
|
+
return answer.join(', ');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return String(answer);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Handle print button click
|
|
108
|
+
const handlePrint = () => {
|
|
109
|
+
window.print();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Handle export button click
|
|
113
|
+
const handleExport = (format: 'pdf' | 'docx' | 'html') => {
|
|
114
|
+
if (onExport) {
|
|
115
|
+
onExport(format);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Render risk level badge
|
|
120
|
+
const renderRiskLevelBadge = (level: 'low' | 'medium' | 'high' | 'critical') => {
|
|
121
|
+
const colorClasses = {
|
|
122
|
+
low: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
|
123
|
+
medium: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
|
124
|
+
high: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200',
|
|
125
|
+
critical: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<span className={`px-2 py-1 rounded text-xs font-medium ${colorClasses[level]}`}>
|
|
130
|
+
{level.charAt(0).toUpperCase() + level.slice(1)}
|
|
131
|
+
</span>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 print:shadow-none print:p-0 ${className}`}>
|
|
137
|
+
{/* Report Header */}
|
|
138
|
+
<div className="mb-8 border-b border-gray-200 dark:border-gray-700 pb-6 print:pb-4">
|
|
139
|
+
<div className="flex justify-between items-start">
|
|
140
|
+
<div>
|
|
141
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
|
142
|
+
Data Protection Impact Assessment Report
|
|
143
|
+
</h1>
|
|
144
|
+
<h2 className="text-xl text-gray-700 dark:text-gray-300 mb-4">
|
|
145
|
+
{result.title}
|
|
146
|
+
</h2>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{(allowPrint || allowExport) && (
|
|
150
|
+
<div className="flex space-x-2 print:hidden">
|
|
151
|
+
{allowPrint && (
|
|
152
|
+
<button
|
|
153
|
+
onClick={handlePrint}
|
|
154
|
+
className={`px-3 py-1 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-600 ${buttonClassName}`}
|
|
155
|
+
>
|
|
156
|
+
<span className="flex items-center">
|
|
157
|
+
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
158
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
|
159
|
+
</svg>
|
|
160
|
+
Print
|
|
161
|
+
</span>
|
|
162
|
+
</button>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{allowExport && (
|
|
166
|
+
<div className="relative inline-block">
|
|
167
|
+
<button
|
|
168
|
+
onClick={() => handleExport('pdf')}
|
|
169
|
+
className={`px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName}`}
|
|
170
|
+
>
|
|
171
|
+
<span className="flex items-center">
|
|
172
|
+
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
173
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
|
174
|
+
</svg>
|
|
175
|
+
Export PDF
|
|
176
|
+
</span>
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
185
|
+
<div>
|
|
186
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
187
|
+
<span className="font-medium">Assessor:</span> {result.assessor.name}, {result.assessor.role}
|
|
188
|
+
</p>
|
|
189
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
190
|
+
<span className="font-medium">Contact:</span> {result.assessor.email}
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
<div>
|
|
194
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
195
|
+
<span className="font-medium">Started:</span> {formatDate(result.startedAt)}
|
|
196
|
+
</p>
|
|
197
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
198
|
+
<span className="font-medium">Completed:</span> {result.completedAt ? formatDate(result.completedAt) : 'In progress'}
|
|
199
|
+
</p>
|
|
200
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
201
|
+
<span className="font-medium">Next review:</span> {result.reviewDate ? formatDate(result.reviewDate) : 'Not scheduled'}
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
{/* Executive Summary */}
|
|
208
|
+
<div className="mb-8">
|
|
209
|
+
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
|
210
|
+
Executive Summary
|
|
211
|
+
</h2>
|
|
212
|
+
|
|
213
|
+
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-md mb-4">
|
|
214
|
+
<div className="flex items-center mb-2">
|
|
215
|
+
<span className="font-medium mr-2">Overall Risk Level:</span>
|
|
216
|
+
{renderRiskLevelBadge(result.overallRiskLevel)}
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div className="mb-2">
|
|
220
|
+
<span className="font-medium">Conclusion:</span> {result.conclusion}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div>
|
|
224
|
+
<span className="font-medium">Can Proceed:</span> {result.canProceed ? 'Yes' : 'No'}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<div>
|
|
229
|
+
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
|
|
230
|
+
Processing Activity Description
|
|
231
|
+
</h3>
|
|
232
|
+
<p className="text-gray-700 dark:text-gray-300 mb-4">
|
|
233
|
+
{result.processingDescription}
|
|
234
|
+
</p>
|
|
235
|
+
|
|
236
|
+
{result.recommendations && result.recommendations.length > 0 && (
|
|
237
|
+
<div>
|
|
238
|
+
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
|
|
239
|
+
Key Recommendations
|
|
240
|
+
</h3>
|
|
241
|
+
<ul className="list-disc pl-5 text-gray-700 dark:text-gray-300">
|
|
242
|
+
{result.recommendations.map((recommendation, index) => (
|
|
243
|
+
<li key={index}>{recommendation}</li>
|
|
244
|
+
))}
|
|
245
|
+
</ul>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{/* Identified Risks */}
|
|
252
|
+
<div className="mb-8">
|
|
253
|
+
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
|
254
|
+
Identified Risks
|
|
255
|
+
</h2>
|
|
256
|
+
|
|
257
|
+
{result.risks.length === 0 ? (
|
|
258
|
+
<p className="text-gray-700 dark:text-gray-300">No risks identified.</p>
|
|
259
|
+
) : (
|
|
260
|
+
<div className="overflow-x-auto">
|
|
261
|
+
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
262
|
+
<thead className="bg-gray-50 dark:bg-gray-700">
|
|
263
|
+
<tr>
|
|
264
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
265
|
+
Risk
|
|
266
|
+
</th>
|
|
267
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
268
|
+
Likelihood
|
|
269
|
+
</th>
|
|
270
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
271
|
+
Impact
|
|
272
|
+
</th>
|
|
273
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
274
|
+
Risk Level
|
|
275
|
+
</th>
|
|
276
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
277
|
+
Mitigated
|
|
278
|
+
</th>
|
|
279
|
+
</tr>
|
|
280
|
+
</thead>
|
|
281
|
+
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
282
|
+
{result.risks.map((risk) => (
|
|
283
|
+
<tr key={risk.id}>
|
|
284
|
+
<td className="px-6 py-4 whitespace-normal text-sm text-gray-900 dark:text-gray-100">
|
|
285
|
+
{risk.description}
|
|
286
|
+
</td>
|
|
287
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
|
288
|
+
{risk.likelihood} / 5
|
|
289
|
+
</td>
|
|
290
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
|
291
|
+
{risk.impact} / 5
|
|
292
|
+
</td>
|
|
293
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
|
294
|
+
{renderRiskLevelBadge(risk.level)}
|
|
295
|
+
</td>
|
|
296
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
|
297
|
+
{risk.mitigated ? (
|
|
298
|
+
<span className="text-green-600 dark:text-green-400">Yes</span>
|
|
299
|
+
) : (
|
|
300
|
+
<span className="text-red-600 dark:text-red-400">No</span>
|
|
301
|
+
)}
|
|
302
|
+
</td>
|
|
303
|
+
</tr>
|
|
304
|
+
))}
|
|
305
|
+
</tbody>
|
|
306
|
+
</table>
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Full Assessment Details */}
|
|
312
|
+
{showFullReport && (
|
|
313
|
+
<div>
|
|
314
|
+
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
|
315
|
+
Full Assessment Details
|
|
316
|
+
</h2>
|
|
317
|
+
|
|
318
|
+
{sections.map((section) => {
|
|
319
|
+
const sectionQuestions = section.questions.filter(question =>
|
|
320
|
+
result.answers[question.id] !== undefined
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
if (sectionQuestions.length === 0) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<div key={section.id} className="mb-6">
|
|
329
|
+
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
|
330
|
+
{section.title}
|
|
331
|
+
</h3>
|
|
332
|
+
|
|
333
|
+
<div className="overflow-x-auto">
|
|
334
|
+
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
335
|
+
<thead className="bg-gray-50 dark:bg-gray-700">
|
|
336
|
+
<tr>
|
|
337
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
338
|
+
Question
|
|
339
|
+
</th>
|
|
340
|
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
341
|
+
Answer
|
|
342
|
+
</th>
|
|
343
|
+
</tr>
|
|
344
|
+
</thead>
|
|
345
|
+
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
346
|
+
{sectionQuestions.map((question) => (
|
|
347
|
+
<tr key={question.id}>
|
|
348
|
+
<td className="px-6 py-4 whitespace-normal text-sm text-gray-900 dark:text-gray-100">
|
|
349
|
+
{question.text}
|
|
350
|
+
</td>
|
|
351
|
+
<td className="px-6 py-4 whitespace-normal text-sm text-gray-500 dark:text-gray-400">
|
|
352
|
+
{getAnswerText(question.id)}
|
|
353
|
+
</td>
|
|
354
|
+
</tr>
|
|
355
|
+
))}
|
|
356
|
+
</tbody>
|
|
357
|
+
</table>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
})}
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{/* Footer */}
|
|
366
|
+
<div className="mt-8 pt-4 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-500 dark:text-gray-400">
|
|
367
|
+
<p>This DPIA was conducted in accordance with the Nigeria Data Protection Regulation (NDPR).</p>
|
|
368
|
+
<p>DPIA Report Version: {result.version}</p>
|
|
369
|
+
<p>Generated on: {new Date().toLocaleDateString()}</p>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface Step {
|
|
4
|
+
/**
|
|
5
|
+
* Unique identifier for the step
|
|
6
|
+
*/
|
|
7
|
+
id: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display label for the step
|
|
11
|
+
*/
|
|
12
|
+
label: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional description for the step
|
|
16
|
+
*/
|
|
17
|
+
description?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether the step is completed
|
|
21
|
+
*/
|
|
22
|
+
completed: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the step is the current active step
|
|
26
|
+
*/
|
|
27
|
+
active: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Optional icon for the step
|
|
31
|
+
*/
|
|
32
|
+
icon?: React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StepIndicatorProps {
|
|
36
|
+
/**
|
|
37
|
+
* Array of steps to display
|
|
38
|
+
*/
|
|
39
|
+
steps: Step[];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Callback function called when a step is clicked
|
|
43
|
+
*/
|
|
44
|
+
onStepClick?: (stepId: string) => void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether the steps are clickable
|
|
48
|
+
* @default true
|
|
49
|
+
*/
|
|
50
|
+
clickable?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Orientation of the step indicator
|
|
54
|
+
* @default "horizontal"
|
|
55
|
+
*/
|
|
56
|
+
orientation?: 'horizontal' | 'vertical';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Custom CSS class for the container
|
|
60
|
+
*/
|
|
61
|
+
className?: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Custom CSS class for the active step
|
|
65
|
+
*/
|
|
66
|
+
activeStepClassName?: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Custom CSS class for completed steps
|
|
70
|
+
*/
|
|
71
|
+
completedStepClassName?: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Custom CSS class for incomplete steps
|
|
75
|
+
*/
|
|
76
|
+
incompleteStepClassName?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const StepIndicator: React.FC<StepIndicatorProps> = ({
|
|
80
|
+
steps,
|
|
81
|
+
onStepClick,
|
|
82
|
+
clickable = true,
|
|
83
|
+
orientation = 'horizontal',
|
|
84
|
+
className = '',
|
|
85
|
+
activeStepClassName = '',
|
|
86
|
+
completedStepClassName = '',
|
|
87
|
+
incompleteStepClassName = ''
|
|
88
|
+
}) => {
|
|
89
|
+
const handleStepClick = (stepId: string) => {
|
|
90
|
+
if (clickable && onStepClick) {
|
|
91
|
+
onStepClick(stepId);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const isVertical = orientation === 'vertical';
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
className={`${className} ${
|
|
100
|
+
isVertical
|
|
101
|
+
? 'flex flex-col space-y-4'
|
|
102
|
+
: 'flex items-center justify-between'
|
|
103
|
+
}`}
|
|
104
|
+
>
|
|
105
|
+
{steps.map((step, index) => {
|
|
106
|
+
const isLast = index === steps.length - 1;
|
|
107
|
+
const stepClassName = step.active
|
|
108
|
+
? `font-medium ${activeStepClassName || 'text-blue-600 dark:text-blue-400'}`
|
|
109
|
+
: step.completed
|
|
110
|
+
? `${completedStepClassName || 'text-green-600 dark:text-green-400'}`
|
|
111
|
+
: `${incompleteStepClassName || 'text-gray-500 dark:text-gray-400'}`;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<React.Fragment key={step.id}>
|
|
115
|
+
<div
|
|
116
|
+
className={`
|
|
117
|
+
${isVertical ? 'flex items-start' : 'flex flex-col items-center'}
|
|
118
|
+
${clickable ? 'cursor-pointer' : ''}
|
|
119
|
+
`}
|
|
120
|
+
onClick={() => handleStepClick(step.id)}
|
|
121
|
+
>
|
|
122
|
+
<div className={`
|
|
123
|
+
flex items-center justify-center
|
|
124
|
+
${isVertical ? 'mr-4' : ''}
|
|
125
|
+
`}>
|
|
126
|
+
<div className={`
|
|
127
|
+
flex items-center justify-center
|
|
128
|
+
w-8 h-8 rounded-full
|
|
129
|
+
${step.active
|
|
130
|
+
? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 border-2 border-blue-600 dark:border-blue-400'
|
|
131
|
+
: step.completed
|
|
132
|
+
? 'bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-400 border-2 border-green-600 dark:border-green-400'
|
|
133
|
+
: 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 border-2 border-gray-300 dark:border-gray-600'
|
|
134
|
+
}
|
|
135
|
+
`}>
|
|
136
|
+
{step.icon ? (
|
|
137
|
+
step.icon
|
|
138
|
+
) : step.completed ? (
|
|
139
|
+
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
140
|
+
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
141
|
+
</svg>
|
|
142
|
+
) : (
|
|
143
|
+
<span>{index + 1}</span>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className={`
|
|
149
|
+
${isVertical ? 'flex-1' : 'mt-2 text-center'}
|
|
150
|
+
`}>
|
|
151
|
+
<div className={`text-sm font-medium ${stepClassName}`}>
|
|
152
|
+
{step.label}
|
|
153
|
+
</div>
|
|
154
|
+
{step.description && (
|
|
155
|
+
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
156
|
+
{step.description}
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{!isLast && (
|
|
163
|
+
<div className={`
|
|
164
|
+
${isVertical
|
|
165
|
+
? 'ml-4 h-8 border-l-2 border-gray-300 dark:border-gray-600'
|
|
166
|
+
: 'w-full border-t-2 border-gray-300 dark:border-gray-600 hidden sm:block'}
|
|
167
|
+
`} />
|
|
168
|
+
)}
|
|
169
|
+
</React.Fragment>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
};
|