@striae-org/striae 4.2.0 → 4.3.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/LICENSE +1 -1
- package/app/components/actions/case-manage.ts +50 -17
- package/app/components/audit/viewer/audit-entries-list.tsx +5 -2
- package/app/components/audit/viewer/use-audit-viewer-data.ts +6 -3
- package/app/components/audit/viewer/use-audit-viewer-export.ts +1 -1
- package/app/components/canvas/confirmation/confirmation.tsx +6 -2
- package/app/components/colors/colors.module.css +4 -3
- package/app/components/navbar/case-modals/archive-case-modal.module.css +0 -76
- package/app/components/navbar/case-modals/archive-case-modal.tsx +9 -8
- package/app/components/navbar/case-modals/case-modal-shared.module.css +94 -0
- package/app/components/navbar/case-modals/delete-case-modal.module.css +9 -0
- package/app/components/navbar/case-modals/delete-case-modal.tsx +79 -0
- package/app/components/navbar/case-modals/open-case-modal.module.css +2 -1
- package/app/components/navbar/case-modals/rename-case-modal.module.css +0 -72
- package/app/components/navbar/case-modals/rename-case-modal.tsx +9 -8
- package/app/components/navbar/navbar.tsx +34 -9
- package/app/components/sidebar/cases/case-sidebar.tsx +93 -73
- package/app/components/sidebar/cases/cases-modal.module.css +312 -10
- package/app/components/sidebar/cases/cases-modal.tsx +737 -116
- package/app/components/sidebar/cases/cases.module.css +43 -0
- package/app/components/sidebar/files/delete-files-modal.module.css +26 -0
- package/app/components/sidebar/files/delete-files-modal.tsx +94 -0
- package/app/components/sidebar/files/files-modal.module.css +285 -44
- package/app/components/sidebar/files/files-modal.tsx +482 -177
- package/app/components/sidebar/notes/addl-notes-modal.tsx +82 -0
- package/app/components/sidebar/notes/class-details-fields.tsx +146 -0
- package/app/components/sidebar/notes/class-details-modal.tsx +147 -0
- package/app/components/sidebar/notes/class-details-sections.tsx +561 -0
- package/app/components/sidebar/notes/class-details-shared.ts +239 -0
- package/app/components/sidebar/notes/{notes-sidebar.tsx → notes-editor-form.tsx} +77 -76
- package/app/components/sidebar/notes/notes-editor-modal.tsx +5 -7
- package/app/components/sidebar/notes/notes.module.css +262 -14
- package/app/components/sidebar/notes/use-class-details-state.ts +371 -0
- package/app/components/sidebar/sidebar-container.tsx +2 -0
- package/app/components/sidebar/sidebar.tsx +15 -1
- package/app/{tailwind.css → global.css} +1 -3
- package/app/hooks/useCaseListPreferences.ts +99 -0
- package/app/hooks/useFileListPreferences.ts +106 -0
- package/app/hooks/useOverlayDismiss.ts +6 -4
- package/app/root.tsx +1 -1
- package/app/routes/striae/striae.tsx +7 -0
- package/app/services/audit/audit.service.ts +2 -2
- package/app/services/audit/builders/audit-event-builders-case-file.ts +1 -1
- package/app/types/annotations.ts +48 -1
- package/app/types/audit.ts +1 -0
- package/app/utils/data/case-filters.ts +127 -0
- package/app/utils/data/confirmation-summary/summary-core.ts +295 -0
- package/app/utils/data/data-operations.ts +17 -861
- package/app/utils/data/file-filters.ts +201 -0
- package/app/utils/data/index.ts +11 -1
- package/app/utils/data/operations/batch-operations.ts +113 -0
- package/app/utils/data/operations/case-operations.ts +168 -0
- package/app/utils/data/operations/confirmation-summary-operations.ts +301 -0
- package/app/utils/data/operations/file-annotation-operations.ts +196 -0
- package/app/utils/data/operations/index.ts +7 -0
- package/app/utils/data/operations/signing-operations.ts +225 -0
- package/app/utils/data/operations/types.ts +42 -0
- package/app/utils/data/operations/validation-operations.ts +48 -0
- package/app/utils/forensics/export-verification.ts +40 -111
- package/functions/api/_shared/firebase-auth.ts +2 -7
- package/functions/api/image/[[path]].ts +23 -22
- package/functions/api/pdf/[[path]].ts +27 -8
- package/package.json +7 -13
- package/scripts/deploy-primershear-emails.sh +1 -1
- package/worker-configuration.d.ts +2 -2
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/image-worker.example.ts +16 -5
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/package.json +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/src/formats/format-striae.ts +84 -124
- package/workers/pdf-worker/src/pdf-worker.example.ts +58 -61
- package/workers/pdf-worker/src/report-layout.ts +227 -0
- package/workers/pdf-worker/src/report-types.ts +23 -3
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +1 -1
- package/workers/user-worker/src/user-worker.example.ts +17 -0
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/NOTICE +0 -13
- package/app/components/sidebar/notes/notes-modal.tsx +0 -52
- package/postcss.config.js +0 -6
- package/tailwind.config.ts +0 -22
- package/workers/pdf-worker/src/assets/icon-256.png +0 -0
- /package/workers/pdf-worker/src/assets/{generated-assets.ts → generated-assets.example.ts} +0 -0
|
@@ -114,6 +114,7 @@ export const Navbar = ({
|
|
|
114
114
|
}, [isCaseMenuOpen, isFileMenuOpen]);
|
|
115
115
|
|
|
116
116
|
const caseActionsDisabled = false;
|
|
117
|
+
const disableLongRunningCaseActions = isUploading;
|
|
117
118
|
const isCaseManagementActive = true;
|
|
118
119
|
const isFileManagementActive = isFileMenuOpen || hasLoadedImage;
|
|
119
120
|
const canOpenImageNotes = hasLoadedImage && !isCurrentImageConfirmed;
|
|
@@ -139,7 +140,7 @@ export const Navbar = ({
|
|
|
139
140
|
aria-haspopup="menu"
|
|
140
141
|
disabled={caseActionsDisabled}
|
|
141
142
|
onClick={() => setIsCaseMenuOpen((prev) => !prev)}
|
|
142
|
-
title={isUploading ? '
|
|
143
|
+
title={isUploading ? 'Some case actions are unavailable while files are uploading' : undefined}
|
|
143
144
|
>
|
|
144
145
|
Case Management
|
|
145
146
|
</button>
|
|
@@ -173,8 +174,14 @@ export const Navbar = ({
|
|
|
173
174
|
type="button"
|
|
174
175
|
role="menuitem"
|
|
175
176
|
className={`${styles.caseMenuItem} ${styles.caseMenuItemExport}`}
|
|
176
|
-
disabled={!hasLoadedCase}
|
|
177
|
-
title={
|
|
177
|
+
disabled={!hasLoadedCase || disableLongRunningCaseActions}
|
|
178
|
+
title={
|
|
179
|
+
!hasLoadedCase
|
|
180
|
+
? 'Load a case to export case data'
|
|
181
|
+
: disableLongRunningCaseActions
|
|
182
|
+
? 'Export is unavailable while files are uploading'
|
|
183
|
+
: undefined
|
|
184
|
+
}
|
|
178
185
|
onClick={() => {
|
|
179
186
|
onOpenCaseExport?.();
|
|
180
187
|
setIsCaseMenuOpen(false);
|
|
@@ -203,8 +210,14 @@ export const Navbar = ({
|
|
|
203
210
|
type="button"
|
|
204
211
|
role="menuitem"
|
|
205
212
|
className={`${styles.caseMenuItem} ${styles.caseMenuItemRename}`}
|
|
206
|
-
disabled={!hasLoadedCase}
|
|
207
|
-
title={
|
|
213
|
+
disabled={!hasLoadedCase || disableLongRunningCaseActions}
|
|
214
|
+
title={
|
|
215
|
+
!hasLoadedCase
|
|
216
|
+
? 'Load a case to rename it'
|
|
217
|
+
: disableLongRunningCaseActions
|
|
218
|
+
? 'Rename is unavailable while files are uploading'
|
|
219
|
+
: undefined
|
|
220
|
+
}
|
|
208
221
|
onClick={() => {
|
|
209
222
|
onOpenRenameCase?.();
|
|
210
223
|
setIsCaseMenuOpen(false);
|
|
@@ -218,8 +231,14 @@ export const Navbar = ({
|
|
|
218
231
|
type="button"
|
|
219
232
|
role="menuitem"
|
|
220
233
|
className={`${styles.caseMenuItem} ${styles.caseMenuItemDelete}`}
|
|
221
|
-
disabled={!hasLoadedCase}
|
|
222
|
-
title={
|
|
234
|
+
disabled={!hasLoadedCase || disableLongRunningCaseActions}
|
|
235
|
+
title={
|
|
236
|
+
!hasLoadedCase
|
|
237
|
+
? 'Load a case to delete it'
|
|
238
|
+
: disableLongRunningCaseActions
|
|
239
|
+
? 'Delete is unavailable while files are uploading'
|
|
240
|
+
: undefined
|
|
241
|
+
}
|
|
223
242
|
onClick={() => {
|
|
224
243
|
onDeleteCase?.();
|
|
225
244
|
setIsCaseMenuOpen(false);
|
|
@@ -233,8 +252,14 @@ export const Navbar = ({
|
|
|
233
252
|
type="button"
|
|
234
253
|
role="menuitem"
|
|
235
254
|
className={`${styles.caseMenuItem} ${styles.caseMenuItemArchive}`}
|
|
236
|
-
disabled={!hasLoadedCase}
|
|
237
|
-
title={
|
|
255
|
+
disabled={!hasLoadedCase || disableLongRunningCaseActions}
|
|
256
|
+
title={
|
|
257
|
+
!hasLoadedCase
|
|
258
|
+
? 'Load a case to archive it'
|
|
259
|
+
: disableLongRunningCaseActions
|
|
260
|
+
? 'Archive is unavailable while files are uploading'
|
|
261
|
+
: undefined
|
|
262
|
+
}
|
|
238
263
|
onClick={() => {
|
|
239
264
|
onArchiveCase?.();
|
|
240
265
|
setIsCaseMenuOpen(false);
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import type { User } from 'firebase/auth';
|
|
2
|
+
import type React from 'react';
|
|
2
3
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
3
4
|
import styles from './cases.module.css';
|
|
4
5
|
import { FilesModal } from '../files/files-modal';
|
|
5
6
|
import { ImageUploadZone } from '../upload/image-upload-zone';
|
|
7
|
+
import { exportConfirmationData } from '../../actions/confirm-export';
|
|
6
8
|
import {
|
|
7
9
|
fetchFiles,
|
|
8
10
|
deleteFile,
|
|
9
11
|
} from '../../actions/image-manage';
|
|
10
12
|
import {
|
|
11
13
|
canUploadFile,
|
|
12
|
-
|
|
14
|
+
ensureCaseConfirmationSummary,
|
|
15
|
+
getCaseConfirmationSummary
|
|
13
16
|
} from '~/utils/data';
|
|
14
17
|
import { type FileData } from '~/types';
|
|
15
18
|
|
|
16
19
|
interface CaseSidebarProps {
|
|
17
20
|
user: User;
|
|
18
21
|
onImageSelect: (file: FileData) => void;
|
|
22
|
+
onOpenCase: () => void;
|
|
19
23
|
imageLoaded: boolean;
|
|
20
24
|
setImageLoaded: (loaded: boolean) => void;
|
|
21
25
|
onNotesClick: () => void;
|
|
@@ -23,17 +27,20 @@ interface CaseSidebarProps {
|
|
|
23
27
|
setFiles: React.Dispatch<React.SetStateAction<FileData[]>>;
|
|
24
28
|
currentCase: string | null;
|
|
25
29
|
isReadOnly?: boolean;
|
|
30
|
+
isArchivedCase?: boolean;
|
|
26
31
|
isConfirmed?: boolean;
|
|
27
32
|
confirmationSaveVersion?: number;
|
|
28
33
|
selectedFileId?: string;
|
|
29
34
|
isUploading?: boolean;
|
|
30
35
|
onUploadStatusChange?: (isUploading: boolean) => void;
|
|
31
36
|
onUploadComplete?: (result: { successCount: number; failedFiles: string[] }) => void;
|
|
37
|
+
onExportNotification?: (message: string, type: 'success' | 'error') => void;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export const CaseSidebar = ({
|
|
35
41
|
user,
|
|
36
42
|
onImageSelect,
|
|
43
|
+
onOpenCase,
|
|
37
44
|
imageLoaded,
|
|
38
45
|
setImageLoaded,
|
|
39
46
|
onNotesClick,
|
|
@@ -41,18 +48,21 @@ export const CaseSidebar = ({
|
|
|
41
48
|
setFiles,
|
|
42
49
|
currentCase,
|
|
43
50
|
isReadOnly = false,
|
|
51
|
+
isArchivedCase = false,
|
|
44
52
|
isConfirmed = false,
|
|
45
53
|
confirmationSaveVersion = 0,
|
|
46
54
|
selectedFileId,
|
|
47
55
|
isUploading = false,
|
|
48
56
|
onUploadStatusChange,
|
|
49
|
-
onUploadComplete
|
|
57
|
+
onUploadComplete,
|
|
58
|
+
onExportNotification
|
|
50
59
|
}: CaseSidebarProps) => {
|
|
51
60
|
|
|
52
61
|
const [, setFileError] = useState('');
|
|
53
62
|
const [canUploadNewFile, setCanUploadNewFile] = useState(true);
|
|
54
63
|
const [uploadFileError, setUploadFileError] = useState('');
|
|
55
64
|
const [isFilesModalOpen, setIsFilesModalOpen] = useState(false);
|
|
65
|
+
const [isExportingConfirmations, setIsExportingConfirmations] = useState(false);
|
|
56
66
|
const [deletingFileId, setDeletingFileId] = useState<string | null>(null);
|
|
57
67
|
const [fileConfirmationStatus, setFileConfirmationStatus] = useState<{
|
|
58
68
|
[fileId: string]: { includeConfirmation: boolean; isConfirmed: boolean }
|
|
@@ -67,21 +77,6 @@ export const CaseSidebar = ({
|
|
|
67
77
|
[files]
|
|
68
78
|
);
|
|
69
79
|
|
|
70
|
-
const calculateCaseConfirmationStatus = useCallback((
|
|
71
|
-
statuses: { [fileId: string]: { includeConfirmation: boolean; isConfirmed: boolean } }
|
|
72
|
-
) => {
|
|
73
|
-
const filesRequiringConfirmation = files
|
|
74
|
-
.map((file) => statuses[file.id] || { includeConfirmation: false, isConfirmed: false })
|
|
75
|
-
.filter((status) => status.includeConfirmation);
|
|
76
|
-
|
|
77
|
-
const allConfirmedFiles = filesRequiringConfirmation.every((status) => status.isConfirmed);
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
includeConfirmation: filesRequiringConfirmation.length > 0,
|
|
81
|
-
isConfirmed: filesRequiringConfirmation.length > 0 ? allConfirmedFiles : false,
|
|
82
|
-
};
|
|
83
|
-
}, [files]);
|
|
84
|
-
|
|
85
80
|
// Function to check file upload permissions (extracted for reuse)
|
|
86
81
|
const checkFileUploadPermissions = useCallback(async (fileCount?: number) => {
|
|
87
82
|
if (currentCase) {
|
|
@@ -135,43 +130,24 @@ export const CaseSidebar = ({
|
|
|
135
130
|
return;
|
|
136
131
|
}
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const annotations = await getFileAnnotations(user, currentCase, file.id);
|
|
142
|
-
return {
|
|
143
|
-
fileId: file.id,
|
|
144
|
-
includeConfirmation: annotations?.includeConfirmation ?? false,
|
|
145
|
-
isConfirmed: !!annotations?.confirmationData,
|
|
146
|
-
};
|
|
147
|
-
} catch (err) {
|
|
148
|
-
console.error(`Error fetching annotations for file ${file.id}:`, err);
|
|
149
|
-
return {
|
|
150
|
-
fileId: file.id,
|
|
151
|
-
includeConfirmation: false,
|
|
152
|
-
isConfirmed: false,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
133
|
+
const caseSummary = await ensureCaseConfirmationSummary(user, currentCase, files).catch((error) => {
|
|
134
|
+
console.error(`Error fetching confirmation summary for case ${currentCase}:`, error);
|
|
135
|
+
return null;
|
|
155
136
|
});
|
|
156
137
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Build the statuses map from results
|
|
161
|
-
const statuses: { [fileId: string]: { includeConfirmation: boolean; isConfirmed: boolean } } = {};
|
|
162
|
-
results.forEach((result) => {
|
|
163
|
-
statuses[result.fileId] = {
|
|
164
|
-
includeConfirmation: result.includeConfirmation,
|
|
165
|
-
isConfirmed: result.isConfirmed,
|
|
166
|
-
};
|
|
167
|
-
});
|
|
138
|
+
if (!caseSummary) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
168
141
|
|
|
169
142
|
if (isCancelled) {
|
|
170
143
|
return;
|
|
171
144
|
}
|
|
172
145
|
|
|
173
|
-
setFileConfirmationStatus(
|
|
174
|
-
setCaseConfirmationStatus(
|
|
146
|
+
setFileConfirmationStatus(caseSummary.filesById);
|
|
147
|
+
setCaseConfirmationStatus({
|
|
148
|
+
includeConfirmation: caseSummary.includeConfirmation,
|
|
149
|
+
isConfirmed: caseSummary.isConfirmed
|
|
150
|
+
});
|
|
175
151
|
};
|
|
176
152
|
|
|
177
153
|
fetchConfirmationStatuses();
|
|
@@ -179,7 +155,7 @@ export const CaseSidebar = ({
|
|
|
179
155
|
return () => {
|
|
180
156
|
isCancelled = true;
|
|
181
157
|
};
|
|
182
|
-
}, [currentCase, fileIdsKey, user, files
|
|
158
|
+
}, [currentCase, fileIdsKey, user, files]);
|
|
183
159
|
|
|
184
160
|
// Refresh only selected file confirmation status after confirmation-related data is persisted
|
|
185
161
|
useEffect(() => {
|
|
@@ -191,24 +167,18 @@ export const CaseSidebar = ({
|
|
|
191
167
|
}
|
|
192
168
|
|
|
193
169
|
try {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
isConfirmed: !!annotations?.confirmationData,
|
|
198
|
-
};
|
|
170
|
+
const caseSummary =
|
|
171
|
+
await getCaseConfirmationSummary(user, currentCase) ||
|
|
172
|
+
await ensureCaseConfirmationSummary(user, currentCase, files);
|
|
199
173
|
|
|
200
174
|
if (isCancelled) {
|
|
201
175
|
return;
|
|
202
176
|
}
|
|
203
177
|
|
|
204
|
-
setFileConfirmationStatus(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
setCaseConfirmationStatus(calculateCaseConfirmationStatus(next));
|
|
211
|
-
return next;
|
|
178
|
+
setFileConfirmationStatus(caseSummary.filesById);
|
|
179
|
+
setCaseConfirmationStatus({
|
|
180
|
+
includeConfirmation: caseSummary.includeConfirmation,
|
|
181
|
+
isConfirmed: caseSummary.isConfirmed
|
|
212
182
|
});
|
|
213
183
|
} catch (err) {
|
|
214
184
|
console.error(`Error refreshing confirmation status for file ${selectedFileId}:`, err);
|
|
@@ -220,7 +190,7 @@ export const CaseSidebar = ({
|
|
|
220
190
|
return () => {
|
|
221
191
|
isCancelled = true;
|
|
222
192
|
};
|
|
223
|
-
}, [currentCase, fileIdsKey, user, selectedFileId, confirmationSaveVersion, files
|
|
193
|
+
}, [currentCase, fileIdsKey, user, selectedFileId, confirmationSaveVersion, files]);
|
|
224
194
|
|
|
225
195
|
const handleFileDelete = async (fileId: string) => {
|
|
226
196
|
// Don't allow file deletion for read-only cases
|
|
@@ -260,6 +230,26 @@ const handleImageSelect = (file: FileData) => {
|
|
|
260
230
|
setImageLoaded(false);
|
|
261
231
|
};
|
|
262
232
|
|
|
233
|
+
const handleExportConfirmations = useCallback(async () => {
|
|
234
|
+
if (!currentCase || !isReadOnly || !isArchivedCase) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
setIsExportingConfirmations(true);
|
|
240
|
+
await exportConfirmationData(user, currentCase);
|
|
241
|
+
onExportNotification?.(`Confirmation export for case ${currentCase} downloaded successfully.`, 'success');
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Failed to export confirmations:', error);
|
|
244
|
+
onExportNotification?.(
|
|
245
|
+
error instanceof Error ? error.message : 'Failed to export confirmation data.',
|
|
246
|
+
'error'
|
|
247
|
+
);
|
|
248
|
+
} finally {
|
|
249
|
+
setIsExportingConfirmations(false);
|
|
250
|
+
}
|
|
251
|
+
}, [currentCase, isArchivedCase, isReadOnly, onExportNotification, user]);
|
|
252
|
+
|
|
263
253
|
const selectedFileConfirmationState = selectedFileId
|
|
264
254
|
? fileConfirmationStatus[selectedFileId]
|
|
265
255
|
: undefined;
|
|
@@ -290,6 +280,14 @@ const handleImageSelect = (file: FileData) => {
|
|
|
290
280
|
? 'Select an image first'
|
|
291
281
|
: undefined;
|
|
292
282
|
|
|
283
|
+
const showExportConfirmationsButton = Boolean(currentCase && isReadOnly && !isArchivedCase);
|
|
284
|
+
|
|
285
|
+
const exportConfirmationsTitle = isUploading
|
|
286
|
+
? 'Cannot export confirmations while uploading'
|
|
287
|
+
: !currentCase
|
|
288
|
+
? 'Load a case first'
|
|
289
|
+
: undefined;
|
|
290
|
+
|
|
293
291
|
return (
|
|
294
292
|
<>
|
|
295
293
|
<div className={styles.caseSection}>
|
|
@@ -303,19 +301,30 @@ return (
|
|
|
303
301
|
setFiles={setFiles}
|
|
304
302
|
isReadOnly={isReadOnly}
|
|
305
303
|
selectedFileId={selectedFileId}
|
|
304
|
+
confirmationSaveVersion={confirmationSaveVersion}
|
|
306
305
|
/>
|
|
307
306
|
|
|
308
307
|
<div className={styles.filesSection}>
|
|
309
308
|
<div className={currentCase ? (isReadOnly ? styles.readOnlyContainer : styles.caseHeader) : styles.emptyCaseHeader}>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
?
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
309
|
+
{currentCase ? (
|
|
310
|
+
<h4 className={`${styles.caseNumber} ${
|
|
311
|
+
caseConfirmationStatus.includeConfirmation
|
|
312
|
+
? caseConfirmationStatus.isConfirmed
|
|
313
|
+
? styles.caseConfirmed
|
|
314
|
+
: styles.caseNotConfirmed
|
|
315
|
+
: ''
|
|
316
|
+
}`}>
|
|
317
|
+
{currentCase}
|
|
318
|
+
</h4>
|
|
319
|
+
) : (
|
|
320
|
+
<button
|
|
321
|
+
type="button"
|
|
322
|
+
className={styles.openCaseButton}
|
|
323
|
+
onClick={onOpenCase}
|
|
324
|
+
>
|
|
325
|
+
Open Case
|
|
326
|
+
</button>
|
|
327
|
+
)}
|
|
319
328
|
</div>
|
|
320
329
|
{currentCase && (
|
|
321
330
|
<ImageUploadZone
|
|
@@ -397,14 +406,25 @@ return (
|
|
|
397
406
|
)}
|
|
398
407
|
</div>
|
|
399
408
|
<div className={styles.sidebarToggle}>
|
|
400
|
-
|
|
409
|
+
{showExportConfirmationsButton ? (
|
|
410
|
+
<button
|
|
411
|
+
className={styles.confirmationExportButton}
|
|
412
|
+
onClick={() => void handleExportConfirmations()}
|
|
413
|
+
disabled={isUploading || !currentCase || isExportingConfirmations}
|
|
414
|
+
title={exportConfirmationsTitle}
|
|
415
|
+
>
|
|
416
|
+
{isExportingConfirmations ? 'Exporting...' : 'Export Confirmations'}
|
|
417
|
+
</button>
|
|
418
|
+
) : (
|
|
419
|
+
<button
|
|
401
420
|
onClick={onNotesClick}
|
|
402
421
|
disabled={isImageNotesDisabled}
|
|
403
422
|
title={imageNotesTitle}
|
|
404
423
|
>
|
|
405
424
|
Image Notes
|
|
406
425
|
</button>
|
|
407
|
-
|
|
426
|
+
)}
|
|
427
|
+
</div>
|
|
408
428
|
</div>
|
|
409
429
|
</>
|
|
410
430
|
);
|