@striae-org/striae 4.0.3 → 4.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.
- package/.env.example +8 -0
- package/app/components/actions/case-export/core-export.ts +14 -8
- package/app/components/actions/case-export/data-processing.ts +1 -0
- package/app/components/actions/case-export/download-handlers.ts +7 -0
- package/app/components/actions/case-export/metadata-helpers.ts +2 -1
- package/app/components/actions/case-import/confirmation-import.ts +12 -2
- package/app/components/actions/case-import/orchestrator.ts +78 -32
- package/app/components/actions/case-import/storage-operations.ts +97 -8
- package/app/components/actions/case-import/zip-processing.ts +159 -86
- package/app/components/actions/case-manage.ts +430 -8
- package/app/components/actions/confirm-export.ts +13 -4
- package/app/components/actions/generate-pdf.ts +10 -2
- package/app/components/actions/image-manage.ts +77 -44
- package/app/components/audit/user-audit-viewer.tsx +137 -945
- package/app/components/audit/user-audit.module.css +41 -0
- package/app/components/audit/viewer/audit-activity-summary.tsx +52 -0
- package/app/components/audit/viewer/audit-entries-list.tsx +207 -0
- package/app/components/audit/viewer/audit-filters-panel.tsx +307 -0
- package/app/components/audit/viewer/audit-user-info-card.tsx +44 -0
- package/app/components/audit/viewer/audit-viewer-header.tsx +55 -0
- package/app/components/audit/viewer/audit-viewer-utils.ts +123 -0
- package/app/components/audit/viewer/types.ts +1 -0
- package/app/components/audit/viewer/use-audit-viewer-data.ts +186 -0
- package/app/components/audit/viewer/use-audit-viewer-export.ts +176 -0
- package/app/components/audit/viewer/use-audit-viewer-filters.ts +141 -0
- package/app/components/auth/mfa-enrollment.module.css +13 -5
- package/app/components/auth/mfa-verification.module.css +13 -5
- package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
- package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
- package/app/components/canvas/canvas.module.css +64 -54
- package/app/components/canvas/canvas.tsx +17 -16
- package/app/components/canvas/confirmation/confirmation.module.css +1 -0
- package/app/components/canvas/confirmation/confirmation.tsx +17 -47
- package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
- package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
- package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
- package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
- package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
- package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
- package/app/components/navbar/navbar.module.css +447 -0
- package/app/components/navbar/navbar.tsx +377 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +2 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +21 -51
- package/app/components/sidebar/case-export/case-export.module.css +1 -0
- package/app/components/sidebar/case-export/case-export.tsx +14 -77
- package/app/components/sidebar/case-import/case-import.module.css +25 -0
- package/app/components/sidebar/case-import/case-import.tsx +64 -40
- package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
- package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
- package/app/components/sidebar/cases/case-sidebar.tsx +25 -519
- package/app/components/sidebar/cases/cases-modal.module.css +45 -9
- package/app/components/sidebar/cases/cases-modal.tsx +16 -16
- package/app/components/sidebar/cases/cases.module.css +62 -21
- package/app/components/sidebar/files/files-modal.module.css +46 -10
- package/app/components/sidebar/files/files-modal.tsx +22 -23
- package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
- package/app/components/sidebar/notes/notes-editor-modal.tsx +66 -0
- package/app/components/sidebar/notes/notes-modal.tsx +18 -17
- package/app/components/sidebar/notes/notes-sidebar.tsx +199 -113
- package/app/components/sidebar/notes/notes.module.css +155 -0
- package/app/components/sidebar/sidebar-container.tsx +15 -28
- package/app/components/sidebar/sidebar.module.css +7 -71
- package/app/components/sidebar/sidebar.tsx +24 -125
- package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
- package/app/components/toast/toast.module.css +2 -1
- package/app/components/toast/toast.tsx +16 -11
- package/app/components/user/delete-account.tsx +10 -31
- package/app/components/user/inactivity-warning.module.css +9 -6
- package/app/components/user/inactivity-warning.tsx +15 -2
- package/app/components/user/manage-profile.module.css +2 -0
- package/app/components/user/manage-profile.tsx +108 -40
- package/app/hooks/useOverlayDismiss.ts +116 -0
- package/app/routes/auth/login.example.tsx +19 -8
- package/app/routes/auth/login.tsx +785 -774
- package/app/routes/auth/passwordReset.module.css +23 -13
- package/app/routes/striae/striae.module.css +10 -3
- package/app/routes/striae/striae.tsx +477 -31
- package/app/routes.ts +7 -0
- package/app/services/audit/audit-export-csv.ts +2 -0
- package/app/services/audit/audit.service.ts +202 -32
- package/app/services/audit/builders/audit-entry-builder.ts +2 -1
- package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
- package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
- package/app/services/audit/builders/audit-event-builders-workflow.ts +8 -0
- package/app/services/audit/builders/index.ts +1 -0
- package/app/types/audit.ts +5 -2
- package/app/types/case.ts +29 -0
- package/app/types/import.ts +3 -0
- package/app/types/user.ts +1 -0
- package/app/utils/data/permissions.ts +17 -1
- package/app/utils/forensics/audit-export-signature.ts +5 -1
- package/app/utils/forensics/confirmation-signature.ts +3 -0
- package/app/utils/forensics/export-verification.ts +497 -22
- package/functions/api/pdf/[[path]].ts +32 -1
- package/load-context.ts +9 -0
- package/package.json +6 -2
- package/primershear.emails.example +6 -0
- package/scripts/deploy-pages-secrets.sh +6 -0
- package/scripts/deploy-primershear-emails.sh +167 -0
- package/worker-configuration.d.ts +7493 -7491
- package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/worker-configuration.d.ts +7448 -11323
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/worker-configuration.d.ts +7447 -11322
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/worker-configuration.d.ts +7447 -11322
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/src/formats/format-striae.ts +8 -7
- package/workers/pdf-worker/src/pdf-worker.example.ts +3 -0
- package/workers/pdf-worker/src/report-types.ts +3 -0
- package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/src/user-worker.example.ts +6 -1
- package/workers/user-worker/worker-configuration.d.ts +7448 -11323
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/public/.well-known/keybase.txt +0 -56
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useContext } from 'react';
|
|
2
2
|
import styles from './case-export.module.css';
|
|
3
3
|
import { AuthContext } from '~/contexts/auth.context';
|
|
4
|
-
import {
|
|
5
|
-
import { getCurrentPublicSigningKeyDetails } from '~/utils/forensics';
|
|
4
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
6
5
|
import { getCaseConfirmations, exportConfirmationData } from '../../actions/confirm-export';
|
|
7
6
|
|
|
8
7
|
export type ExportFormat = 'json' | 'csv';
|
|
@@ -34,8 +33,14 @@ export const CaseExport = ({
|
|
|
34
33
|
const [selectedFormat, setSelectedFormat] = useState<ExportFormat>('json');
|
|
35
34
|
const [includeImages, setIncludeImages] = useState(false);
|
|
36
35
|
const [hasConfirmationData, setHasConfirmationData] = useState(false);
|
|
37
|
-
const
|
|
38
|
-
|
|
36
|
+
const {
|
|
37
|
+
requestClose,
|
|
38
|
+
overlayProps,
|
|
39
|
+
getCloseButtonProps
|
|
40
|
+
} = useOverlayDismiss({
|
|
41
|
+
isOpen,
|
|
42
|
+
onClose,
|
|
43
|
+
});
|
|
39
44
|
|
|
40
45
|
// Update caseNumber when currentCaseNumber prop changes
|
|
41
46
|
useEffect(() => {
|
|
@@ -94,29 +99,6 @@ export const CaseExport = ({
|
|
|
94
99
|
}
|
|
95
100
|
}, [isReadOnly]);
|
|
96
101
|
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (!isOpen) {
|
|
99
|
-
setIsPublicKeyModalOpen(false);
|
|
100
|
-
}
|
|
101
|
-
}, [isOpen]);
|
|
102
|
-
|
|
103
|
-
// Handle Escape key to close modal
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
106
|
-
if (event.key === 'Escape' && isOpen && !isPublicKeyModalOpen) {
|
|
107
|
-
onClose();
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
if (isOpen) {
|
|
112
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return () => {
|
|
116
|
-
document.removeEventListener('keydown', handleEscapeKey);
|
|
117
|
-
};
|
|
118
|
-
}, [isOpen, isPublicKeyModalOpen, onClose]);
|
|
119
|
-
|
|
120
102
|
if (!isOpen) return null;
|
|
121
103
|
|
|
122
104
|
const handleExport = async () => {
|
|
@@ -133,7 +115,7 @@ export const CaseExport = ({
|
|
|
133
115
|
await onExport(caseNumber.trim(), selectedFormat, includeImages, (progress, label) => {
|
|
134
116
|
setExportProgress({ current: progress, total: 100, caseName: label, mode: 'single' });
|
|
135
117
|
});
|
|
136
|
-
|
|
118
|
+
requestClose();
|
|
137
119
|
} catch (error) {
|
|
138
120
|
console.error('Export failed:', error);
|
|
139
121
|
setError(error instanceof Error ? error.message : 'Export failed. Please try again.');
|
|
@@ -152,7 +134,7 @@ export const CaseExport = ({
|
|
|
152
134
|
await onExportAll((current: number, total: number, caseName: string) => {
|
|
153
135
|
setExportProgress({ current, total, caseName });
|
|
154
136
|
}, selectedFormat);
|
|
155
|
-
|
|
137
|
+
requestClose();
|
|
156
138
|
} catch (error) {
|
|
157
139
|
console.error('Export all failed:', error);
|
|
158
140
|
setError(error instanceof Error ? error.message : 'Export all cases failed. Please try again.');
|
|
@@ -173,7 +155,7 @@ export const CaseExport = ({
|
|
|
173
155
|
|
|
174
156
|
try {
|
|
175
157
|
await exportConfirmationData(user, caseNumber.trim());
|
|
176
|
-
|
|
158
|
+
requestClose();
|
|
177
159
|
} catch (error) {
|
|
178
160
|
console.error('Confirmation export failed:', error);
|
|
179
161
|
setError(error instanceof Error ? error.message : 'Confirmation export failed. Please try again.');
|
|
@@ -182,40 +164,16 @@ export const CaseExport = ({
|
|
|
182
164
|
}
|
|
183
165
|
};
|
|
184
166
|
|
|
185
|
-
const handleOverlayMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
186
|
-
if (e.target === e.currentTarget) {
|
|
187
|
-
onClose();
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const handleOverlayKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
192
|
-
if (e.target !== e.currentTarget) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
197
|
-
e.preventDefault();
|
|
198
|
-
onClose();
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
167
|
return (
|
|
203
168
|
<div
|
|
204
169
|
className={styles.overlay}
|
|
205
|
-
onMouseDown={handleOverlayMouseDown}
|
|
206
|
-
onKeyDown={handleOverlayKeyDown}
|
|
207
|
-
role="button"
|
|
208
|
-
tabIndex={0}
|
|
209
170
|
aria-label="Close case export dialog"
|
|
171
|
+
{...overlayProps}
|
|
210
172
|
>
|
|
211
173
|
<div className={styles.modal}>
|
|
212
174
|
<div className={styles.header}>
|
|
213
175
|
<h2 className={styles.title}>Export Case Data</h2>
|
|
214
|
-
<button
|
|
215
|
-
className={styles.closeButton}
|
|
216
|
-
onClick={onClose}
|
|
217
|
-
aria-label="Close modal"
|
|
218
|
-
>
|
|
176
|
+
<button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close case export dialog' })}>
|
|
219
177
|
×
|
|
220
178
|
</button>
|
|
221
179
|
</div>
|
|
@@ -338,20 +296,6 @@ export const CaseExport = ({
|
|
|
338
296
|
</div>
|
|
339
297
|
</div>
|
|
340
298
|
)}
|
|
341
|
-
|
|
342
|
-
<div className={styles.divider}>
|
|
343
|
-
<span>Verification</span>
|
|
344
|
-
</div>
|
|
345
|
-
|
|
346
|
-
<div className={styles.publicKeySection}>
|
|
347
|
-
<button
|
|
348
|
-
type="button"
|
|
349
|
-
className={styles.publicKeyButton}
|
|
350
|
-
onClick={() => setIsPublicKeyModalOpen(true)}
|
|
351
|
-
>
|
|
352
|
-
View Public Signing Key
|
|
353
|
-
</button>
|
|
354
|
-
</div>
|
|
355
299
|
|
|
356
300
|
{error && (
|
|
357
301
|
<div className={styles.error}>
|
|
@@ -361,13 +305,6 @@ export const CaseExport = ({
|
|
|
361
305
|
</div>
|
|
362
306
|
</div>
|
|
363
307
|
</div>
|
|
364
|
-
|
|
365
|
-
<PublicSigningKeyModal
|
|
366
|
-
isOpen={isPublicKeyModalOpen}
|
|
367
|
-
onClose={() => setIsPublicKeyModalOpen(false)}
|
|
368
|
-
publicSigningKeyId={publicSigningKeyId}
|
|
369
|
-
publicKeyPem={publicKeyPem}
|
|
370
|
-
/>
|
|
371
308
|
</div>
|
|
372
309
|
);
|
|
373
310
|
};
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.modal {
|
|
14
|
+
position: relative;
|
|
14
15
|
background: var(--backgroundLight);
|
|
15
16
|
border-radius: var(--spaceXS);
|
|
16
17
|
width: 90%;
|
|
@@ -424,6 +425,28 @@
|
|
|
424
425
|
color: var(--textTitle);
|
|
425
426
|
}
|
|
426
427
|
|
|
428
|
+
.archivedImportNote {
|
|
429
|
+
margin-bottom: var(--spaceM);
|
|
430
|
+
padding: var(--spaceS) var(--spaceM);
|
|
431
|
+
border-radius: var(--spaceXS);
|
|
432
|
+
background: color-mix(in lab, var(--success) 10%, transparent);
|
|
433
|
+
border: 1px solid color-mix(in lab, var(--success) 25%, transparent);
|
|
434
|
+
color: color-mix(in lab, var(--success) 80%, var(--black));
|
|
435
|
+
font-size: var(--fontSizeBodyXS);
|
|
436
|
+
font-weight: var(--fontWeightMedium);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.archivedRegularCaseRiskNote {
|
|
440
|
+
margin-bottom: var(--spaceM);
|
|
441
|
+
padding: var(--spaceS) var(--spaceM);
|
|
442
|
+
border-radius: var(--spaceXS);
|
|
443
|
+
background: color-mix(in lab, var(--warning) 12%, transparent);
|
|
444
|
+
border: 1px solid color-mix(in lab, var(--warning) 30%, transparent);
|
|
445
|
+
color: color-mix(in lab, var(--warning) 85%, var(--black));
|
|
446
|
+
font-size: var(--fontSizeBodyXS);
|
|
447
|
+
font-weight: var(--fontWeightMedium);
|
|
448
|
+
}
|
|
449
|
+
|
|
427
450
|
/* Validation Section - Green/Red Based on Status */
|
|
428
451
|
.validationSection {
|
|
429
452
|
border-radius: var(--spaceXS);
|
|
@@ -521,6 +544,7 @@
|
|
|
521
544
|
justify-content: center;
|
|
522
545
|
align-items: center;
|
|
523
546
|
z-index: 9999;
|
|
547
|
+
cursor: default;
|
|
524
548
|
}
|
|
525
549
|
|
|
526
550
|
.confirmationModal {
|
|
@@ -530,6 +554,7 @@
|
|
|
530
554
|
max-width: 400px;
|
|
531
555
|
box-shadow: 0 var(--spaceM) var(--spaceXL)
|
|
532
556
|
color-mix(in lab, var(--black) 20%, transparent);
|
|
557
|
+
cursor: default;
|
|
533
558
|
}
|
|
534
559
|
|
|
535
560
|
.confirmationContent {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
|
2
2
|
import { AuthContext } from '~/contexts/auth.context';
|
|
3
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
4
|
import {
|
|
4
5
|
listReadOnlyCases,
|
|
5
6
|
deleteReadOnlyCase
|
|
6
7
|
} from '~/components/actions/case-review';
|
|
8
|
+
import { listCases } from '~/components/actions/case-manage';
|
|
7
9
|
import {
|
|
8
10
|
type ImportResult,
|
|
9
11
|
type ConfirmationImportResult
|
|
@@ -49,13 +51,25 @@ export const CaseImport = ({
|
|
|
49
51
|
resetImportState,
|
|
50
52
|
setImportProgress
|
|
51
53
|
} = useImportState();
|
|
54
|
+
const canDismissOverlay = !importState.isImporting && !importState.isClearing;
|
|
55
|
+
const {
|
|
56
|
+
requestClose,
|
|
57
|
+
overlayProps,
|
|
58
|
+
getCloseButtonProps
|
|
59
|
+
} = useOverlayDismiss({
|
|
60
|
+
isOpen,
|
|
61
|
+
onClose,
|
|
62
|
+
canDismiss: canDismissOverlay
|
|
63
|
+
});
|
|
52
64
|
|
|
53
65
|
const [existingReadOnlyCase, setExistingReadOnlyCase] = useState<string | null>(null);
|
|
66
|
+
const [showArchivedRegularCaseRiskWarning, setShowArchivedRegularCaseRiskWarning] = useState(false);
|
|
54
67
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
55
68
|
|
|
56
69
|
// Clear import selection state (used by preview hook on validation failure)
|
|
57
70
|
const clearImportSelection = useCallback(() => {
|
|
58
71
|
updateImportState({ selectedFile: null, importType: null });
|
|
72
|
+
setShowArchivedRegularCaseRiskWarning(false);
|
|
59
73
|
resetFileInput(fileInputRef);
|
|
60
74
|
}, [updateImportState]);
|
|
61
75
|
|
|
@@ -113,10 +127,21 @@ export const CaseImport = ({
|
|
|
113
127
|
updateImportState({ isClearing: true });
|
|
114
128
|
|
|
115
129
|
try {
|
|
116
|
-
await deleteReadOnlyCase(user, existingReadOnlyCase);
|
|
117
|
-
|
|
118
130
|
const clearedCaseName = existingReadOnlyCase;
|
|
119
|
-
|
|
131
|
+
const deleteSuccess = await deleteReadOnlyCase(user, clearedCaseName);
|
|
132
|
+
const remainingReadOnlyCases = await listReadOnlyCases(user);
|
|
133
|
+
const stillExists = remainingReadOnlyCases.some((caseMeta) => caseMeta.caseNumber === clearedCaseName);
|
|
134
|
+
|
|
135
|
+
setExistingReadOnlyCase(remainingReadOnlyCases.length > 0 ? remainingReadOnlyCases[0].caseNumber : null);
|
|
136
|
+
|
|
137
|
+
if (!deleteSuccess || stillExists) {
|
|
138
|
+
setError(
|
|
139
|
+
`Failed to fully clear read-only case "${clearedCaseName}". ` +
|
|
140
|
+
'Please try again. If this was an archived import that overlaps a regular case, verify that all case images are accessible before retrying.'
|
|
141
|
+
);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
120
145
|
setSuccess(`Removed read-only case "${clearedCaseName}"`);
|
|
121
146
|
|
|
122
147
|
onImportComplete?.({
|
|
@@ -226,25 +251,8 @@ export const CaseImport = ({
|
|
|
226
251
|
|
|
227
252
|
const handleModalCancel = useCallback(() => {
|
|
228
253
|
clearImportData();
|
|
229
|
-
|
|
230
|
-
}, [clearImportData,
|
|
231
|
-
|
|
232
|
-
const handleOverlayMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
|
233
|
-
if (e.target === e.currentTarget && !importState.isImporting && !importState.isClearing) {
|
|
234
|
-
onClose();
|
|
235
|
-
}
|
|
236
|
-
}, [importState.isImporting, importState.isClearing, onClose]);
|
|
237
|
-
|
|
238
|
-
const handleOverlayKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
239
|
-
if (e.target !== e.currentTarget || importState.isImporting || importState.isClearing) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
244
|
-
e.preventDefault();
|
|
245
|
-
onClose();
|
|
246
|
-
}
|
|
247
|
-
}, [importState.isImporting, importState.isClearing, onClose]);
|
|
254
|
+
requestClose();
|
|
255
|
+
}, [clearImportData, requestClose]);
|
|
248
256
|
|
|
249
257
|
// Effects
|
|
250
258
|
useEffect(() => {
|
|
@@ -253,22 +261,41 @@ export const CaseImport = ({
|
|
|
253
261
|
}
|
|
254
262
|
}, [user, isOpen, checkForExistingReadOnlyCase]);
|
|
255
263
|
|
|
256
|
-
// Handle keyboard events
|
|
257
264
|
useEffect(() => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
265
|
+
let isMounted = true;
|
|
266
|
+
|
|
267
|
+
const checkArchivedRegularCaseRisk = async () => {
|
|
268
|
+
if (
|
|
269
|
+
!user ||
|
|
270
|
+
!isOpen ||
|
|
271
|
+
importState.importType !== 'case' ||
|
|
272
|
+
!casePreview?.archived ||
|
|
273
|
+
!casePreview.caseNumber
|
|
274
|
+
) {
|
|
275
|
+
if (isMounted) {
|
|
276
|
+
setShowArchivedRegularCaseRiskWarning(false);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const regularCases = await listCases(user);
|
|
283
|
+
if (isMounted) {
|
|
284
|
+
setShowArchivedRegularCaseRiskWarning(regularCases.includes(casePreview.caseNumber));
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
if (isMounted) {
|
|
288
|
+
setShowArchivedRegularCaseRiskWarning(false);
|
|
289
|
+
}
|
|
261
290
|
}
|
|
262
291
|
};
|
|
263
292
|
|
|
264
|
-
|
|
265
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
266
|
-
}
|
|
293
|
+
void checkArchivedRegularCaseRisk();
|
|
267
294
|
|
|
268
295
|
return () => {
|
|
269
|
-
|
|
296
|
+
isMounted = false;
|
|
270
297
|
};
|
|
271
|
-
}, [
|
|
298
|
+
}, [user, isOpen, importState.importType, casePreview?.archived, casePreview?.caseNumber]);
|
|
272
299
|
|
|
273
300
|
// Reset state when modal closes
|
|
274
301
|
useEffect(() => {
|
|
@@ -289,20 +316,15 @@ export const CaseImport = ({
|
|
|
289
316
|
<>
|
|
290
317
|
<div
|
|
291
318
|
className={styles.overlay}
|
|
292
|
-
onMouseDown={handleOverlayMouseDown}
|
|
293
|
-
onKeyDown={handleOverlayKeyDown}
|
|
294
|
-
role="button"
|
|
295
|
-
tabIndex={0}
|
|
296
319
|
aria-label="Close case import dialog"
|
|
320
|
+
{...overlayProps}
|
|
297
321
|
>
|
|
298
322
|
<div className={styles.modal}>
|
|
299
323
|
<div className={styles.header}>
|
|
300
|
-
<h2 className={styles.title}>Import
|
|
301
|
-
<button
|
|
324
|
+
<h2 className={styles.title}>Import Case or Confirmations</h2>
|
|
325
|
+
<button
|
|
302
326
|
className={styles.closeButton}
|
|
303
|
-
|
|
304
|
-
aria-label="Close modal"
|
|
305
|
-
disabled={importState.isImporting || importState.isClearing}
|
|
327
|
+
{...getCloseButtonProps({ ariaLabel: 'Close case import dialog' })}
|
|
306
328
|
>
|
|
307
329
|
×
|
|
308
330
|
</button>
|
|
@@ -340,6 +362,7 @@ export const CaseImport = ({
|
|
|
340
362
|
<CasePreviewSection
|
|
341
363
|
casePreview={casePreview}
|
|
342
364
|
isLoadingPreview={importState.isLoadingPreview}
|
|
365
|
+
showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
|
|
343
366
|
/>
|
|
344
367
|
)}
|
|
345
368
|
|
|
@@ -431,6 +454,7 @@ export const CaseImport = ({
|
|
|
431
454
|
<ConfirmationDialog
|
|
432
455
|
showConfirmation={importState.showConfirmation}
|
|
433
456
|
casePreview={casePreview}
|
|
457
|
+
showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
|
|
434
458
|
onConfirm={handleConfirmImport}
|
|
435
459
|
onCancel={handleCancelImport}
|
|
436
460
|
/>
|
|
@@ -4,9 +4,14 @@ import styles from '../case-import.module.css';
|
|
|
4
4
|
interface CasePreviewSectionProps {
|
|
5
5
|
casePreview: CaseImportPreview | null;
|
|
6
6
|
isLoadingPreview: boolean;
|
|
7
|
+
showArchivedRegularCaseRiskWarning?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export const CasePreviewSection = ({
|
|
10
|
+
export const CasePreviewSection = ({
|
|
11
|
+
casePreview,
|
|
12
|
+
isLoadingPreview,
|
|
13
|
+
showArchivedRegularCaseRiskWarning = false
|
|
14
|
+
}: CasePreviewSectionProps) => {
|
|
10
15
|
if (isLoadingPreview) {
|
|
11
16
|
return (
|
|
12
17
|
<div className={styles.previewSection}>
|
|
@@ -24,6 +29,16 @@ export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePrevie
|
|
|
24
29
|
{/* Case Information - Always Blue */}
|
|
25
30
|
<div className={styles.previewSection}>
|
|
26
31
|
<h3 className={styles.previewTitle}>Case Information</h3>
|
|
32
|
+
{casePreview.archived && (
|
|
33
|
+
<div className={styles.archivedImportNote}>
|
|
34
|
+
Archived export detected. Original exporter imports are allowed for archived cases.
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
{showArchivedRegularCaseRiskWarning && (
|
|
38
|
+
<div className={styles.archivedRegularCaseRiskNote}>
|
|
39
|
+
Warning: This archived import matches a case already in your regular case list. If you later clear the imported read-only case, the regular case images will be deleted and become inaccessible.
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
27
42
|
<div className={styles.previewGrid}>
|
|
28
43
|
<div className={styles.previewItem}>
|
|
29
44
|
<span className={styles.previewLabel}>Case Number:</span>
|
|
@@ -49,6 +64,10 @@ export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePrevie
|
|
|
49
64
|
<span className={styles.previewLabel}>Total Images:</span>
|
|
50
65
|
<span className={styles.previewValue}>{casePreview.totalFiles}</span>
|
|
51
66
|
</div>
|
|
67
|
+
<div className={styles.previewItem}>
|
|
68
|
+
<span className={styles.previewLabel}>Archived Export:</span>
|
|
69
|
+
<span className={styles.previewValue}>{casePreview.archived ? 'Yes' : 'No'}</span>
|
|
70
|
+
</div>
|
|
52
71
|
</div>
|
|
53
72
|
</div>
|
|
54
73
|
|
|
@@ -4,6 +4,7 @@ import styles from '../case-import.module.css';
|
|
|
4
4
|
interface ConfirmationDialogProps {
|
|
5
5
|
showConfirmation: boolean;
|
|
6
6
|
casePreview: CaseImportPreview | null;
|
|
7
|
+
showArchivedRegularCaseRiskWarning?: boolean;
|
|
7
8
|
onConfirm: () => void;
|
|
8
9
|
onCancel: () => void;
|
|
9
10
|
}
|
|
@@ -11,6 +12,7 @@ interface ConfirmationDialogProps {
|
|
|
11
12
|
export const ConfirmationDialog = ({
|
|
12
13
|
showConfirmation,
|
|
13
14
|
casePreview,
|
|
15
|
+
showArchivedRegularCaseRiskWarning = false,
|
|
14
16
|
onConfirm,
|
|
15
17
|
onCancel
|
|
16
18
|
}: ConfirmationDialogProps) => {
|
|
@@ -41,6 +43,19 @@ export const ConfirmationDialog = ({
|
|
|
41
43
|
<div className={styles.confirmationItem}>
|
|
42
44
|
<strong>Total Images:</strong> {casePreview.totalFiles}
|
|
43
45
|
</div>
|
|
46
|
+
<div className={styles.confirmationItem}>
|
|
47
|
+
<strong>Archived Export:</strong> {casePreview.archived ? 'Yes' : 'No'}
|
|
48
|
+
</div>
|
|
49
|
+
{casePreview.archived && (
|
|
50
|
+
<div className={styles.archivedImportNote}>
|
|
51
|
+
Archived export detected. Original exporter imports are allowed for archived cases.
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
{showArchivedRegularCaseRiskWarning && (
|
|
55
|
+
<div className={styles.archivedRegularCaseRiskNote}>
|
|
56
|
+
Warning: This archived import matches a case in your regular case list. If you clear the imported read-only case later, the regular case images will be deleted and inaccessible.
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
44
59
|
{casePreview.hashValid !== undefined && (
|
|
45
60
|
<div className={`${styles.confirmationItem} ${casePreview.hashValid ? styles.confirmationItemValid : styles.confirmationItemInvalid}`}>
|
|
46
61
|
<strong>Data Integrity:</strong>
|