@striae-org/striae 4.1.0 → 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 +9 -2
- package/app/components/actions/image-manage.ts +77 -44
- package/app/components/audit/user-audit-viewer.tsx +19 -8
- package/app/components/audit/user-audit.module.css +21 -0
- package/app/components/audit/viewer/audit-entries-list.tsx +7 -0
- package/app/components/audit/viewer/audit-filters-panel.tsx +1 -0
- package/app/components/audit/viewer/audit-viewer-utils.ts +2 -0
- package/app/components/audit/viewer/use-audit-viewer-data.ts +21 -1
- 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 +14 -16
- package/app/components/canvas/confirmation/confirmation.module.css +1 -0
- package/app/components/canvas/confirmation/confirmation.tsx +6 -12
- 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 +1 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +15 -16
- package/app/components/sidebar/case-export/case-export.module.css +1 -0
- package/app/components/sidebar/case-export/case-export.tsx +8 -46
- package/app/components/sidebar/case-import/case-import.module.css +23 -0
- package/app/components/sidebar/case-import/case-import.tsx +64 -16
- 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 +1 -0
- package/app/components/sidebar/cases/cases-modal.tsx +6 -8
- package/app/components/sidebar/cases/cases.module.css +62 -21
- package/app/components/sidebar/files/files-modal.module.css +1 -0
- package/app/components/sidebar/files/files-modal.tsx +12 -13
- 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 +7 -8
- package/app/components/sidebar/notes/notes-sidebar.tsx +199 -113
- package/app/components/sidebar/notes/notes.module.css +153 -0
- package/app/components/sidebar/sidebar-container.tsx +15 -28
- package/app/components/sidebar/sidebar.module.css +5 -69
- package/app/components/sidebar/sidebar.tsx +24 -125
- package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
- package/app/components/user/inactivity-warning.module.css +1 -0
- package/app/components/user/inactivity-warning.tsx +15 -2
- package/app/components/user/manage-profile.tsx +23 -10
- package/app/hooks/useOverlayDismiss.ts +52 -4
- package/app/routes/auth/login.tsx +785 -774
- package/app/routes/striae/striae.module.css +10 -3
- package/app/routes/striae/striae.tsx +469 -30
- package/app/services/audit/audit.service.ts +173 -27
- package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
- package/app/services/audit/builders/audit-event-builders-workflow.ts +2 -0
- package/app/services/audit/builders/index.ts +1 -0
- package/app/types/audit.ts +3 -1
- package/app/types/case.ts +29 -0
- package/app/types/import.ts +3 -0
- package/app/utils/data/permissions.ts +16 -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/package.json +3 -3
- package/scripts/deploy-primershear-emails.sh +2 -1
- package/worker-configuration.d.ts +1 -1
- 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/worker-configuration.d.ts +7448 -11323
- package/workers/pdf-worker/wrangler.jsonc.example +1 -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
|
@@ -16,13 +16,16 @@ interface NotesSidebarProps {
|
|
|
16
16
|
onAnnotationRefresh?: () => void;
|
|
17
17
|
originalFileName?: string;
|
|
18
18
|
isUploading?: boolean;
|
|
19
|
+
showReturnButton?: boolean;
|
|
20
|
+
stickyActionBar?: boolean;
|
|
21
|
+
compactLayout?: boolean;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
type SupportLevel = 'ID' | 'Exclusion' | 'Inconclusive';
|
|
22
25
|
type ClassType = 'Bullet' | 'Cartridge Case' | 'Other';
|
|
23
26
|
type IndexType = 'number' | 'color';
|
|
24
27
|
|
|
25
|
-
export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotationRefresh, originalFileName, isUploading = false }: NotesSidebarProps) => {
|
|
28
|
+
export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotationRefresh, originalFileName, isUploading = false, showReturnButton = true, stickyActionBar = false, compactLayout = false }: NotesSidebarProps) => {
|
|
26
29
|
// Loading/Saving Notes States
|
|
27
30
|
const [isLoading, setIsLoading] = useState(false);
|
|
28
31
|
const [loadError, setLoadError] = useState<string>();
|
|
@@ -56,6 +59,10 @@ export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotatio
|
|
|
56
59
|
// Additional Notes Modal
|
|
57
60
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
58
61
|
const [additionalNotes, setAdditionalNotes] = useState('');
|
|
62
|
+
const [isCaseInfoOpen, setIsCaseInfoOpen] = useState(true);
|
|
63
|
+
const [isClassOpen, setIsClassOpen] = useState(true);
|
|
64
|
+
const [isIndexOpen, setIsIndexOpen] = useState(true);
|
|
65
|
+
const [isSupportOpen, setIsSupportOpen] = useState(true);
|
|
59
66
|
const areInputsDisabled = isUploading || isConfirmedImage;
|
|
60
67
|
|
|
61
68
|
useEffect(() => {
|
|
@@ -227,7 +234,7 @@ export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotatio
|
|
|
227
234
|
};
|
|
228
235
|
|
|
229
236
|
return (
|
|
230
|
-
<div className={styles.notesSidebar}>
|
|
237
|
+
<div className={`${styles.notesSidebar} ${compactLayout ? styles.compactLayout : ''}`}>
|
|
231
238
|
{isLoading ? (
|
|
232
239
|
<div className={styles.loading}>Loading notes...</div>
|
|
233
240
|
) : loadError ? (
|
|
@@ -245,7 +252,17 @@ export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotatio
|
|
|
245
252
|
)}
|
|
246
253
|
|
|
247
254
|
<div className={styles.section}>
|
|
248
|
-
<
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
className={styles.sectionToggle}
|
|
258
|
+
onClick={() => setIsCaseInfoOpen((prev) => !prev)}
|
|
259
|
+
aria-expanded={isCaseInfoOpen}
|
|
260
|
+
>
|
|
261
|
+
<span className={styles.sectionTitle}>Case Information</span>
|
|
262
|
+
<span className={styles.sectionToggleIcon}>{isCaseInfoOpen ? '−' : '+'}</span>
|
|
263
|
+
</button>
|
|
264
|
+
{isCaseInfoOpen && (
|
|
265
|
+
<>
|
|
249
266
|
<hr />
|
|
250
267
|
<div className={styles.caseNumbers}>
|
|
251
268
|
{/* Left side inputs */}
|
|
@@ -279,9 +296,18 @@ export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotatio
|
|
|
279
296
|
onChange={(e) => setLeftItem(e.target.value)}
|
|
280
297
|
disabled={areInputsDisabled}
|
|
281
298
|
/>
|
|
282
|
-
</div>
|
|
299
|
+
</div>
|
|
300
|
+
{compactLayout && (
|
|
301
|
+
<div className={styles.caseInput}>
|
|
302
|
+
<label htmlFor="colorSelect">Font</label>
|
|
303
|
+
<ColorSelector
|
|
304
|
+
selectedColor={caseFontColor}
|
|
305
|
+
onColorSelect={setCaseFontColor}
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
283
309
|
</div>
|
|
284
|
-
<hr />
|
|
310
|
+
{!compactLayout && <hr />}
|
|
285
311
|
{/* Right side inputs */}
|
|
286
312
|
<div className={styles.inputGroup}>
|
|
287
313
|
<div className={styles.caseInput}>
|
|
@@ -316,63 +342,102 @@ export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotatio
|
|
|
316
342
|
</div>
|
|
317
343
|
</div>
|
|
318
344
|
</div>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
345
|
+
{!compactLayout && (
|
|
346
|
+
<>
|
|
347
|
+
<label htmlFor="colorSelect">Font</label>
|
|
348
|
+
<ColorSelector
|
|
349
|
+
selectedColor={caseFontColor}
|
|
350
|
+
onColorSelect={setCaseFontColor}
|
|
351
|
+
/>
|
|
352
|
+
</>
|
|
353
|
+
)}
|
|
354
|
+
</>
|
|
355
|
+
)}
|
|
324
356
|
</div>
|
|
325
357
|
|
|
326
|
-
<div className={styles.
|
|
327
|
-
|
|
328
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
358
|
+
<div className={compactLayout ? styles.compactSectionGrid : undefined}>
|
|
359
|
+
<div className={`${styles.section} ${compactLayout ? styles.compactFullSection : ''}`}>
|
|
360
|
+
<button
|
|
361
|
+
type="button"
|
|
362
|
+
className={styles.sectionToggle}
|
|
363
|
+
onClick={() => setIsClassOpen((prev) => !prev)}
|
|
364
|
+
aria-expanded={isClassOpen}
|
|
365
|
+
>
|
|
366
|
+
<span className={styles.sectionTitle}>Class Characteristics</span>
|
|
367
|
+
<span className={styles.sectionToggleIcon}>{isClassOpen ? '−' : '+'}</span>
|
|
368
|
+
</button>
|
|
369
|
+
{isClassOpen && (
|
|
370
|
+
<>
|
|
371
|
+
<div className={compactLayout ? styles.classCharacteristicsColumns : undefined}>
|
|
372
|
+
<div className={styles.classCharacteristicsMain}>
|
|
373
|
+
<div className={styles.classCharacteristics}>
|
|
374
|
+
<select
|
|
375
|
+
id="classType"
|
|
376
|
+
aria-label="Class Type"
|
|
377
|
+
value={classType}
|
|
378
|
+
onChange={(e) => setClassType(e.target.value as ClassType)}
|
|
379
|
+
className={styles.select}
|
|
380
|
+
disabled={areInputsDisabled}
|
|
381
|
+
>
|
|
382
|
+
<option value="">Select class type...</option>
|
|
383
|
+
<option value="Bullet">Bullet</option>
|
|
384
|
+
<option value="Cartridge Case">Cartridge Case</option>
|
|
385
|
+
<option value="Other">Other</option>
|
|
386
|
+
</select>
|
|
342
387
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
388
|
+
{classType === 'Other' && (
|
|
389
|
+
<input
|
|
390
|
+
type="text"
|
|
391
|
+
value={customClass}
|
|
392
|
+
onChange={(e) => setCustomClass(e.target.value)}
|
|
393
|
+
placeholder="Specify object type"
|
|
394
|
+
disabled={areInputsDisabled}
|
|
395
|
+
/>
|
|
396
|
+
)}
|
|
352
397
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
398
|
+
<textarea
|
|
399
|
+
value={classNote}
|
|
400
|
+
onChange={(e) => setClassNote(e.target.value)}
|
|
401
|
+
placeholder="Enter class characteristic details..."
|
|
402
|
+
className={styles.textarea}
|
|
403
|
+
disabled={areInputsDisabled}
|
|
404
|
+
/>
|
|
405
|
+
</div>
|
|
406
|
+
<label className={`${styles.checkboxLabel} mb-4`}>
|
|
407
|
+
<input
|
|
408
|
+
type="checkbox"
|
|
409
|
+
checked={hasSubclass}
|
|
410
|
+
onChange={(e) => setHasSubclass(e.target.checked)}
|
|
411
|
+
className={styles.checkbox}
|
|
412
|
+
disabled={areInputsDisabled}
|
|
413
|
+
/>
|
|
414
|
+
<span>Potential subclass?</span>
|
|
415
|
+
</label>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
{compactLayout && (
|
|
419
|
+
<div className={styles.characteristicsPlaceholder}>
|
|
420
|
+
<h6 className={styles.placeholderTitle}>Characteristics Details</h6>
|
|
421
|
+
<p className={styles.placeholderText}>This section is reserved for future development.</p>
|
|
422
|
+
</div>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
</>
|
|
426
|
+
)}
|
|
371
427
|
</div>
|
|
372
428
|
|
|
373
|
-
<div className={styles.section}>
|
|
374
|
-
<
|
|
375
|
-
|
|
429
|
+
<div className={`${styles.section} ${compactLayout ? styles.compactHalfSection : ''}`}>
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
className={styles.sectionToggle}
|
|
433
|
+
onClick={() => setIsIndexOpen((prev) => !prev)}
|
|
434
|
+
aria-expanded={isIndexOpen}
|
|
435
|
+
>
|
|
436
|
+
<span className={styles.sectionTitle}>Index Type</span>
|
|
437
|
+
<span className={styles.sectionToggleIcon}>{isIndexOpen ? '−' : '+'}</span>
|
|
438
|
+
</button>
|
|
439
|
+
{isIndexOpen && (
|
|
440
|
+
<div className={styles.indexing}>
|
|
376
441
|
<div className={styles.radioGroup}>
|
|
377
442
|
<label className={styles.radioLabel}>
|
|
378
443
|
<input
|
|
@@ -409,75 +474,96 @@ export const NotesSidebar = ({ currentCase, onReturn, user, imageId, onAnnotatio
|
|
|
409
474
|
/>
|
|
410
475
|
) : null}
|
|
411
476
|
</div>
|
|
477
|
+
)}
|
|
412
478
|
</div>
|
|
413
479
|
|
|
414
|
-
<div className={styles.section}>
|
|
415
|
-
<
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
480
|
+
<div className={`${styles.section} ${compactLayout ? styles.compactHalfSection : ''}`}>
|
|
481
|
+
<button
|
|
482
|
+
type="button"
|
|
483
|
+
className={styles.sectionToggle}
|
|
484
|
+
onClick={() => setIsSupportOpen((prev) => !prev)}
|
|
485
|
+
aria-expanded={isSupportOpen}
|
|
486
|
+
>
|
|
487
|
+
<span className={styles.sectionTitle}>Support Level</span>
|
|
488
|
+
<span className={styles.sectionToggleIcon}>{isSupportOpen ? '−' : '+'}</span>
|
|
489
|
+
</button>
|
|
490
|
+
{isSupportOpen && (
|
|
491
|
+
<>
|
|
492
|
+
<div className={styles.support}>
|
|
493
|
+
<select
|
|
494
|
+
id="supportLevel"
|
|
495
|
+
aria-label="Support Level"
|
|
496
|
+
value={supportLevel}
|
|
497
|
+
onChange={(e) => {
|
|
498
|
+
const newSupportLevel = e.target.value as SupportLevel;
|
|
499
|
+
setSupportLevel(newSupportLevel);
|
|
500
|
+
|
|
501
|
+
// Automatically check confirmation field when ID is selected
|
|
502
|
+
if (newSupportLevel === 'ID') {
|
|
503
|
+
setIncludeConfirmation(true);
|
|
504
|
+
}
|
|
505
|
+
}}
|
|
506
|
+
className={styles.select}
|
|
507
|
+
disabled={areInputsDisabled}
|
|
508
|
+
>
|
|
509
|
+
<option value="">Select support level...</option>
|
|
510
|
+
<option value="ID">Identification</option>
|
|
511
|
+
<option value="Exclusion">Exclusion</option>
|
|
512
|
+
<option value="Inconclusive">Inconclusive</option>
|
|
513
|
+
</select>
|
|
514
|
+
<label className={`${styles.checkboxLabel} mb-4`}>
|
|
515
|
+
<input
|
|
516
|
+
type="checkbox"
|
|
517
|
+
checked={includeConfirmation}
|
|
518
|
+
onChange={(e) => setIncludeConfirmation(e.target.checked)}
|
|
519
|
+
className={styles.checkbox}
|
|
520
|
+
disabled={areInputsDisabled}
|
|
521
|
+
/>
|
|
522
|
+
<span>Include confirmation field</span>
|
|
523
|
+
</label>
|
|
524
|
+
</div>
|
|
525
|
+
</>
|
|
526
|
+
)}
|
|
457
527
|
</div>
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<div className={styles.additionalNotesRow}>
|
|
531
|
+
<button
|
|
532
|
+
onClick={() => setIsModalOpen(true)}
|
|
533
|
+
className={styles.notesButton}
|
|
461
534
|
disabled={areInputsDisabled}
|
|
462
|
-
title={isConfirmedImage ? "Cannot
|
|
535
|
+
title={isConfirmedImage ? "Cannot edit notes for confirmed images" : isUploading ? "Cannot add notes while uploading" : undefined}
|
|
463
536
|
>
|
|
464
|
-
|
|
537
|
+
Additional Notes
|
|
465
538
|
</button>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<div className={`${styles.notesActionBar} ${stickyActionBar ? styles.notesActionBarSticky : ''}`}>
|
|
542
|
+
<button
|
|
543
|
+
onClick={handleSave}
|
|
544
|
+
className={styles.saveButton}
|
|
545
|
+
disabled={areInputsDisabled}
|
|
546
|
+
title={isConfirmedImage ? "Cannot save notes for confirmed images" : isUploading ? "Cannot save notes while uploading" : undefined}
|
|
547
|
+
>
|
|
548
|
+
Save Notes
|
|
549
|
+
</button>
|
|
550
|
+
{showReturnButton && (
|
|
551
|
+
<button
|
|
552
|
+
onClick={onReturn}
|
|
553
|
+
className={styles.returnButton}
|
|
554
|
+
disabled={isUploading}
|
|
555
|
+
title={isUploading ? "Cannot return while uploading" : undefined}
|
|
556
|
+
>
|
|
557
|
+
Return to Case Management
|
|
558
|
+
</button>
|
|
559
|
+
)}
|
|
560
|
+
</div>
|
|
466
561
|
|
|
467
562
|
{saveSuccess && (
|
|
468
563
|
<div className={styles.successMessage}>
|
|
469
564
|
Notes saved successfully!
|
|
470
565
|
</div>
|
|
471
566
|
)}
|
|
472
|
-
|
|
473
|
-
<button
|
|
474
|
-
onClick={onReturn}
|
|
475
|
-
className={styles.returnButton}
|
|
476
|
-
disabled={isUploading}
|
|
477
|
-
title={isUploading ? "Cannot return while uploading" : undefined}
|
|
478
|
-
>
|
|
479
|
-
Return to Case Management
|
|
480
|
-
</button>
|
|
481
567
|
<NotesModal
|
|
482
568
|
isOpen={isModalOpen}
|
|
483
569
|
onClose={() => setIsModalOpen(false)}
|
|
@@ -2,6 +2,84 @@
|
|
|
2
2
|
padding: 0.3rem;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
.compactLayout .caseNumbers {
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
8
|
+
gap: 1.25rem;
|
|
9
|
+
align-items: start;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.compactLayout .caseNumbers > .inputGroup + .inputGroup {
|
|
13
|
+
border-left: 1px solid #dee2e6;
|
|
14
|
+
padding-left: 1.25rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.compactLayout .inputGroup {
|
|
18
|
+
margin-bottom: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.compactSectionGrid {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
24
|
+
gap: 1.25rem;
|
|
25
|
+
align-items: start;
|
|
26
|
+
border-top: 1px solid #dee2e6;
|
|
27
|
+
padding-top: 1.25rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.compactFullSection {
|
|
31
|
+
grid-column: 1 / -1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.compactHalfSection {
|
|
35
|
+
margin-bottom: 1.5rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.compactLayout .notesActionBarSticky {
|
|
39
|
+
margin-top: 0.25rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.compactLayout .compactSectionGrid > .compactHalfSection + .compactHalfSection {
|
|
43
|
+
border-left: 1px solid #dee2e6;
|
|
44
|
+
padding-left: 1.25rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.classCharacteristicsColumns > .characteristicsPlaceholder {
|
|
48
|
+
border-left: 1px solid #dee2e6;
|
|
49
|
+
padding-left: 1.25rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.compactLayout .additionalNotesRow {
|
|
53
|
+
border-top: 1px solid #dee2e6;
|
|
54
|
+
padding-top: 1.25rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@media (max-width: 980px) {
|
|
58
|
+
.compactLayout .caseNumbers,
|
|
59
|
+
.compactSectionGrid {
|
|
60
|
+
grid-template-columns: 1fr;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.compactLayout .caseNumbers > .inputGroup + .inputGroup,
|
|
64
|
+
.compactLayout
|
|
65
|
+
.compactSectionGrid
|
|
66
|
+
> .compactHalfSection
|
|
67
|
+
+ .compactHalfSection,
|
|
68
|
+
.classCharacteristicsColumns > .characteristicsPlaceholder {
|
|
69
|
+
border-left: none;
|
|
70
|
+
padding-left: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.classCharacteristicsColumns {
|
|
74
|
+
grid-template-columns: 1fr;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.compactFullSection,
|
|
78
|
+
.compactHalfSection {
|
|
79
|
+
grid-column: auto;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
5
83
|
hr {
|
|
6
84
|
padding-bottom: 0.5rem;
|
|
7
85
|
margin: 0.5rem 0;
|
|
@@ -13,6 +91,26 @@ hr {
|
|
|
13
91
|
margin-bottom: 2rem;
|
|
14
92
|
}
|
|
15
93
|
|
|
94
|
+
.sectionToggle {
|
|
95
|
+
width: 100%;
|
|
96
|
+
border: none;
|
|
97
|
+
background: none;
|
|
98
|
+
padding: 0;
|
|
99
|
+
margin: 0;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: space-between;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
text-align: left;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.sectionToggleIcon {
|
|
108
|
+
color: #6c757d;
|
|
109
|
+
font-size: 1.2rem;
|
|
110
|
+
line-height: 1;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
}
|
|
113
|
+
|
|
16
114
|
.sectionTitle {
|
|
17
115
|
font-size: 1.3rem;
|
|
18
116
|
font-weight: 600;
|
|
@@ -103,6 +201,39 @@ textarea:focus {
|
|
|
103
201
|
margin-bottom: 2rem;
|
|
104
202
|
}
|
|
105
203
|
|
|
204
|
+
.classCharacteristicsColumns {
|
|
205
|
+
display: grid;
|
|
206
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
207
|
+
gap: 1.25rem;
|
|
208
|
+
align-items: start;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.classCharacteristicsMain {
|
|
212
|
+
min-width: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.characteristicsPlaceholder {
|
|
216
|
+
border: 1px dashed #ced4da;
|
|
217
|
+
border-radius: 8px;
|
|
218
|
+
padding: 0.85rem;
|
|
219
|
+
background: #f8f9fa;
|
|
220
|
+
min-height: 150px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.placeholderTitle {
|
|
224
|
+
margin: 0 0 0.5rem;
|
|
225
|
+
color: #495057;
|
|
226
|
+
font-size: 0.92rem;
|
|
227
|
+
font-weight: 600;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.placeholderText {
|
|
231
|
+
margin: 0;
|
|
232
|
+
color: #6c757d;
|
|
233
|
+
font-size: 0.85rem;
|
|
234
|
+
line-height: 1.4;
|
|
235
|
+
}
|
|
236
|
+
|
|
106
237
|
.classCharacteristics input {
|
|
107
238
|
width: 100%;
|
|
108
239
|
padding: 0.75rem;
|
|
@@ -247,6 +378,14 @@ textarea:focus {
|
|
|
247
378
|
margin-top: 1rem;
|
|
248
379
|
}
|
|
249
380
|
|
|
381
|
+
.additionalNotesRow {
|
|
382
|
+
width: 100%;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.compactLayout .additionalNotesRow {
|
|
386
|
+
grid-column: 1 / -1;
|
|
387
|
+
}
|
|
388
|
+
|
|
250
389
|
.notesButton:hover {
|
|
251
390
|
background-color: color-mix(in lab, var(--primary) 95%, transparent);
|
|
252
391
|
}
|
|
@@ -266,6 +405,7 @@ textarea:focus {
|
|
|
266
405
|
}
|
|
267
406
|
|
|
268
407
|
.modal {
|
|
408
|
+
position: relative;
|
|
269
409
|
background: white;
|
|
270
410
|
padding: 2rem;
|
|
271
411
|
border-radius: 8px;
|
|
@@ -332,6 +472,19 @@ textarea:focus {
|
|
|
332
472
|
box-sizing: border-box;
|
|
333
473
|
}
|
|
334
474
|
|
|
475
|
+
.notesActionBar {
|
|
476
|
+
display: flex;
|
|
477
|
+
flex-direction: column;
|
|
478
|
+
gap: 0.75rem;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.notesActionBarSticky {
|
|
482
|
+
position: sticky;
|
|
483
|
+
bottom: 0;
|
|
484
|
+
padding: 0.75rem 0 0.25rem;
|
|
485
|
+
background: linear-gradient(to top, white 72%, rgba(255, 255, 255, 0));
|
|
486
|
+
}
|
|
487
|
+
|
|
335
488
|
.saveButton:hover {
|
|
336
489
|
background-color: color-mix(in lab, var(--accent) 95%, transparent);
|
|
337
490
|
}
|
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
2
2
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
3
3
|
import type React from 'react';
|
|
4
|
-
import { useState
|
|
4
|
+
import { useState } from 'react';
|
|
5
5
|
import { Link } from 'react-router';
|
|
6
6
|
import { Sidebar } from './sidebar';
|
|
7
7
|
import type { User } from 'firebase/auth';
|
|
8
8
|
import { type FileData } from '~/types';
|
|
9
9
|
import styles from './sidebar.module.css';
|
|
10
10
|
import { getAppVersion } from '~/utils/common';
|
|
11
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
11
12
|
|
|
12
13
|
interface SidebarContainerProps {
|
|
13
14
|
user: User;
|
|
14
15
|
onImageSelect: (file: FileData) => void;
|
|
15
16
|
imageId?: string;
|
|
16
|
-
onCaseChange: (caseNumber: string) => void;
|
|
17
17
|
currentCase: string;
|
|
18
|
-
setCurrentCase: (caseNumber: string) => void;
|
|
19
18
|
files: FileData[];
|
|
20
19
|
setFiles: React.Dispatch<React.SetStateAction<FileData[]>>;
|
|
21
20
|
imageLoaded: boolean;
|
|
22
21
|
setImageLoaded: (loaded: boolean) => void;
|
|
23
|
-
caseNumber: string;
|
|
24
|
-
setCaseNumber: (caseNumber: string) => void;
|
|
25
|
-
error: string;
|
|
26
|
-
setError: (error: string) => void;
|
|
27
|
-
successAction: 'loaded' | 'created' | 'deleted' | null;
|
|
28
|
-
setSuccessAction: (action: 'loaded' | 'created' | 'deleted' | null) => void;
|
|
29
22
|
showNotes: boolean;
|
|
30
23
|
setShowNotes: (show: boolean) => void;
|
|
31
24
|
onAnnotationRefresh?: () => void;
|
|
32
25
|
isReadOnly?: boolean;
|
|
33
26
|
isConfirmed?: boolean;
|
|
34
27
|
confirmationSaveVersion?: number;
|
|
28
|
+
isUploading?: boolean;
|
|
29
|
+
onUploadStatusChange?: (isUploading: boolean) => void;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
export const SidebarContainer: React.FC<SidebarContainerProps> = (props) => {
|
|
@@ -39,24 +34,16 @@ export const SidebarContainer: React.FC<SidebarContainerProps> = (props) => {
|
|
|
39
34
|
const year = new Date().getFullYear();
|
|
40
35
|
const appVersion = getAppVersion();
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (isFooterModalOpen) {
|
|
50
|
-
document.addEventListener('keydown', handleEscape);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return () => {
|
|
54
|
-
document.removeEventListener('keydown', handleEscape);
|
|
55
|
-
};
|
|
56
|
-
}, [isFooterModalOpen]);
|
|
37
|
+
const {
|
|
38
|
+
overlayProps,
|
|
39
|
+
getCloseButtonProps,
|
|
40
|
+
} = useOverlayDismiss({
|
|
41
|
+
isOpen: isFooterModalOpen,
|
|
42
|
+
onClose: () => setIsFooterModalOpen(false),
|
|
43
|
+
});
|
|
57
44
|
|
|
58
45
|
return (
|
|
59
|
-
<div style={{ display: 'flex', flexDirection: 'column', height: '
|
|
46
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
60
47
|
{/* Main Sidebar */}
|
|
61
48
|
<Sidebar {...props} />
|
|
62
49
|
|
|
@@ -72,13 +59,13 @@ export const SidebarContainer: React.FC<SidebarContainerProps> = (props) => {
|
|
|
72
59
|
|
|
73
60
|
{/* Footer Modal */}
|
|
74
61
|
{isFooterModalOpen && (
|
|
75
|
-
<div className={styles.footerModalOverlay}
|
|
62
|
+
<div className={styles.footerModalOverlay} {...overlayProps} aria-label="Close About and Support dialog">
|
|
76
63
|
<div className={styles.footerModal} onClick={(e) => e.stopPropagation()}>
|
|
77
64
|
<div className={styles.footerModalHeader}>
|
|
78
65
|
<h2 className={styles.footerModalTitle}>About Striae</h2>
|
|
79
|
-
<button
|
|
80
|
-
onClick={() => setIsFooterModalOpen(false)}
|
|
66
|
+
<button
|
|
81
67
|
className={styles.footerModalClose}
|
|
68
|
+
{...getCloseButtonProps({ ariaLabel: 'Close About and Support dialog' })}
|
|
82
69
|
>
|
|
83
70
|
×
|
|
84
71
|
</button>
|