@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
|
@@ -6,16 +6,20 @@
|
|
|
6
6
|
justify-content: center;
|
|
7
7
|
align-items: center;
|
|
8
8
|
z-index: var(--zIndex5);
|
|
9
|
+
cursor: default;
|
|
9
10
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
.modal {
|
|
14
|
+
position: relative;
|
|
13
15
|
background: var(--backgroundLight);
|
|
14
16
|
border-radius: var(--spaceXS);
|
|
15
17
|
width: 90%;
|
|
16
18
|
max-width: var(--maxWidthL);
|
|
17
19
|
max-height: 80vh;
|
|
18
|
-
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
20
|
+
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
21
|
+
color-mix(in lab, var(--black) 10%, transparent);
|
|
22
|
+
cursor: default;
|
|
19
23
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
20
24
|
display: flex;
|
|
21
25
|
flex-direction: column;
|
|
@@ -134,33 +138,65 @@
|
|
|
134
138
|
|
|
135
139
|
/* Case Confirmation Status Indicators */
|
|
136
140
|
.caseItemNotConfirmed {
|
|
137
|
-
background-color: color-mix(
|
|
141
|
+
background-color: color-mix(
|
|
142
|
+
in lab,
|
|
143
|
+
var(--warning) 15%,
|
|
144
|
+
var(--backgroundLight)
|
|
145
|
+
);
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
.caseItemNotConfirmed:hover {
|
|
141
|
-
background-color: color-mix(
|
|
149
|
+
background-color: color-mix(
|
|
150
|
+
in lab,
|
|
151
|
+
var(--warning) 20%,
|
|
152
|
+
var(--backgroundLight)
|
|
153
|
+
);
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
.caseItem.active.caseItemNotConfirmed {
|
|
145
|
-
background-color: color-mix(
|
|
157
|
+
background-color: color-mix(
|
|
158
|
+
in lab,
|
|
159
|
+
var(--warning) 15%,
|
|
160
|
+
var(--backgroundLight)
|
|
161
|
+
);
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
.caseItem.active.caseItemNotConfirmed:hover {
|
|
149
|
-
background-color: color-mix(
|
|
165
|
+
background-color: color-mix(
|
|
166
|
+
in lab,
|
|
167
|
+
var(--warning) 20%,
|
|
168
|
+
var(--backgroundLight)
|
|
169
|
+
);
|
|
150
170
|
}
|
|
151
171
|
|
|
152
172
|
.caseItemConfirmed {
|
|
153
|
-
background-color: color-mix(
|
|
173
|
+
background-color: color-mix(
|
|
174
|
+
in lab,
|
|
175
|
+
var(--success) 20%,
|
|
176
|
+
var(--backgroundLight)
|
|
177
|
+
);
|
|
154
178
|
}
|
|
155
179
|
|
|
156
180
|
.caseItemConfirmed:hover {
|
|
157
|
-
background-color: color-mix(
|
|
181
|
+
background-color: color-mix(
|
|
182
|
+
in lab,
|
|
183
|
+
var(--success) 28%,
|
|
184
|
+
var(--backgroundLight)
|
|
185
|
+
);
|
|
158
186
|
}
|
|
159
187
|
|
|
160
188
|
.caseItem.active.caseItemConfirmed {
|
|
161
|
-
background-color: color-mix(
|
|
189
|
+
background-color: color-mix(
|
|
190
|
+
in lab,
|
|
191
|
+
var(--success) 20%,
|
|
192
|
+
var(--backgroundLight)
|
|
193
|
+
);
|
|
162
194
|
}
|
|
163
195
|
|
|
164
196
|
.caseItem.active.caseItemConfirmed:hover {
|
|
165
|
-
background-color: color-mix(
|
|
197
|
+
background-color: color-mix(
|
|
198
|
+
in lab,
|
|
199
|
+
var(--success) 28%,
|
|
200
|
+
var(--backgroundLight)
|
|
201
|
+
);
|
|
166
202
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import type { User } from 'firebase/auth';
|
|
3
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
4
|
import { listCases } from '~/components/actions/case-manage';
|
|
4
5
|
import { getFileAnnotations } from '~/utils/data';
|
|
5
6
|
import { fetchFiles } from '~/components/actions/image-manage';
|
|
@@ -18,6 +19,14 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
18
19
|
const [isLoading, setIsLoading] = useState(false);
|
|
19
20
|
const [error, setError] = useState<string>('');
|
|
20
21
|
const [currentPage, setCurrentPage] = useState(0);
|
|
22
|
+
const {
|
|
23
|
+
requestClose,
|
|
24
|
+
overlayProps,
|
|
25
|
+
getCloseButtonProps
|
|
26
|
+
} = useOverlayDismiss({
|
|
27
|
+
isOpen,
|
|
28
|
+
onClose
|
|
29
|
+
});
|
|
21
30
|
const [caseConfirmationStatus, setCaseConfirmationStatus] = useState<{
|
|
22
31
|
[caseNum: string]: { includeConfirmation: boolean; isConfirmed: boolean }
|
|
23
32
|
}>({});
|
|
@@ -28,19 +37,6 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
28
37
|
setError('');
|
|
29
38
|
};
|
|
30
39
|
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
33
|
-
if (e.key === 'Escape') {
|
|
34
|
-
onClose();
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
if (isOpen) {
|
|
39
|
-
document.addEventListener('keydown', handleEscape);
|
|
40
|
-
return () => document.removeEventListener('keydown', handleEscape);
|
|
41
|
-
}
|
|
42
|
-
}, [isOpen, onClose]);
|
|
43
|
-
|
|
44
40
|
useEffect(() => {
|
|
45
41
|
if (isOpen) {
|
|
46
42
|
const loadingTimer = window.setTimeout(() => {
|
|
@@ -144,11 +140,15 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
144
140
|
if (!isOpen) return null;
|
|
145
141
|
|
|
146
142
|
return (
|
|
147
|
-
<div
|
|
143
|
+
<div
|
|
144
|
+
className={styles.modalOverlay}
|
|
145
|
+
aria-label="Close cases dialog"
|
|
146
|
+
{...overlayProps}
|
|
147
|
+
>
|
|
148
148
|
<div className={styles.modal}>
|
|
149
149
|
<header className={styles.modalHeader}>
|
|
150
150
|
<h2>All Cases</h2>
|
|
151
|
-
<button
|
|
151
|
+
<button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close cases dialog' })}>×</button>
|
|
152
152
|
</header>
|
|
153
153
|
|
|
154
154
|
<div className={styles.modalContent}>
|
|
@@ -176,7 +176,7 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
176
176
|
className={`${styles.caseItem} ${currentCase === caseNum ? styles.active : ''} ${confirmationClass}`}
|
|
177
177
|
onClick={() => {
|
|
178
178
|
onSelectCase(caseNum);
|
|
179
|
-
|
|
179
|
+
requestClose();
|
|
180
180
|
}}
|
|
181
181
|
>
|
|
182
182
|
{caseNum}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/* Case Management */
|
|
2
2
|
.caseSection {
|
|
3
|
-
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 0.75rem;
|
|
6
|
+
height: 100%;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
margin-bottom: 0;
|
|
4
9
|
}
|
|
5
10
|
|
|
6
11
|
.caseSection h4 {
|
|
@@ -141,17 +146,18 @@
|
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
.filesModalSection {
|
|
144
|
-
margin:
|
|
149
|
+
margin: 0.25rem 0 0;
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
.filesModalButton {
|
|
148
153
|
width: 100%;
|
|
149
|
-
padding: 0.75rem;
|
|
154
|
+
padding: 0.625rem 0.75rem;
|
|
150
155
|
background-color: #17a2b8;
|
|
151
156
|
color: white;
|
|
152
157
|
border: none;
|
|
153
158
|
border-radius: 6px;
|
|
154
159
|
font-weight: 500;
|
|
160
|
+
font-size: 0.9rem;
|
|
155
161
|
cursor: pointer;
|
|
156
162
|
transition: all 0.2s;
|
|
157
163
|
box-sizing: border-box;
|
|
@@ -179,16 +185,39 @@
|
|
|
179
185
|
|
|
180
186
|
/* Files Section */
|
|
181
187
|
.filesSection {
|
|
182
|
-
|
|
188
|
+
display: flex;
|
|
189
|
+
flex: 1;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
gap: 0.625rem;
|
|
192
|
+
min-height: 0;
|
|
193
|
+
margin-top: 0.25rem;
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
.filesSection h4 {
|
|
186
|
-
margin-bottom:
|
|
187
|
-
font-size: 1.
|
|
197
|
+
margin-bottom: 0;
|
|
198
|
+
font-size: 1.1rem;
|
|
188
199
|
font-weight: 900;
|
|
189
200
|
text-align: center;
|
|
190
201
|
}
|
|
191
202
|
|
|
203
|
+
.emptyCaseHeader {
|
|
204
|
+
margin-top: 0.75rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.fileListPlaceholder {
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
min-height: 5rem;
|
|
212
|
+
padding: 0.875rem;
|
|
213
|
+
border: 1px dashed #dee2e6;
|
|
214
|
+
border-radius: 6px;
|
|
215
|
+
background: #f8f9fa;
|
|
216
|
+
color: #6c757d;
|
|
217
|
+
font-size: 0.875rem;
|
|
218
|
+
text-align: center;
|
|
219
|
+
}
|
|
220
|
+
|
|
192
221
|
.emptyState {
|
|
193
222
|
color: #6c757d;
|
|
194
223
|
font-size: 0.9rem;
|
|
@@ -207,10 +236,20 @@
|
|
|
207
236
|
border: 1px solid #dee2e6;
|
|
208
237
|
border-radius: 6px;
|
|
209
238
|
overflow: hidden;
|
|
210
|
-
|
|
239
|
+
flex: 1;
|
|
240
|
+
min-height: 0;
|
|
241
|
+
max-height: none;
|
|
211
242
|
overflow-y: auto;
|
|
212
243
|
}
|
|
213
244
|
|
|
245
|
+
.fileListMessage {
|
|
246
|
+
padding: 0.875rem;
|
|
247
|
+
color: #6c757d;
|
|
248
|
+
font-size: 0.875rem;
|
|
249
|
+
text-align: center;
|
|
250
|
+
background: #f8f9fa;
|
|
251
|
+
}
|
|
252
|
+
|
|
214
253
|
.fileList::-webkit-scrollbar {
|
|
215
254
|
width: 6px;
|
|
216
255
|
}
|
|
@@ -231,7 +270,7 @@
|
|
|
231
270
|
.fileItem {
|
|
232
271
|
display: flex;
|
|
233
272
|
align-items: center;
|
|
234
|
-
padding: 0.
|
|
273
|
+
padding: 0.375rem 0.625rem;
|
|
235
274
|
border-bottom: 1px solid #dee2e6;
|
|
236
275
|
background: white;
|
|
237
276
|
transition: background-color 0.2s;
|
|
@@ -240,13 +279,14 @@
|
|
|
240
279
|
.fileButton {
|
|
241
280
|
flex: 1;
|
|
242
281
|
text-align: left;
|
|
243
|
-
padding: 0.
|
|
282
|
+
padding: 0.375rem;
|
|
244
283
|
background: none;
|
|
245
284
|
border: none;
|
|
246
285
|
cursor: pointer;
|
|
247
286
|
overflow: hidden;
|
|
248
287
|
text-overflow: ellipsis;
|
|
249
288
|
white-space: nowrap;
|
|
289
|
+
font-size: 0.875rem;
|
|
250
290
|
}
|
|
251
291
|
|
|
252
292
|
.fileItem:last-child {
|
|
@@ -335,14 +375,14 @@
|
|
|
335
375
|
background: none;
|
|
336
376
|
border: none;
|
|
337
377
|
color: #dc3545;
|
|
338
|
-
font-size:
|
|
378
|
+
font-size: 1rem;
|
|
339
379
|
cursor: pointer;
|
|
340
|
-
padding: 0.
|
|
380
|
+
padding: 0.375rem;
|
|
341
381
|
display: flex;
|
|
342
382
|
align-items: center;
|
|
343
383
|
justify-content: center;
|
|
344
|
-
min-width:
|
|
345
|
-
height:
|
|
384
|
+
min-width: 28px;
|
|
385
|
+
height: 28px;
|
|
346
386
|
}
|
|
347
387
|
|
|
348
388
|
.deleteButton:hover {
|
|
@@ -404,18 +444,19 @@
|
|
|
404
444
|
/* Notes Toggle */
|
|
405
445
|
|
|
406
446
|
.sidebarToggle {
|
|
407
|
-
margin-
|
|
408
|
-
padding:
|
|
447
|
+
margin-top: 0;
|
|
448
|
+
padding: 0;
|
|
409
449
|
}
|
|
410
450
|
|
|
411
451
|
.sidebarToggle button {
|
|
412
452
|
width: 100%;
|
|
413
|
-
padding: 0.75rem;
|
|
453
|
+
padding: 0.625rem 0.75rem;
|
|
414
454
|
background-color: var(--primary);
|
|
415
455
|
color: white;
|
|
416
456
|
border: none;
|
|
417
457
|
border-radius: 6px;
|
|
418
458
|
font-weight: 500;
|
|
459
|
+
font-size: 0.9rem;
|
|
419
460
|
cursor: pointer;
|
|
420
461
|
transition: all 0.2s;
|
|
421
462
|
}
|
|
@@ -643,27 +684,27 @@
|
|
|
643
684
|
background: #fff3cd;
|
|
644
685
|
border: 1px solid #ffeaa7;
|
|
645
686
|
border-radius: 4px;
|
|
646
|
-
padding: 0.75rem;
|
|
647
|
-
margin-bottom:
|
|
687
|
+
padding: 0.625rem 0.75rem;
|
|
688
|
+
margin-bottom: 0;
|
|
648
689
|
}
|
|
649
690
|
|
|
650
691
|
.caseNumber {
|
|
651
692
|
margin: 0;
|
|
652
|
-
font-size:
|
|
693
|
+
font-size: 0.95rem;
|
|
653
694
|
font-weight: 600;
|
|
654
695
|
}
|
|
655
696
|
|
|
656
697
|
/* Case Confirmation Status Indicators */
|
|
657
698
|
.caseNumber.caseNotConfirmed {
|
|
658
699
|
background-color: #fffacd;
|
|
659
|
-
padding: 0.75rem;
|
|
700
|
+
padding: 0.625rem 0.75rem;
|
|
660
701
|
border-radius: 4px;
|
|
661
702
|
margin: 0;
|
|
662
703
|
}
|
|
663
704
|
|
|
664
705
|
.caseNumber.caseConfirmed {
|
|
665
706
|
background-color: #c8e6c9;
|
|
666
|
-
padding: 0.75rem;
|
|
707
|
+
padding: 0.625rem 0.75rem;
|
|
667
708
|
border-radius: 4px;
|
|
668
709
|
margin: 0;
|
|
669
710
|
}
|
|
@@ -6,16 +6,20 @@
|
|
|
6
6
|
justify-content: center;
|
|
7
7
|
align-items: center;
|
|
8
8
|
z-index: var(--zIndex5);
|
|
9
|
+
cursor: default;
|
|
9
10
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
.modal {
|
|
14
|
+
position: relative;
|
|
13
15
|
background: var(--backgroundLight);
|
|
14
16
|
border-radius: var(--spaceXS);
|
|
15
17
|
width: 90%;
|
|
16
18
|
max-width: var(--maxWidthL);
|
|
17
19
|
max-height: 80vh;
|
|
18
|
-
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
20
|
+
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
21
|
+
color-mix(in lab, var(--black) 10%, transparent);
|
|
22
|
+
cursor: default;
|
|
19
23
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
20
24
|
display: flex;
|
|
21
25
|
flex-direction: column;
|
|
@@ -177,33 +181,65 @@
|
|
|
177
181
|
}
|
|
178
182
|
/* Confirmation Status Indicators */
|
|
179
183
|
.fileItemNotConfirmed {
|
|
180
|
-
background-color: color-mix(
|
|
184
|
+
background-color: color-mix(
|
|
185
|
+
in lab,
|
|
186
|
+
var(--warning) 15%,
|
|
187
|
+
var(--backgroundLight)
|
|
188
|
+
);
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
.fileItemNotConfirmed:hover {
|
|
184
|
-
background-color: color-mix(
|
|
192
|
+
background-color: color-mix(
|
|
193
|
+
in lab,
|
|
194
|
+
var(--warning) 20%,
|
|
195
|
+
var(--backgroundLight)
|
|
196
|
+
);
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
.fileItem.active.fileItemNotConfirmed {
|
|
188
|
-
background-color: color-mix(
|
|
200
|
+
background-color: color-mix(
|
|
201
|
+
in lab,
|
|
202
|
+
var(--warning) 15%,
|
|
203
|
+
var(--backgroundLight)
|
|
204
|
+
);
|
|
189
205
|
}
|
|
190
206
|
|
|
191
207
|
.fileItem.active.fileItemNotConfirmed:hover {
|
|
192
|
-
background-color: color-mix(
|
|
208
|
+
background-color: color-mix(
|
|
209
|
+
in lab,
|
|
210
|
+
var(--warning) 20%,
|
|
211
|
+
var(--backgroundLight)
|
|
212
|
+
);
|
|
193
213
|
}
|
|
194
214
|
|
|
195
215
|
.fileItemConfirmed {
|
|
196
|
-
background-color: color-mix(
|
|
216
|
+
background-color: color-mix(
|
|
217
|
+
in lab,
|
|
218
|
+
var(--success) 20%,
|
|
219
|
+
var(--backgroundLight)
|
|
220
|
+
);
|
|
197
221
|
}
|
|
198
222
|
|
|
199
223
|
.fileItemConfirmed:hover {
|
|
200
|
-
background-color: color-mix(
|
|
224
|
+
background-color: color-mix(
|
|
225
|
+
in lab,
|
|
226
|
+
var(--success) 28%,
|
|
227
|
+
var(--backgroundLight)
|
|
228
|
+
);
|
|
201
229
|
}
|
|
202
230
|
|
|
203
231
|
.fileItem.active.fileItemConfirmed {
|
|
204
|
-
background-color: color-mix(
|
|
232
|
+
background-color: color-mix(
|
|
233
|
+
in lab,
|
|
234
|
+
var(--success) 20%,
|
|
235
|
+
var(--backgroundLight)
|
|
236
|
+
);
|
|
205
237
|
}
|
|
206
238
|
|
|
207
239
|
.fileItem.active.fileItemConfirmed:hover {
|
|
208
|
-
background-color: color-mix(
|
|
209
|
-
|
|
240
|
+
background-color: color-mix(
|
|
241
|
+
in lab,
|
|
242
|
+
var(--success) 28%,
|
|
243
|
+
var(--backgroundLight)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { useState, useContext, useEffect } from 'react';
|
|
3
3
|
import { AuthContext } from '~/contexts/auth.context';
|
|
4
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
4
5
|
import { deleteFile } from '~/components/actions/image-manage';
|
|
5
6
|
import { getFileAnnotations } from '~/utils/data';
|
|
6
7
|
import { type FileData } from '~/types';
|
|
@@ -33,6 +34,14 @@ export const FilesModal = ({ isOpen, onClose, onFileSelect, currentCase, files,
|
|
|
33
34
|
const [currentPage, setCurrentPage] = useState(0);
|
|
34
35
|
const [deletingFileId, setDeletingFileId] = useState<string | null>(null);
|
|
35
36
|
const [fileConfirmationStatus, setFileConfirmationStatus] = useState<FileConfirmationStatus>({});
|
|
37
|
+
const {
|
|
38
|
+
requestClose,
|
|
39
|
+
overlayProps,
|
|
40
|
+
getCloseButtonProps
|
|
41
|
+
} = useOverlayDismiss({
|
|
42
|
+
isOpen,
|
|
43
|
+
onClose
|
|
44
|
+
});
|
|
36
45
|
|
|
37
46
|
const totalPages = Math.ceil(files.length / FILES_PER_PAGE);
|
|
38
47
|
const startIndex = currentPage * FILES_PER_PAGE;
|
|
@@ -88,24 +97,9 @@ export const FilesModal = ({ isOpen, onClose, onFileSelect, currentCase, files,
|
|
|
88
97
|
fetchConfirmationStatuses();
|
|
89
98
|
}, [isOpen, currentCase, currentPage, files, user]);
|
|
90
99
|
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
const handleEscape = (event: KeyboardEvent) => {
|
|
93
|
-
if (event.key === 'Escape' && isOpen) {
|
|
94
|
-
onClose();
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
if (isOpen) {
|
|
99
|
-
document.addEventListener('keydown', handleEscape);
|
|
100
|
-
return () => {
|
|
101
|
-
document.removeEventListener('keydown', handleEscape);
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}, [isOpen, onClose]);
|
|
105
|
-
|
|
106
100
|
const handleFileSelect = (file: FileData) => {
|
|
107
101
|
onFileSelect?.(file);
|
|
108
|
-
|
|
102
|
+
requestClose();
|
|
109
103
|
};
|
|
110
104
|
|
|
111
105
|
const handleDeleteFile = async (fileId: string, event: React.MouseEvent) => {
|
|
@@ -123,10 +117,15 @@ export const FilesModal = ({ isOpen, onClose, onFileSelect, currentCase, files,
|
|
|
123
117
|
setDeletingFileId(fileId);
|
|
124
118
|
|
|
125
119
|
try {
|
|
126
|
-
await deleteFile(user, currentCase, fileId);
|
|
120
|
+
const deleteResult = await deleteFile(user, currentCase, fileId);
|
|
127
121
|
// Remove the deleted file from the list
|
|
128
122
|
const updatedFiles = files.filter(f => f.id !== fileId);
|
|
129
123
|
setFiles(updatedFiles);
|
|
124
|
+
|
|
125
|
+
if (deleteResult.imageMissing) {
|
|
126
|
+
setError(`File record deleted. Image asset "${deleteResult.fileName}" was not found and was skipped.`);
|
|
127
|
+
setTimeout(() => setError(null), 4000);
|
|
128
|
+
}
|
|
130
129
|
|
|
131
130
|
// Adjust page if needed
|
|
132
131
|
const newTotalPages = Math.ceil(updatedFiles.length / FILES_PER_PAGE);
|
|
@@ -166,15 +165,15 @@ export const FilesModal = ({ isOpen, onClose, onFileSelect, currentCase, files,
|
|
|
166
165
|
if (!isOpen) return null;
|
|
167
166
|
|
|
168
167
|
return (
|
|
169
|
-
<div
|
|
168
|
+
<div
|
|
169
|
+
className={styles.modalOverlay}
|
|
170
|
+
aria-label="Close files dialog"
|
|
171
|
+
{...overlayProps}
|
|
172
|
+
>
|
|
170
173
|
<div className={styles.modal}>
|
|
171
174
|
<div className={styles.modalHeader}>
|
|
172
175
|
<h2>Files in Case {currentCase}</h2>
|
|
173
|
-
<button
|
|
174
|
-
className={styles.closeButton}
|
|
175
|
-
onClick={onClose}
|
|
176
|
-
aria-label="Close modal"
|
|
177
|
-
>
|
|
176
|
+
<button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close files dialog' })}>
|
|
178
177
|
×
|
|
179
178
|
</button>
|
|
180
179
|
</div>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.overlay {
|
|
2
|
+
position: fixed;
|
|
3
|
+
inset: 0;
|
|
4
|
+
background-color: color-mix(in lab, var(--background) 56%, transparent);
|
|
5
|
+
display: flex;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
align-items: center;
|
|
8
|
+
z-index: var(--zIndex5);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.modal {
|
|
12
|
+
position: relative;
|
|
13
|
+
width: min(900px, calc(100vw - 2rem));
|
|
14
|
+
max-height: calc(100vh - 4rem);
|
|
15
|
+
background: var(--backgroundLight);
|
|
16
|
+
border-radius: var(--spaceXS);
|
|
17
|
+
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
18
|
+
color-mix(in lab, var(--black) 16%, transparent);
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.header {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: space-between;
|
|
28
|
+
padding: var(--spaceM) var(--spaceL);
|
|
29
|
+
border-bottom: 1px solid color-mix(in lab, var(--text) 12%, transparent);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.title {
|
|
33
|
+
margin: 0;
|
|
34
|
+
color: var(--textTitle);
|
|
35
|
+
font-size: var(--fontSizeBodyM);
|
|
36
|
+
font-weight: var(--fontWeightMedium);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.closeButton {
|
|
40
|
+
background: none;
|
|
41
|
+
border: none;
|
|
42
|
+
color: var(--textLight);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.content {
|
|
46
|
+
padding: var(--spaceM) var(--spaceL);
|
|
47
|
+
overflow-y: auto;
|
|
48
|
+
max-height: calc(100vh - 11rem);
|
|
49
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { User } from 'firebase/auth';
|
|
2
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
|
+
import { NotesSidebar } from './notes-sidebar';
|
|
4
|
+
import styles from './notes-editor-modal.module.css';
|
|
5
|
+
|
|
6
|
+
interface NotesEditorModalProps {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
currentCase: string;
|
|
10
|
+
user: User;
|
|
11
|
+
imageId: string;
|
|
12
|
+
originalFileName?: string;
|
|
13
|
+
onAnnotationRefresh?: () => void;
|
|
14
|
+
isUploading?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const NotesEditorModal = ({
|
|
18
|
+
isOpen,
|
|
19
|
+
onClose,
|
|
20
|
+
currentCase,
|
|
21
|
+
user,
|
|
22
|
+
imageId,
|
|
23
|
+
originalFileName,
|
|
24
|
+
onAnnotationRefresh,
|
|
25
|
+
isUploading = false,
|
|
26
|
+
}: NotesEditorModalProps) => {
|
|
27
|
+
const {
|
|
28
|
+
requestClose,
|
|
29
|
+
overlayProps,
|
|
30
|
+
getCloseButtonProps,
|
|
31
|
+
} = useOverlayDismiss({
|
|
32
|
+
isOpen,
|
|
33
|
+
onClose,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!isOpen) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className={styles.overlay} aria-label="Close image notes dialog" {...overlayProps}>
|
|
42
|
+
<div className={styles.modal} role="dialog" aria-modal="true" aria-label="Image Notes">
|
|
43
|
+
<div className={styles.header}>
|
|
44
|
+
<h2 className={styles.title}>Image Notes</h2>
|
|
45
|
+
<button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close image notes dialog' })}>
|
|
46
|
+
×
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
<div className={styles.content}>
|
|
50
|
+
<NotesSidebar
|
|
51
|
+
currentCase={currentCase}
|
|
52
|
+
onReturn={requestClose}
|
|
53
|
+
user={user}
|
|
54
|
+
imageId={imageId}
|
|
55
|
+
onAnnotationRefresh={onAnnotationRefresh}
|
|
56
|
+
originalFileName={originalFileName}
|
|
57
|
+
isUploading={isUploading}
|
|
58
|
+
showReturnButton={false}
|
|
59
|
+
stickyActionBar={true}
|
|
60
|
+
compactLayout={true}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|