@tantainnovative/ndpr-toolkit 1.0.3 → 1.0.4

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 (156) hide show
  1. package/next-env.d.ts +5 -0
  2. package/package.json +1 -1
  3. package/.claude/settings.local.json +0 -20
  4. package/.eslintrc.json +0 -10
  5. package/.github/workflows/ci.yml +0 -36
  6. package/.github/workflows/nextjs.yml +0 -104
  7. package/.husky/commit-msg +0 -4
  8. package/.husky/pre-commit +0 -4
  9. package/.lintstagedrc.js +0 -4
  10. package/.nvmrc +0 -1
  11. package/.versionrc +0 -17
  12. package/CLAUDE.md +0 -90
  13. package/commitlint.config.js +0 -36
  14. package/eslint.config.mjs +0 -16
  15. package/jest.config.js +0 -31
  16. package/jest.setup.js +0 -15
  17. package/next.config.js +0 -15
  18. package/next.config.ts +0 -62
  19. package/packages/ndpr-toolkit/README.md +0 -467
  20. package/packages/ndpr-toolkit/jest.config.js +0 -23
  21. package/packages/ndpr-toolkit/package-lock.json +0 -8197
  22. package/packages/ndpr-toolkit/package.json +0 -71
  23. package/packages/ndpr-toolkit/rollup.config.js +0 -34
  24. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
  25. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
  26. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
  27. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
  28. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
  29. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
  30. package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
  31. package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
  32. package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
  33. package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
  34. package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
  35. package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
  36. package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
  37. package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +0 -701
  38. package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +0 -631
  39. package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +0 -569
  40. package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +0 -496
  41. package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +0 -270
  42. package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +0 -217
  43. package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +0 -206
  44. package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +0 -342
  45. package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +0 -373
  46. package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +0 -174
  47. package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +0 -717
  48. package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +0 -476
  49. package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +0 -620
  50. package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +0 -541
  51. package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +0 -454
  52. package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +0 -333
  53. package/packages/ndpr-toolkit/src/hooks/useBreach.ts +0 -409
  54. package/packages/ndpr-toolkit/src/hooks/useConsent.ts +0 -263
  55. package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +0 -457
  56. package/packages/ndpr-toolkit/src/hooks/useDSR.ts +0 -236
  57. package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +0 -428
  58. package/packages/ndpr-toolkit/src/index.ts +0 -44
  59. package/packages/ndpr-toolkit/src/setupTests.ts +0 -5
  60. package/packages/ndpr-toolkit/src/types/breach.ts +0 -283
  61. package/packages/ndpr-toolkit/src/types/consent.ts +0 -111
  62. package/packages/ndpr-toolkit/src/types/dpia.ts +0 -236
  63. package/packages/ndpr-toolkit/src/types/dsr.ts +0 -192
  64. package/packages/ndpr-toolkit/src/types/index.ts +0 -42
  65. package/packages/ndpr-toolkit/src/types/privacy.ts +0 -246
  66. package/packages/ndpr-toolkit/src/utils/breach.ts +0 -122
  67. package/packages/ndpr-toolkit/src/utils/consent.ts +0 -51
  68. package/packages/ndpr-toolkit/src/utils/dpia.ts +0 -104
  69. package/packages/ndpr-toolkit/src/utils/dsr.ts +0 -77
  70. package/packages/ndpr-toolkit/src/utils/privacy.ts +0 -100
  71. package/packages/ndpr-toolkit/tsconfig.json +0 -23
  72. package/postcss.config.mjs +0 -5
  73. package/src/__tests__/example.test.ts +0 -13
  74. package/src/__tests__/requestService.test.ts +0 -57
  75. package/src/app/accessibility.css +0 -70
  76. package/src/app/docs/components/DocLayout.tsx +0 -267
  77. package/src/app/docs/components/breach-notification/page.tsx +0 -797
  78. package/src/app/docs/components/consent-management/page.tsx +0 -576
  79. package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
  80. package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
  81. package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
  82. package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
  83. package/src/app/docs/components/hooks/page.tsx +0 -305
  84. package/src/app/docs/components/page.tsx +0 -84
  85. package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
  86. package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
  87. package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
  88. package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
  89. package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
  90. package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
  91. package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
  92. package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
  93. package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
  94. package/src/app/docs/guides/managing-consent/page.tsx +0 -738
  95. package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
  96. package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
  97. package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
  98. package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
  99. package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
  100. package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
  101. package/src/app/docs/guides/page.tsx +0 -67
  102. package/src/app/docs/layout.tsx +0 -15
  103. package/src/app/docs/metadata.ts +0 -31
  104. package/src/app/docs/page.tsx +0 -572
  105. package/src/app/favicon.ico +0 -0
  106. package/src/app/globals.css +0 -123
  107. package/src/app/layout.tsx +0 -37
  108. package/src/app/ndpr-demos/breach/page.tsx +0 -354
  109. package/src/app/ndpr-demos/consent/page.tsx +0 -366
  110. package/src/app/ndpr-demos/dpia/page.tsx +0 -495
  111. package/src/app/ndpr-demos/dsr/page.tsx +0 -280
  112. package/src/app/ndpr-demos/page.tsx +0 -73
  113. package/src/app/ndpr-demos/policy/page.tsx +0 -771
  114. package/src/app/page.tsx +0 -452
  115. package/src/components/ErrorBoundary.tsx +0 -90
  116. package/src/components/breach-notification/BreachNotificationForm.tsx +0 -479
  117. package/src/components/consent/ConsentBanner.tsx +0 -159
  118. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +0 -419
  119. package/src/components/docs/DocLayout.tsx +0 -289
  120. package/src/components/docs/index.ts +0 -2
  121. package/src/components/dpia/DPIAQuestionnaire.tsx +0 -483
  122. package/src/components/privacy-policy/PolicyGenerator.tsx +0 -1062
  123. package/src/components/privacy-policy/data.ts +0 -98
  124. package/src/components/privacy-policy/shared/CheckboxField.tsx +0 -38
  125. package/src/components/privacy-policy/shared/CheckboxGroup.tsx +0 -85
  126. package/src/components/privacy-policy/shared/FormField.tsx +0 -79
  127. package/src/components/privacy-policy/shared/StepIndicator.tsx +0 -86
  128. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +0 -335
  129. package/src/components/privacy-policy/steps/DataCollectionStep.tsx +0 -231
  130. package/src/components/privacy-policy/steps/DataSharingStep.tsx +0 -418
  131. package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +0 -202
  132. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +0 -172
  133. package/src/components/ui/Badge.tsx +0 -46
  134. package/src/components/ui/Button.tsx +0 -59
  135. package/src/components/ui/Card.tsx +0 -92
  136. package/src/components/ui/Checkbox.tsx +0 -57
  137. package/src/components/ui/FormField.tsx +0 -50
  138. package/src/components/ui/Input.tsx +0 -38
  139. package/src/components/ui/Loading.tsx +0 -201
  140. package/src/components/ui/Select.tsx +0 -42
  141. package/src/components/ui/TextArea.tsx +0 -38
  142. package/src/components/ui/label.tsx +0 -24
  143. package/src/components/ui/switch.tsx +0 -31
  144. package/src/components/ui/tabs.tsx +0 -66
  145. package/src/hooks/useConsent.ts +0 -64
  146. package/src/hooks/useLoadingState.ts +0 -85
  147. package/src/lib/consentService.ts +0 -137
  148. package/src/lib/dpiaQuestions.ts +0 -148
  149. package/src/lib/requestService.ts +0 -75
  150. package/src/lib/sanitize.ts +0 -108
  151. package/src/lib/storage.ts +0 -222
  152. package/src/lib/utils.ts +0 -6
  153. package/src/types/html-to-docx.d.ts +0 -30
  154. package/src/types/index.ts +0 -72
  155. package/tailwind.config.ts +0 -65
  156. package/tsconfig.json +0 -41
@@ -1,717 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { DSRRequest, DSRStatus, DSRType } from '../../types/dsr';
3
- import { formatDSRRequest } from '../../utils/dsr';
4
-
5
- export interface DSRDashboardProps {
6
- /**
7
- * List of DSR requests to display
8
- */
9
- requests: DSRRequest[];
10
-
11
- /**
12
- * Callback function called when a request is selected
13
- */
14
- onSelectRequest?: (requestId: string) => void;
15
-
16
- /**
17
- * Callback function called when a request status is updated
18
- */
19
- onUpdateStatus?: (requestId: string, status: DSRStatus) => void;
20
-
21
- /**
22
- * Callback function called when a request is assigned
23
- */
24
- onAssignRequest?: (requestId: string, assignee: string) => void;
25
-
26
- /**
27
- * Title displayed on the dashboard
28
- * @default "Data Subject Request Dashboard"
29
- */
30
- title?: string;
31
-
32
- /**
33
- * Description text displayed on the dashboard
34
- * @default "Track and manage data subject requests in compliance with NDPR requirements."
35
- */
36
- description?: string;
37
-
38
- /**
39
- * Custom CSS class for the dashboard
40
- */
41
- className?: string;
42
-
43
- /**
44
- * Custom CSS class for the buttons
45
- */
46
- buttonClassName?: string;
47
-
48
- /**
49
- * Whether to show the request details
50
- * @default true
51
- */
52
- showRequestDetails?: boolean;
53
-
54
- /**
55
- * Whether to show the request timeline
56
- * @default true
57
- */
58
- showRequestTimeline?: boolean;
59
-
60
- /**
61
- * Whether to show the deadline alerts
62
- * @default true
63
- */
64
- showDeadlineAlerts?: boolean;
65
-
66
- /**
67
- * List of possible assignees
68
- */
69
- assignees?: string[];
70
- }
71
-
72
- export const DSRDashboard: React.FC<DSRDashboardProps> = ({
73
- requests,
74
- onSelectRequest,
75
- onUpdateStatus,
76
- onAssignRequest,
77
- title = "Data Subject Request Dashboard",
78
- description = "Track and manage data subject requests in compliance with NDPR requirements.",
79
- className = "",
80
- buttonClassName = "",
81
- showRequestDetails = true,
82
- showRequestTimeline = true,
83
- showDeadlineAlerts = true,
84
- assignees = []
85
- }) => {
86
- const [selectedRequestId, setSelectedRequestId] = useState<string | null>(null);
87
- const [filteredRequests, setFilteredRequests] = useState<DSRRequest[]>(requests);
88
- const [statusFilter, setStatusFilter] = useState<string>('all');
89
- const [typeFilter, setTypeFilter] = useState<string>('all');
90
- const [searchTerm, setSearchTerm] = useState<string>('');
91
- const [sortBy, setSortBy] = useState<string>('createdAt');
92
- const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
93
- const [assignee, setAssignee] = useState<string>('');
94
-
95
- // Update filtered requests when filters change
96
- useEffect(() => {
97
- let filtered = [...requests];
98
-
99
- // Apply status filter
100
- if (statusFilter !== 'all') {
101
- filtered = filtered.filter(request => request.status === statusFilter);
102
- }
103
-
104
- // Apply type filter
105
- if (typeFilter !== 'all') {
106
- filtered = filtered.filter(request => request.type === typeFilter);
107
- }
108
-
109
- // Apply search filter
110
- if (searchTerm) {
111
- const term = searchTerm.toLowerCase();
112
- filtered = filtered.filter(request =>
113
- request.subject.name.toLowerCase().includes(term) ||
114
- request.subject.email.toLowerCase().includes(term) ||
115
- (request.description && request.description.toLowerCase().includes(term))
116
- );
117
- }
118
-
119
- // Apply sorting
120
- filtered.sort((a, b) => {
121
- let comparison = 0;
122
-
123
- switch (sortBy) {
124
- case 'createdAt':
125
- comparison = a.createdAt - b.createdAt;
126
- break;
127
- case 'dueDate':
128
- comparison = (a.dueDate || 0) - (b.dueDate || 0);
129
- break;
130
- case 'type':
131
- comparison = a.type.localeCompare(b.type);
132
- break;
133
- case 'status':
134
- comparison = a.status.localeCompare(b.status);
135
- break;
136
- default:
137
- comparison = a.createdAt - b.createdAt;
138
- }
139
-
140
- return sortDirection === 'asc' ? comparison : -comparison;
141
- });
142
-
143
- setFilteredRequests(filtered);
144
- }, [requests, statusFilter, typeFilter, searchTerm, sortBy, sortDirection]);
145
-
146
- // Select the first request if none is selected
147
- useEffect(() => {
148
- if (filteredRequests.length > 0 && !selectedRequestId) {
149
- setSelectedRequestId(filteredRequests[0].id);
150
- }
151
- }, [filteredRequests, selectedRequestId]);
152
-
153
- // Handle request selection
154
- const handleSelectRequest = (requestId: string) => {
155
- setSelectedRequestId(requestId);
156
- if (onSelectRequest) {
157
- onSelectRequest(requestId);
158
- }
159
- };
160
-
161
- // Handle status update
162
- const handleUpdateStatus = (status: DSRStatus) => {
163
- if (selectedRequestId && onUpdateStatus) {
164
- onUpdateStatus(selectedRequestId, status);
165
- }
166
- };
167
-
168
- // Handle request assignment
169
- const handleAssignRequest = () => {
170
- if (selectedRequestId && assignee && onAssignRequest) {
171
- onAssignRequest(selectedRequestId, assignee);
172
- setAssignee('');
173
- }
174
- };
175
-
176
- // Format a date from timestamp
177
- const formatDate = (timestamp: number): string => {
178
- return new Date(timestamp).toLocaleDateString();
179
- };
180
-
181
- // Calculate days remaining until deadline
182
- const calculateDaysRemaining = (dueDate: number): number => {
183
- const now = Date.now();
184
- const remaining = (dueDate - now) / (24 * 60 * 60 * 1000);
185
- return Math.ceil(remaining);
186
- };
187
-
188
- // Get the selected request
189
- const selectedRequest = selectedRequestId
190
- ? requests.find(request => request.id === selectedRequestId)
191
- : null;
192
-
193
- // Render type badge
194
- const renderTypeBadge = (type: DSRType) => {
195
- const colorClasses = {
196
- access: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
197
- rectification: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
198
- erasure: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
199
- restriction: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
200
- portability: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200',
201
- objection: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200'
202
- };
203
-
204
- return (
205
- <span className={`px-2 py-1 rounded text-xs font-medium ${colorClasses[type]}`}>
206
- {type.charAt(0).toUpperCase() + type.slice(1)}
207
- </span>
208
- );
209
- };
210
-
211
- // Render status badge
212
- const renderStatusBadge = (status: DSRStatus) => {
213
- const colorClasses = {
214
- pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
215
- inProgress: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
216
- completed: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
217
- rejected: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
218
- awaitingVerification: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'
219
- };
220
-
221
- return (
222
- <span className={`px-2 py-1 rounded text-xs font-medium ${colorClasses[status]}`}>
223
- {status === 'inProgress' ? 'In Progress' :
224
- status === 'awaitingVerification' ? 'Awaiting Verification' :
225
- status.charAt(0).toUpperCase() + status.slice(1)}
226
- </span>
227
- );
228
- };
229
-
230
- // Render deadline alert
231
- const renderDeadlineAlert = (request: DSRRequest) => {
232
- if (!request.dueDate) return null;
233
-
234
- const daysRemaining = calculateDaysRemaining(request.dueDate);
235
-
236
- if (daysRemaining <= 0) {
237
- return (
238
- <div className="bg-red-50 dark:bg-red-900/20 p-3 rounded-md">
239
- <p className="text-sm text-red-800 dark:text-red-200 font-medium">
240
- Deadline Passed
241
- </p>
242
- <p className="text-xs text-red-700 dark:text-red-300 mt-1">
243
- The response deadline has passed. Immediate action is required.
244
- </p>
245
- </div>
246
- );
247
- }
248
-
249
- if (daysRemaining <= 3) {
250
- return (
251
- <div className="bg-red-50 dark:bg-red-900/20 p-3 rounded-md">
252
- <p className="text-sm text-red-800 dark:text-red-200 font-medium">
253
- Urgent: Deadline Approaching
254
- </p>
255
- <p className="text-xs text-red-700 dark:text-red-300 mt-1">
256
- Only {daysRemaining} day{daysRemaining !== 1 ? 's' : ''} remaining until the response deadline.
257
- </p>
258
- </div>
259
- );
260
- }
261
-
262
- if (daysRemaining <= 7) {
263
- return (
264
- <div className="bg-yellow-50 dark:bg-yellow-900/20 p-3 rounded-md">
265
- <p className="text-sm text-yellow-800 dark:text-yellow-200 font-medium">
266
- Deadline Approaching
267
- </p>
268
- <p className="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
269
- {daysRemaining} days remaining until the response deadline.
270
- </p>
271
- </div>
272
- );
273
- }
274
-
275
- return (
276
- <div className="bg-green-50 dark:bg-green-900/20 p-3 rounded-md">
277
- <p className="text-sm text-green-800 dark:text-green-200 font-medium">
278
- Deadline Tracking
279
- </p>
280
- <p className="text-xs text-green-700 dark:text-green-300 mt-1">
281
- {daysRemaining} days remaining until the response deadline.
282
- </p>
283
- </div>
284
- );
285
- };
286
-
287
- // Render request timeline
288
- const renderRequestTimeline = (request: DSRRequest) => {
289
- const timeline = [
290
- {
291
- title: 'Request Received',
292
- date: request.createdAt,
293
- completed: true,
294
- description: `Request was received on ${formatDate(request.createdAt)}.`
295
- }
296
- ];
297
-
298
- if (request.verifiedAt) {
299
- timeline.push({
300
- title: 'Identity Verified',
301
- date: request.verifiedAt,
302
- completed: true,
303
- description: `Data subject's identity was verified on ${formatDate(request.verifiedAt)}.`
304
- });
305
- } else if (request.status === 'awaitingVerification') {
306
- timeline.push({
307
- title: 'Identity Verification',
308
- date: Date.now(),
309
- completed: false,
310
- description: 'Awaiting verification of data subject\'s identity.'
311
- });
312
- }
313
-
314
- if (request.status === 'inProgress' || request.status === 'completed' || request.status === 'rejected') {
315
- timeline.push({
316
- title: 'Processing Started',
317
- date: request.updatedAt,
318
- completed: true,
319
- description: `Request processing started on ${formatDate(request.updatedAt)}.`
320
- });
321
- }
322
-
323
- if (request.status === 'completed') {
324
- timeline.push({
325
- title: 'Request Completed',
326
- date: request.completedAt || Date.now(),
327
- completed: true,
328
- description: `Request was completed on ${formatDate(request.completedAt || Date.now())}.`
329
- });
330
- } else if (request.status === 'rejected') {
331
- timeline.push({
332
- title: 'Request Rejected',
333
- date: request.completedAt || Date.now(),
334
- completed: true,
335
- description: `Request was rejected on ${formatDate(request.completedAt || Date.now())}.${request.rejectionReason ? ` Reason: ${request.rejectionReason}` : ''}`
336
- });
337
- }
338
-
339
- if (request.dueDate) {
340
- timeline.push({
341
- title: 'Response Deadline',
342
- date: request.dueDate,
343
- completed: Date.now() > request.dueDate,
344
- description: `Response is due by ${formatDate(request.dueDate)}.`
345
- });
346
- }
347
-
348
- return (
349
- <div className="mt-6">
350
- <h3 className="text-lg font-medium mb-4">Request Timeline</h3>
351
- <ol className="relative border-l border-gray-200 dark:border-gray-700">
352
- {timeline.map((item, index) => (
353
- <li key={index} className="mb-6 ml-4">
354
- <div className={`absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 border ${
355
- item.completed
356
- ? 'bg-green-500 border-green-500 dark:border-green-500'
357
- : 'bg-gray-200 border-gray-200 dark:bg-gray-700 dark:border-gray-700'
358
- }`}></div>
359
- <time className="mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500">
360
- {item.date ? formatDate(item.date) : 'Pending'}
361
- </time>
362
- <h4 className="text-sm font-semibold text-gray-900 dark:text-white">
363
- {item.title}
364
- </h4>
365
- <p className="text-xs text-gray-500 dark:text-gray-400">
366
- {item.description}
367
- </p>
368
- </li>
369
- ))}
370
- </ol>
371
- </div>
372
- );
373
- };
374
-
375
- // Render the type filter options
376
- const renderTypeOptions = () => {
377
- const options = [
378
- { value: 'all', label: 'All Types' },
379
- { value: 'access', label: 'Access' },
380
- { value: 'rectification', label: 'Rectification' },
381
- { value: 'erasure', label: 'Erasure' },
382
- { value: 'restriction', label: 'Restriction' },
383
- { value: 'portability', label: 'Portability' },
384
- { value: 'objection', label: 'Objection' }
385
- ];
386
-
387
- return options.map(option => (
388
- <option key={option.value} value={option.value}>
389
- {option.label}
390
- </option>
391
- ));
392
- };
393
-
394
- // Render the status filter options
395
- const renderStatusOptions = () => {
396
- const options = [
397
- { value: 'all', label: 'All Statuses' },
398
- { value: 'pending', label: 'Pending' },
399
- { value: 'awaitingVerification', label: 'Awaiting Verification' },
400
- { value: 'inProgress', label: 'In Progress' },
401
- { value: 'completed', label: 'Completed' },
402
- { value: 'rejected', label: 'Rejected' }
403
- ];
404
-
405
- return options.map(option => (
406
- <option key={option.value} value={option.value}>
407
- {option.label}
408
- </option>
409
- ));
410
- };
411
-
412
- // Render the status update options
413
- const renderStatusUpdateOptions = () => {
414
- const options = [
415
- { value: 'pending', label: 'Pending' },
416
- { value: 'awaitingVerification', label: 'Awaiting Verification' },
417
- { value: 'inProgress', label: 'In Progress' },
418
- { value: 'completed', label: 'Completed' },
419
- { value: 'rejected', label: 'Rejected' }
420
- ];
421
-
422
- return options.map(option => (
423
- <option key={option.value} value={option.value}>
424
- {option.label}
425
- </option>
426
- ));
427
- };
428
-
429
- return (
430
- <div className={`bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md ${className}`}>
431
- <h2 className="text-xl font-bold mb-2">{title}</h2>
432
- <p className="mb-6 text-gray-600 dark:text-gray-300">{description}</p>
433
-
434
- {/* Filters and Search */}
435
- <div className="mb-6 grid grid-cols-1 md:grid-cols-4 gap-4">
436
- <div>
437
- <label htmlFor="statusFilter" className="block text-sm font-medium mb-1">
438
- Status Filter
439
- </label>
440
- <select
441
- id="statusFilter"
442
- value={statusFilter}
443
- onChange={e => setStatusFilter(e.target.value)}
444
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
445
- >
446
- {renderStatusOptions()}
447
- </select>
448
- </div>
449
-
450
- <div>
451
- <label htmlFor="typeFilter" className="block text-sm font-medium mb-1">
452
- Request Type Filter
453
- </label>
454
- <select
455
- id="typeFilter"
456
- value={typeFilter}
457
- onChange={e => setTypeFilter(e.target.value)}
458
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
459
- >
460
- {renderTypeOptions()}
461
- </select>
462
- </div>
463
-
464
- <div>
465
- <label htmlFor="sortBy" className="block text-sm font-medium mb-1">
466
- Sort By
467
- </label>
468
- <select
469
- id="sortBy"
470
- value={sortBy}
471
- onChange={e => setSortBy(e.target.value)}
472
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
473
- >
474
- <option value="createdAt">Date Received</option>
475
- <option value="dueDate">Due Date</option>
476
- <option value="type">Request Type</option>
477
- <option value="status">Status</option>
478
- </select>
479
- </div>
480
-
481
- <div>
482
- <label htmlFor="searchTerm" className="block text-sm font-medium mb-1">
483
- Search
484
- </label>
485
- <input
486
- type="text"
487
- id="searchTerm"
488
- value={searchTerm}
489
- onChange={e => setSearchTerm(e.target.value)}
490
- placeholder="Search requests..."
491
- className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
492
- />
493
- </div>
494
- </div>
495
-
496
- {/* Request List and Details */}
497
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
498
- {/* Request List */}
499
- <div className="md:col-span-1">
500
- <h3 className="text-lg font-medium mb-3">DSR Requests</h3>
501
-
502
- {filteredRequests.length === 0 ? (
503
- <p className="text-gray-500 dark:text-gray-400 text-sm">
504
- No data subject requests found.
505
- </p>
506
- ) : (
507
- <div className="space-y-2 max-h-96 overflow-y-auto pr-2">
508
- {filteredRequests.map(request => {
509
- // Calculate days remaining for the list item
510
- const daysRemaining = request.dueDate ? calculateDaysRemaining(request.dueDate) : null;
511
-
512
- // Determine deadline status for the list item
513
- let deadlineStatus = null;
514
- if (daysRemaining !== null) {
515
- if (daysRemaining <= 0) {
516
- deadlineStatus = (
517
- <span className="text-xs text-red-600 dark:text-red-400 font-bold">
518
- Overdue
519
- </span>
520
- );
521
- } else if (daysRemaining <= 3) {
522
- deadlineStatus = (
523
- <span className="text-xs text-red-600 dark:text-red-400">
524
- Urgent
525
- </span>
526
- );
527
- } else if (daysRemaining <= 7) {
528
- deadlineStatus = (
529
- <span className="text-xs text-yellow-600 dark:text-yellow-400">
530
- Soon
531
- </span>
532
- );
533
- }
534
- }
535
-
536
- return (
537
- <div
538
- key={request.id}
539
- className={`p-3 rounded-md cursor-pointer ${
540
- selectedRequestId === request.id
541
- ? 'bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800'
542
- : 'bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600'
543
- }`}
544
- onClick={() => handleSelectRequest(request.id)}
545
- >
546
- <div className="flex justify-between items-start mb-1">
547
- <h4 className="font-medium text-sm">{request.subject.name}</h4>
548
- {renderTypeBadge(request.type)}
549
- </div>
550
- <p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
551
- {request.subject.email}
552
- </p>
553
- <p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
554
- Received: {formatDate(request.createdAt)}
555
- </p>
556
- <div className="flex justify-between items-center mt-2">
557
- <div>
558
- {renderStatusBadge(request.status)}
559
- </div>
560
- <div>
561
- {deadlineStatus}
562
- </div>
563
- </div>
564
- </div>
565
- );
566
- })}
567
- </div>
568
- )}
569
- </div>
570
-
571
- {/* Request Details */}
572
- <div className="md:col-span-2">
573
- {selectedRequest ? (
574
- <div>
575
- <div className="flex justify-between items-start mb-4">
576
- <h3 className="text-lg font-medium">{selectedRequest.subject.name}</h3>
577
- <div className="flex space-x-2">
578
- {renderTypeBadge(selectedRequest.type)}
579
- {renderStatusBadge(selectedRequest.status)}
580
- </div>
581
- </div>
582
-
583
- {/* Deadline Alert */}
584
- {showDeadlineAlerts && selectedRequest.dueDate && (
585
- <div className="mb-4">
586
- {renderDeadlineAlert(selectedRequest)}
587
- </div>
588
- )}
589
-
590
- {/* Request Details */}
591
- {showRequestDetails && (
592
- <div className="mb-6">
593
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
594
- <div>
595
- <p className="text-sm"><span className="font-medium">Email:</span> {selectedRequest.subject.email}</p>
596
- {selectedRequest.subject.phone && (
597
- <p className="text-sm"><span className="font-medium">Phone:</span> {selectedRequest.subject.phone}</p>
598
- )}
599
- <p className="text-sm"><span className="font-medium">Received:</span> {formatDate(selectedRequest.createdAt)}</p>
600
- </div>
601
- <div>
602
- <p className="text-sm">
603
- <span className="font-medium">Request Type:</span> {selectedRequest.type.charAt(0).toUpperCase() + selectedRequest.type.slice(1)}
604
- </p>
605
- <p className="text-sm">
606
- <span className="font-medium">Status:</span> {
607
- selectedRequest.status === 'inProgress' ? 'In Progress' :
608
- selectedRequest.status === 'awaitingVerification' ? 'Awaiting Verification' :
609
- selectedRequest.status.charAt(0).toUpperCase() + selectedRequest.status.slice(1)
610
- }
611
- </p>
612
- {selectedRequest.dueDate && (
613
- <p className="text-sm">
614
- <span className="font-medium">Due Date:</span> {formatDate(selectedRequest.dueDate)}
615
- </p>
616
- )}
617
- </div>
618
- </div>
619
-
620
- {selectedRequest.description && (
621
- <div className="mb-4">
622
- <p className="text-sm font-medium">Request Details:</p>
623
- <p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 p-2 rounded-md mt-1">
624
- {selectedRequest.description}
625
- </p>
626
- </div>
627
- )}
628
-
629
- {selectedRequest.additionalInfo && (
630
- <div>
631
- <p className="text-sm font-medium">Additional Information:</p>
632
- <p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 p-2 rounded-md mt-1">
633
- {typeof selectedRequest.additionalInfo === 'object' ?
634
- JSON.stringify(selectedRequest.additionalInfo, null, 2) :
635
- String(selectedRequest.additionalInfo || 'No additional information provided')}
636
- </p>
637
- </div>
638
- )}
639
- </div>
640
- )}
641
-
642
- {/* Request Management */}
643
- <div className="mb-6 grid grid-cols-1 md:grid-cols-2 gap-4">
644
- {/* Status Update */}
645
- <div>
646
- <h3 className="text-md font-medium mb-2">Update Status</h3>
647
- <div className="flex space-x-2">
648
- <select
649
- value={selectedRequest.status}
650
- onChange={e => handleUpdateStatus(e.target.value as DSRStatus)}
651
- className="flex-grow px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
652
- >
653
- {renderStatusUpdateOptions()}
654
- </select>
655
- <button
656
- onClick={() => handleUpdateStatus(selectedRequest.status)}
657
- className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName}`}
658
- >
659
- Update
660
- </button>
661
- </div>
662
- </div>
663
-
664
- {/* Assign Request */}
665
- {assignees.length > 0 && (
666
- <div>
667
- <h3 className="text-md font-medium mb-2">Assign Request</h3>
668
- <div className="flex space-x-2">
669
- <select
670
- value={assignee}
671
- onChange={e => setAssignee(e.target.value)}
672
- className="flex-grow px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
673
- >
674
- <option value="">Select Assignee</option>
675
- {assignees.map(name => (
676
- <option key={name} value={name}>{name}</option>
677
- ))}
678
- </select>
679
- <button
680
- onClick={handleAssignRequest}
681
- disabled={!assignee}
682
- className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-300 disabled:text-gray-500 ${buttonClassName}`}
683
- >
684
- Assign
685
- </button>
686
- </div>
687
- </div>
688
- )}
689
- </div>
690
-
691
- {/* Request Summary */}
692
- <div className="mb-6">
693
- <h3 className="text-lg font-medium mb-3">Request Summary</h3>
694
- <div className="bg-gray-50 dark:bg-gray-700 p-3 rounded-md">
695
- <pre className="whitespace-pre-wrap text-sm font-mono text-gray-800 dark:text-gray-200">
696
- <pre>
697
- {JSON.stringify(formatDSRRequest(selectedRequest), null, 2)}
698
- </pre>
699
- </pre>
700
- </div>
701
- </div>
702
-
703
- {/* Request Timeline */}
704
- {showRequestTimeline && renderRequestTimeline(selectedRequest)}
705
- </div>
706
- ) : (
707
- <div className="flex items-center justify-center h-64 bg-gray-50 dark:bg-gray-700 rounded-md">
708
- <p className="text-gray-500 dark:text-gray-400">
709
- Select a request to view details
710
- </p>
711
- </div>
712
- )}
713
- </div>
714
- </div>
715
- </div>
716
- );
717
- };