@striae-org/striae 3.2.1 → 3.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/app/components/actions/case-export/core-export.ts +2 -2
- package/app/components/actions/case-export/data-processing.ts +19 -4
- package/app/components/actions/case-export/download-handlers.ts +57 -8
- package/app/components/actions/case-export/metadata-helpers.ts +1 -1
- package/app/components/actions/case-import/annotation-import.ts +2 -2
- package/app/components/actions/case-import/confirmation-import.ts +44 -20
- package/app/components/actions/case-import/confirmation-package.ts +86 -0
- package/app/components/actions/case-import/image-operations.ts +1 -1
- package/app/components/actions/case-import/index.ts +1 -0
- package/app/components/actions/case-import/orchestrator.ts +16 -6
- package/app/components/actions/case-import/storage-operations.ts +7 -7
- package/app/components/actions/case-import/validation.ts +7 -100
- package/app/components/actions/case-import/zip-processing.ts +47 -5
- package/app/components/actions/case-manage.ts +3 -3
- package/app/components/actions/confirm-export.ts +47 -16
- package/app/components/actions/generate-pdf.ts +3 -3
- package/app/components/actions/image-manage.ts +3 -3
- package/app/components/actions/notes-manage.ts +3 -3
- package/app/components/actions/signout.tsx +1 -1
- package/app/components/audit/user-audit-viewer.tsx +2 -3
- package/app/components/auth/auth-provider.tsx +2 -2
- package/app/components/auth/mfa-enrollment.tsx +3 -3
- package/app/components/auth/mfa-verification.tsx +4 -4
- package/app/components/canvas/box-annotations/box-annotations.tsx +2 -2
- package/app/components/canvas/canvas.tsx +1 -1
- package/app/components/canvas/confirmation/confirmation.tsx +1 -1
- package/app/components/form/form-button.tsx +1 -1
- package/app/components/form/form.module.css +9 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +163 -49
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +365 -88
- package/app/components/sidebar/case-export/case-export.tsx +2 -54
- package/app/components/sidebar/case-import/case-import.tsx +20 -8
- package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +1 -1
- package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +1 -1
- package/app/components/sidebar/case-import/hooks/useFilePreview.ts +9 -7
- package/app/components/sidebar/case-import/hooks/useImportExecution.ts +2 -2
- package/app/components/sidebar/case-import/utils/file-validation.ts +57 -2
- package/app/components/sidebar/cases/case-sidebar.tsx +106 -50
- package/app/components/sidebar/cases/cases-modal.tsx +1 -1
- package/app/components/sidebar/cases/cases.module.css +101 -18
- package/app/components/sidebar/files/files-modal.tsx +3 -2
- package/app/components/sidebar/notes/notes-sidebar.tsx +3 -3
- package/app/components/sidebar/notes/notes.module.css +33 -13
- package/app/components/sidebar/sidebar-container.tsx +4 -3
- package/app/components/sidebar/sidebar.tsx +2 -2
- package/app/components/sidebar/upload/image-upload-zone.tsx +2 -2
- package/app/components/theme-provider/theme-provider.tsx +1 -1
- package/app/components/user/delete-account.tsx +1 -1
- package/app/components/user/manage-profile.tsx +3 -3
- package/app/components/user/mfa-phone-update.tsx +17 -14
- package/app/contexts/auth.context.ts +1 -1
- package/app/root.tsx +2 -2
- package/app/routes/auth/emailActionHandler.tsx +2 -2
- package/app/routes/auth/emailVerification.tsx +2 -2
- package/app/routes/auth/login.tsx +134 -11
- package/app/routes/auth/passwordReset.tsx +2 -2
- package/app/routes/striae/striae.tsx +2 -2
- package/app/services/audit/audit-console-logger.ts +46 -0
- package/app/services/audit/audit-export-csv.ts +126 -0
- package/app/services/audit/audit-export-report.ts +174 -0
- package/app/services/audit/audit-export-signing.ts +85 -0
- package/app/services/audit/audit-export.service.ts +334 -0
- package/app/services/audit/audit-file-type.ts +13 -0
- package/app/services/audit/audit-query-helpers.ts +88 -0
- package/app/services/audit/audit-worker-client.ts +95 -0
- package/app/services/audit/audit.service.ts +990 -0
- package/app/services/audit/builders/audit-entry-builder.ts +32 -0
- package/app/services/audit/builders/audit-event-builders-annotation.ts +150 -0
- package/app/services/audit/builders/audit-event-builders-case-file.ts +249 -0
- package/app/services/audit/builders/audit-event-builders-user-security.ts +449 -0
- package/app/services/audit/builders/audit-event-builders-workflow.ts +272 -0
- package/app/services/audit/builders/index.ts +40 -0
- package/app/services/audit/index.ts +2 -0
- package/app/types/case.ts +2 -2
- package/app/types/exceljs-bare.d.ts +3 -1
- package/app/types/user.ts +1 -1
- package/app/utils/SHA256.ts +5 -1
- package/app/utils/audit-export-signature.ts +2 -2
- package/app/utils/confirmation-signature.ts +8 -4
- package/app/utils/data-operations.ts +5 -5
- package/app/utils/export-verification.ts +353 -0
- package/app/utils/mfa-phone.ts +1 -1
- package/app/utils/mfa.ts +1 -1
- package/app/utils/permissions.ts +2 -2
- package/app/utils/signature-utils.ts +74 -4
- package/package.json +11 -9
- package/public/favicon.ico +0 -0
- package/public/icon-256.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.json +39 -0
- package/public/shortcut.png +0 -0
- package/public/social-image.png +0 -0
- package/react-router.config.ts +5 -0
- package/worker-configuration.d.ts +4435 -562
- package/workers/data-worker/src/data-worker.example.ts +3 -3
- package/workers/pdf-worker/scripts/generate-assets.js +94 -0
- package/workers/pdf-worker/src/{generated-assets.ts → assets/generated-assets.ts} +117 -117
- package/workers/pdf-worker/src/{format-striae.ts → formats/format-striae.ts} +535 -535
- package/workers/pdf-worker/src/pdf-worker.example.ts +1 -1
- package/app/services/audit-export.service.ts +0 -755
- package/app/services/audit.service.ts +0 -1474
- package/public/favicon.svg +0 -9
- /package/app/services/{firebase-errors.ts → firebase/errors.ts} +0 -0
- /package/app/services/{firebase.ts → firebase/index.ts} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react';
|
|
2
|
-
import { User } from 'firebase/auth';
|
|
3
|
-
import { previewCaseImport } from '~/components/actions/case-review';
|
|
4
|
-
import { CaseImportPreview } from '~/types';
|
|
5
|
-
import { ConfirmationPreview } from '../components/ConfirmationPreviewSection';
|
|
2
|
+
import type { User } from 'firebase/auth';
|
|
3
|
+
import { previewCaseImport, extractConfirmationImportPackage } from '~/components/actions/case-review';
|
|
4
|
+
import { type CaseImportPreview } from '~/types';
|
|
5
|
+
import { type ConfirmationPreview } from '../components/ConfirmationPreviewSection';
|
|
6
6
|
|
|
7
7
|
type UnknownRecord = Record<string, unknown>;
|
|
8
8
|
|
|
@@ -56,8 +56,8 @@ export const useFilePreview = (
|
|
|
56
56
|
|
|
57
57
|
setIsLoadingPreview(true);
|
|
58
58
|
try {
|
|
59
|
-
const
|
|
60
|
-
const parsed =
|
|
59
|
+
const { confirmationData } = await extractConfirmationImportPackage(file);
|
|
60
|
+
const parsed = confirmationData as unknown;
|
|
61
61
|
|
|
62
62
|
if (!isRecord(parsed)) {
|
|
63
63
|
throw new Error('Invalid confirmation data format');
|
|
@@ -104,7 +104,9 @@ export const useFilePreview = (
|
|
|
104
104
|
setConfirmationPreview(preview);
|
|
105
105
|
} catch (error) {
|
|
106
106
|
console.error('Error loading confirmation preview:', error);
|
|
107
|
-
setError(
|
|
107
|
+
setError(
|
|
108
|
+
`Failed to read confirmation data: ${error instanceof Error ? error.message : 'Invalid confirmation package format'}`
|
|
109
|
+
);
|
|
108
110
|
clearImportData();
|
|
109
111
|
} finally {
|
|
110
112
|
setIsLoadingPreview(false);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
-
import { User } from 'firebase/auth';
|
|
2
|
+
import type { User } from 'firebase/auth';
|
|
3
3
|
import { importCaseForReview, importConfirmationData } from '~/components/actions/case-review';
|
|
4
|
-
import { ImportResult, ConfirmationImportResult } from '~/types';
|
|
4
|
+
import { type ImportResult, type ConfirmationImportResult } from '~/types';
|
|
5
5
|
|
|
6
6
|
interface ProgressState {
|
|
7
7
|
stage: string;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { isConfirmationDataFile } from '~/components/actions/case-review';
|
|
2
2
|
|
|
3
|
+
const CASE_EXPORT_DATA_FILE_REGEX = /_data\.(json|csv)$/i;
|
|
4
|
+
const CONFIRMATION_EXPORT_FILE_REGEX = /^confirmation-data-.*\.json$/i;
|
|
5
|
+
const FORENSIC_MANIFEST_FILE_NAME = 'forensic_manifest.json';
|
|
6
|
+
|
|
7
|
+
function getLeafFileName(path: string): string {
|
|
8
|
+
const segments = path.split('/').filter(Boolean);
|
|
9
|
+
return segments.length > 0 ? segments[segments.length - 1] : path;
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* Check if a file is a valid ZIP file
|
|
5
14
|
*/
|
|
@@ -13,8 +22,10 @@ export const isValidZipFile = (file: File): boolean => {
|
|
|
13
22
|
* Check if a file is a valid confirmation JSON file
|
|
14
23
|
*/
|
|
15
24
|
export const isValidConfirmationFile = (file: File): boolean => {
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
const lowerName = file.name.toLowerCase();
|
|
26
|
+
const jsonType = file.type === 'application/json' || file.type === '';
|
|
27
|
+
|
|
28
|
+
return lowerName.endsWith('.json') && jsonType && isConfirmationDataFile(file.name);
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
/**
|
|
@@ -33,6 +44,50 @@ export const getImportType = (file: File): 'case' | 'confirmation' | null => {
|
|
|
33
44
|
return null;
|
|
34
45
|
};
|
|
35
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Resolve import type, including ZIP package inspection.
|
|
49
|
+
* Case ZIPs are identified by case data files or FORENSIC_MANIFEST.json.
|
|
50
|
+
* Confirmation ZIPs are identified by confirmation-data-*.json.
|
|
51
|
+
*/
|
|
52
|
+
export const resolveImportType = async (file: File): Promise<'case' | 'confirmation' | null> => {
|
|
53
|
+
if (isValidConfirmationFile(file)) {
|
|
54
|
+
return 'confirmation';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isValidZipFile(file)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const JSZip = (await import('jszip')).default;
|
|
63
|
+
const zip = await JSZip.loadAsync(file);
|
|
64
|
+
const fileEntries = Object.keys(zip.files).filter((path) => !zip.files[path].dir);
|
|
65
|
+
|
|
66
|
+
const hasCaseData = fileEntries.some((path) =>
|
|
67
|
+
CASE_EXPORT_DATA_FILE_REGEX.test(getLeafFileName(path))
|
|
68
|
+
);
|
|
69
|
+
const hasManifest = fileEntries.some(
|
|
70
|
+
(path) => getLeafFileName(path).toLowerCase() === FORENSIC_MANIFEST_FILE_NAME
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (hasCaseData || hasManifest) {
|
|
74
|
+
return 'case';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const hasConfirmationData = fileEntries.some((path) =>
|
|
78
|
+
CONFIRMATION_EXPORT_FILE_REGEX.test(getLeafFileName(path))
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (hasConfirmationData) {
|
|
82
|
+
return 'confirmation';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
36
91
|
/**
|
|
37
92
|
* Reset file input element
|
|
38
93
|
*/
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { User } from 'firebase/auth';
|
|
1
|
+
import type { User } from 'firebase/auth';
|
|
2
|
+
import type * as CaseExportActions from '../../actions/case-export';
|
|
2
3
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
3
4
|
import styles from './cases.module.css';
|
|
5
|
+
import { Toast } from '~/components/toast/toast';
|
|
4
6
|
import { CasesModal } from './cases-modal';
|
|
5
7
|
import { FilesModal } from '../files/files-modal';
|
|
6
|
-
import { CaseExport, ExportFormat } from '../case-export/case-export';
|
|
8
|
+
import { CaseExport, type ExportFormat } from '../case-export/case-export';
|
|
7
9
|
import { ImageUploadZone } from '../upload/image-upload-zone';
|
|
8
10
|
import { UserAuditViewer } from '~/components/audit/user-audit-viewer';
|
|
9
11
|
import {
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
getUserData
|
|
28
30
|
} from '~/utils/permissions';
|
|
29
31
|
import { getFileAnnotations } from '~/utils/data-operations';
|
|
30
|
-
import { FileData, CaseActionType } from '~/types';
|
|
32
|
+
import { type FileData, type CaseActionType } from '~/types';
|
|
31
33
|
|
|
32
34
|
interface CaseSidebarProps {
|
|
33
35
|
user: User;
|
|
@@ -57,7 +59,7 @@ interface CaseSidebarProps {
|
|
|
57
59
|
|
|
58
60
|
const SUCCESS_MESSAGE_TIMEOUT = 3000;
|
|
59
61
|
|
|
60
|
-
type CaseExportActionsModule = typeof
|
|
62
|
+
type CaseExportActionsModule = typeof CaseExportActions;
|
|
61
63
|
|
|
62
64
|
let caseExportActionsPromise: Promise<CaseExportActionsModule> | null = null;
|
|
63
65
|
|
|
@@ -102,7 +104,11 @@ export const CaseSidebar = ({
|
|
|
102
104
|
const [, setFileError] = useState('');
|
|
103
105
|
const [newCaseName, setNewCaseName] = useState('');
|
|
104
106
|
const [showCaseActions, setShowCaseActions] = useState(false);
|
|
107
|
+
const [showCaseManagement, setShowCaseManagement] = useState(false);
|
|
105
108
|
const [canCreateNewCase, setCanCreateNewCase] = useState(true);
|
|
109
|
+
const [isToastVisible, setIsToastVisible] = useState(false);
|
|
110
|
+
const [toastMessage, setToastMessage] = useState('');
|
|
111
|
+
const [toastType, setToastType] = useState<'success' | 'error' | 'warning'>('success');
|
|
106
112
|
const [canUploadNewFile, setCanUploadNewFile] = useState(true);
|
|
107
113
|
const [createCaseError, setCreateCaseError] = useState('');
|
|
108
114
|
const [uploadFileError, setUploadFileError] = useState('');
|
|
@@ -312,6 +318,24 @@ export const CaseSidebar = ({
|
|
|
312
318
|
isCancelled = true;
|
|
313
319
|
};
|
|
314
320
|
}, [currentCase, fileIdsKey, user, selectedFileId, confirmationSaveVersion, files.length, calculateCaseConfirmationStatus]);
|
|
321
|
+
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
if (error) {
|
|
324
|
+
setToastMessage(error);
|
|
325
|
+
setToastType('error');
|
|
326
|
+
setIsToastVisible(true);
|
|
327
|
+
}
|
|
328
|
+
}, [error]);
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (successAction) {
|
|
332
|
+
setToastMessage(`Case ${currentCase} ${successAction} successfully!`);
|
|
333
|
+
setToastType('success');
|
|
334
|
+
setIsToastVisible(true);
|
|
335
|
+
}
|
|
336
|
+
// currentCase intentionally omitted: we capture its value at the time successAction changes
|
|
337
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
338
|
+
}, [successAction]);
|
|
315
339
|
|
|
316
340
|
const handleCase = async () => {
|
|
317
341
|
setIsLoading(true);
|
|
@@ -335,6 +359,7 @@ export const CaseSidebar = ({
|
|
|
335
359
|
setFiles(files);
|
|
336
360
|
setCaseNumber('');
|
|
337
361
|
setSuccessAction('loaded');
|
|
362
|
+
setShowCaseManagement(false);
|
|
338
363
|
setTimeout(() => setSuccessAction(null), SUCCESS_MESSAGE_TIMEOUT);
|
|
339
364
|
return;
|
|
340
365
|
}
|
|
@@ -361,6 +386,7 @@ export const CaseSidebar = ({
|
|
|
361
386
|
setFiles([]);
|
|
362
387
|
setCaseNumber('');
|
|
363
388
|
setSuccessAction('created');
|
|
389
|
+
setShowCaseManagement(false);
|
|
364
390
|
setTimeout(() => setSuccessAction(null), SUCCESS_MESSAGE_TIMEOUT);
|
|
365
391
|
|
|
366
392
|
// Refresh permissions after successful case creation
|
|
@@ -563,53 +589,73 @@ const handleImageSelect = (file: FileData) => {
|
|
|
563
589
|
};
|
|
564
590
|
|
|
565
591
|
return (
|
|
592
|
+
<>
|
|
566
593
|
<div className={styles.caseSection}>
|
|
567
|
-
|
|
568
|
-
<h4>Case Management</h4>
|
|
569
|
-
{limitsDescription && (
|
|
570
|
-
<p className={styles.limitsInfo}>
|
|
571
|
-
{limitsDescription}
|
|
572
|
-
</p>
|
|
573
|
-
)}
|
|
574
|
-
<div className={`${styles.caseInput} mb-4`}>
|
|
575
|
-
<input
|
|
576
|
-
type="text"
|
|
577
|
-
value={caseNumber}
|
|
578
|
-
onChange={(e) => setCaseNumber(e.target.value)}
|
|
579
|
-
placeholder="Case #"
|
|
580
|
-
/>
|
|
581
|
-
</div>
|
|
594
|
+
{currentCase && !showCaseManagement ? (
|
|
582
595
|
<div className={`${styles.caseLoad} mb-4`}>
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
className={styles.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
596
|
+
<button
|
|
597
|
+
className={styles.switchCaseButton}
|
|
598
|
+
onClick={() => setShowCaseManagement(true)}
|
|
599
|
+
disabled={isUploading}
|
|
600
|
+
title={isUploading ? "Cannot switch cases while uploading files" : undefined}
|
|
601
|
+
>
|
|
602
|
+
Switch Case
|
|
603
|
+
</button>
|
|
604
|
+
</div>
|
|
605
|
+
) : (
|
|
606
|
+
<>
|
|
607
|
+
<h4>Case Management</h4>
|
|
608
|
+
{limitsDescription && (
|
|
609
|
+
<p className={styles.limitsInfo}>
|
|
610
|
+
{limitsDescription}
|
|
611
|
+
</p>
|
|
612
|
+
)}
|
|
613
|
+
<div className={`${styles.caseInput} mb-4`}>
|
|
614
|
+
<input
|
|
615
|
+
type="text"
|
|
616
|
+
value={caseNumber}
|
|
617
|
+
onChange={(e) => setCaseNumber(e.target.value)}
|
|
618
|
+
placeholder="Case #"
|
|
619
|
+
/>
|
|
620
|
+
</div>
|
|
621
|
+
<div className={`${styles.caseLoad} mb-4`}>
|
|
622
|
+
<button
|
|
623
|
+
onClick={handleCase}
|
|
624
|
+
disabled={isLoading || !caseNumber || permissionChecking || (isReadOnly && !!currentCase) || isUploading}
|
|
625
|
+
title={
|
|
626
|
+
isUploading
|
|
627
|
+
? "Cannot load/create cases while uploading files"
|
|
628
|
+
: (isReadOnly && currentCase)
|
|
629
|
+
? "Cannot load/create cases while reviewing a read-only case. Clear the current case first."
|
|
630
|
+
: (!canCreateNewCase ? createCaseError : undefined)
|
|
631
|
+
}
|
|
632
|
+
>
|
|
633
|
+
{isLoading ? 'Loading...' : permissionChecking ? 'Checking permissions...' : 'Load/Create Case'}
|
|
634
|
+
</button>
|
|
635
|
+
</div>
|
|
636
|
+
<div className={styles.caseInput}>
|
|
637
|
+
<button
|
|
638
|
+
onClick={() => setIsModalOpen(true)}
|
|
639
|
+
className={styles.listButton}
|
|
640
|
+
disabled={isUploading}
|
|
641
|
+
title={isUploading ? "Cannot list cases while uploading files" : undefined}
|
|
642
|
+
>
|
|
643
|
+
List All Cases
|
|
644
|
+
</button>
|
|
645
|
+
</div>
|
|
646
|
+
{currentCase && (
|
|
647
|
+
<div className="mb-4">
|
|
648
|
+
<button
|
|
649
|
+
className={styles.cancelSwitchButton}
|
|
650
|
+
onClick={() => setShowCaseManagement(false)}
|
|
651
|
+
disabled={isUploading}
|
|
652
|
+
>
|
|
653
|
+
Cancel
|
|
654
|
+
</button>
|
|
655
|
+
</div>
|
|
656
|
+
)}
|
|
657
|
+
</>
|
|
658
|
+
)}
|
|
613
659
|
<CasesModal
|
|
614
660
|
isOpen={isModalOpen}
|
|
615
661
|
onClose={() => setIsModalOpen(false)}
|
|
@@ -829,6 +875,16 @@ return (
|
|
|
829
875
|
/>
|
|
830
876
|
|
|
831
877
|
</div>
|
|
832
|
-
|
|
878
|
+
<Toast
|
|
879
|
+
message={toastMessage}
|
|
880
|
+
type={toastType}
|
|
881
|
+
isVisible={isToastVisible}
|
|
882
|
+
onClose={() => {
|
|
883
|
+
setIsToastVisible(false);
|
|
884
|
+
setError('');
|
|
885
|
+
setSuccessAction(null);
|
|
886
|
+
}}
|
|
887
|
+
/>
|
|
888
|
+
</>
|
|
833
889
|
);
|
|
834
890
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { User } from 'firebase/auth';
|
|
2
|
+
import type { User } from 'firebase/auth';
|
|
3
3
|
import { listCases } from '~/components/actions/case-manage';
|
|
4
4
|
import { getFileAnnotations } from '~/utils/data-operations';
|
|
5
5
|
import { fetchFiles } from '~/components/actions/image-manage';
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
.caseInput input:focus {
|
|
51
51
|
outline: none;
|
|
52
52
|
border-color: #0d6efd;
|
|
53
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
53
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/* Buttons */
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
background-color: #0d6efd;
|
|
127
127
|
color: white;
|
|
128
128
|
border: none;
|
|
129
|
-
border-radius: 6px;
|
|
129
|
+
border-radius: 6px;
|
|
130
130
|
font-weight: 500;
|
|
131
131
|
cursor: pointer;
|
|
132
132
|
transition: all 0.2s;
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
padding: 0.5rem;
|
|
175
175
|
border-radius: 4px;
|
|
176
176
|
color: #198754;
|
|
177
|
-
background-color: rgba(25,135,84,0.1);
|
|
177
|
+
background-color: rgba(25, 135, 84, 0.1);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/* Files Section */
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
margin-top: 2rem;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
.filesSection h4 {
|
|
185
|
+
.filesSection h4 {
|
|
186
186
|
margin-bottom: 1rem;
|
|
187
187
|
font-size: 1.3rem;
|
|
188
188
|
font-weight: 900;
|
|
@@ -274,8 +274,6 @@
|
|
|
274
274
|
background-color: #dee2e6;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
|
|
278
|
-
|
|
279
277
|
/* Files and Case Management */
|
|
280
278
|
|
|
281
279
|
.fileName {
|
|
@@ -285,7 +283,6 @@
|
|
|
285
283
|
white-space: nowrap;
|
|
286
284
|
}
|
|
287
285
|
|
|
288
|
-
|
|
289
286
|
/* Rename and Delete Cases */
|
|
290
287
|
|
|
291
288
|
.caseRename {
|
|
@@ -307,12 +304,12 @@
|
|
|
307
304
|
.caseRename input:focus {
|
|
308
305
|
outline: none;
|
|
309
306
|
border-color: #0d6efd;
|
|
310
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
307
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
311
308
|
}
|
|
312
309
|
|
|
313
310
|
/* Buttons */
|
|
314
311
|
.caseRename button {
|
|
315
|
-
|
|
312
|
+
width: 100%;
|
|
316
313
|
padding: 0.75rem;
|
|
317
314
|
background-color: #ffc107;
|
|
318
315
|
color: #000;
|
|
@@ -681,33 +678,119 @@
|
|
|
681
678
|
}
|
|
682
679
|
/* Confirmation Status Indicators */
|
|
683
680
|
.fileItemNotConfirmed {
|
|
684
|
-
background-color: color-mix(
|
|
681
|
+
background-color: color-mix(
|
|
682
|
+
in lab,
|
|
683
|
+
var(--warning) 15%,
|
|
684
|
+
var(--backgroundLight)
|
|
685
|
+
);
|
|
685
686
|
}
|
|
686
687
|
|
|
687
688
|
.fileItemNotConfirmed:hover {
|
|
688
|
-
background-color: color-mix(
|
|
689
|
+
background-color: color-mix(
|
|
690
|
+
in lab,
|
|
691
|
+
var(--warning) 20%,
|
|
692
|
+
var(--backgroundLight)
|
|
693
|
+
);
|
|
689
694
|
}
|
|
690
695
|
|
|
691
696
|
.fileItem.active.fileItemNotConfirmed {
|
|
692
|
-
background-color: color-mix(
|
|
697
|
+
background-color: color-mix(
|
|
698
|
+
in lab,
|
|
699
|
+
var(--warning) 15%,
|
|
700
|
+
var(--backgroundLight)
|
|
701
|
+
);
|
|
693
702
|
}
|
|
694
703
|
|
|
695
704
|
.fileItem.active.fileItemNotConfirmed:hover {
|
|
696
|
-
background-color: color-mix(
|
|
705
|
+
background-color: color-mix(
|
|
706
|
+
in lab,
|
|
707
|
+
var(--warning) 20%,
|
|
708
|
+
var(--backgroundLight)
|
|
709
|
+
);
|
|
697
710
|
}
|
|
698
711
|
|
|
699
712
|
.fileItemConfirmed {
|
|
700
|
-
background-color: color-mix(
|
|
713
|
+
background-color: color-mix(
|
|
714
|
+
in lab,
|
|
715
|
+
var(--success) 20%,
|
|
716
|
+
var(--backgroundLight)
|
|
717
|
+
);
|
|
701
718
|
}
|
|
702
719
|
|
|
703
720
|
.fileItemConfirmed:hover {
|
|
704
|
-
background-color: color-mix(
|
|
721
|
+
background-color: color-mix(
|
|
722
|
+
in lab,
|
|
723
|
+
var(--success) 28%,
|
|
724
|
+
var(--backgroundLight)
|
|
725
|
+
);
|
|
705
726
|
}
|
|
706
727
|
|
|
707
728
|
.fileItem.active.fileItemConfirmed {
|
|
708
|
-
background-color: color-mix(
|
|
729
|
+
background-color: color-mix(
|
|
730
|
+
in lab,
|
|
731
|
+
var(--success) 20%,
|
|
732
|
+
var(--backgroundLight)
|
|
733
|
+
);
|
|
709
734
|
}
|
|
710
735
|
|
|
711
736
|
.fileItem.active.fileItemConfirmed:hover {
|
|
712
|
-
background-color: color-mix(
|
|
713
|
-
|
|
737
|
+
background-color: color-mix(
|
|
738
|
+
in lab,
|
|
739
|
+
var(--success) 28%,
|
|
740
|
+
var(--backgroundLight)
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* Switch/Cancel Case buttons */
|
|
745
|
+
.switchCaseButton {
|
|
746
|
+
width: 100%;
|
|
747
|
+
padding: 0.75rem;
|
|
748
|
+
background-color: #198754;
|
|
749
|
+
color: white;
|
|
750
|
+
border: none;
|
|
751
|
+
border-radius: 6px;
|
|
752
|
+
font-weight: 500;
|
|
753
|
+
cursor: pointer;
|
|
754
|
+
transition: all 0.2s;
|
|
755
|
+
margin-top: 0.5rem;
|
|
756
|
+
box-sizing: border-box;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.switchCaseButton:hover:not(:disabled) {
|
|
760
|
+
background-color: #105032;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.switchCaseButton:disabled {
|
|
764
|
+
background-color: #e9ecef;
|
|
765
|
+
color: #6c757d;
|
|
766
|
+
cursor: not-allowed;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.cancelSwitchButton {
|
|
770
|
+
width: 100%;
|
|
771
|
+
padding: 0.75rem;
|
|
772
|
+
margin-top: 0.75rem;
|
|
773
|
+
background-color: #dc3545;
|
|
774
|
+
color: white;
|
|
775
|
+
border: none;
|
|
776
|
+
border-radius: 6px;
|
|
777
|
+
font-weight: 500;
|
|
778
|
+
cursor: pointer;
|
|
779
|
+
transition: all 0.2s;
|
|
780
|
+
box-sizing: border-box;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.cancelSwitchButton:hover:not(:disabled) {
|
|
784
|
+
background-color: #bd2130;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.cancelSwitchButton:disabled {
|
|
788
|
+
background-color: #e9ecef;
|
|
789
|
+
color: #6c757d;
|
|
790
|
+
cursor: not-allowed;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.cancelSwitchButton:disabled {
|
|
794
|
+
opacity: 0.6;
|
|
795
|
+
cursor: not-allowed;
|
|
796
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { useState, useContext, useEffect } from 'react';
|
|
2
3
|
import { AuthContext } from '~/contexts/auth.context';
|
|
3
4
|
import { deleteFile } from '~/components/actions/image-manage';
|
|
4
5
|
import { getFileAnnotations } from '~/utils/data-operations';
|
|
5
|
-
import { FileData } from '~/types';
|
|
6
|
+
import { type FileData } from '~/types';
|
|
6
7
|
import styles from './files-modal.module.css';
|
|
7
8
|
|
|
8
9
|
interface FilesModalProps {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { User } from 'firebase/auth';
|
|
2
|
+
import type { User } from 'firebase/auth';
|
|
3
3
|
import { ColorSelector } from '~/components/colors/colors';
|
|
4
4
|
import { NotesModal } from './notes-modal';
|
|
5
5
|
import { getNotes, saveNotes } from '~/components/actions/notes-manage';
|
|
6
|
-
import { AnnotationData } from '~/types/annotations';
|
|
6
|
+
import { type AnnotationData } from '~/types/annotations';
|
|
7
7
|
import { resolveEarliestAnnotationTimestamp } from '~/utils/annotation-timestamp';
|
|
8
|
-
import { auditService } from '~/services/audit
|
|
8
|
+
import { auditService } from '~/services/audit';
|
|
9
9
|
import styles from './notes.module.css';
|
|
10
10
|
|
|
11
11
|
interface NotesSidebarProps {
|
|
@@ -10,12 +10,12 @@ hr {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.section {
|
|
13
|
-
margin-bottom: 2rem;
|
|
13
|
+
margin-bottom: 2rem;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.sectionTitle {
|
|
17
17
|
font-size: 1.3rem;
|
|
18
|
-
font-weight: 600;
|
|
18
|
+
font-weight: 600;
|
|
19
19
|
color: #495057;
|
|
20
20
|
margin-bottom: 1rem;
|
|
21
21
|
}
|
|
@@ -50,7 +50,7 @@ input[type="color"]:focus,
|
|
|
50
50
|
textarea:focus {
|
|
51
51
|
outline: none;
|
|
52
52
|
border-color: #0d6efd;
|
|
53
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
53
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.caseNumbers {
|
|
@@ -74,10 +74,14 @@ textarea:focus {
|
|
|
74
74
|
box-sizing: border-box;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
.caseInput input:not(:disabled) {
|
|
78
|
+
background-color: #ffffff;
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
.caseInput input:focus {
|
|
78
82
|
outline: none;
|
|
79
83
|
border-color: #0d6efd;
|
|
80
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
84
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
.checkboxLabel {
|
|
@@ -108,6 +112,11 @@ textarea:focus {
|
|
|
108
112
|
transition: border-color 0.2s;
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
.classCharacteristics input:not(:disabled),
|
|
116
|
+
.classCharacteristics textarea:not(:disabled) {
|
|
117
|
+
background-color: #ffffff;
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
.classCharacteristics select,
|
|
112
121
|
.support select {
|
|
113
122
|
width: 100%;
|
|
@@ -124,7 +133,7 @@ textarea:focus {
|
|
|
124
133
|
.support select:focus {
|
|
125
134
|
outline: none;
|
|
126
135
|
border-color: #0d6efd;
|
|
127
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
136
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
.classCharacteristics textarea {
|
|
@@ -138,11 +147,14 @@ textarea:focus {
|
|
|
138
147
|
box-sizing: border-box;
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
.classCharacteristics input + textarea {
|
|
151
|
+
margin-top: 1rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
141
154
|
.classCharacteristics textarea:focus {
|
|
142
155
|
outline: none;
|
|
143
156
|
border-color: #0d6efd;
|
|
144
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
145
|
-
margin-top: 1rem;
|
|
157
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
146
158
|
resize: vertical;
|
|
147
159
|
}
|
|
148
160
|
|
|
@@ -188,13 +200,17 @@ textarea:focus {
|
|
|
188
200
|
box-sizing: border-box;
|
|
189
201
|
}
|
|
190
202
|
|
|
203
|
+
.indexing input[type="text"]:not(:disabled) {
|
|
204
|
+
background-color: #ffffff;
|
|
205
|
+
}
|
|
206
|
+
|
|
191
207
|
.indexing input[type="text"]:focus {
|
|
192
208
|
outline: none;
|
|
193
209
|
border-color: #0d6efd;
|
|
194
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
210
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
195
211
|
}
|
|
196
212
|
|
|
197
|
-
.confirmation {
|
|
213
|
+
.confirmation {
|
|
198
214
|
padding: 0.75rem;
|
|
199
215
|
background-color: #f8f9fa;
|
|
200
216
|
border-radius: 6px;
|
|
@@ -232,7 +248,7 @@ textarea:focus {
|
|
|
232
248
|
}
|
|
233
249
|
|
|
234
250
|
.notesButton:hover {
|
|
235
|
-
background-color: color-mix(in lab, var(--primary) 95%, transparent);
|
|
251
|
+
background-color: color-mix(in lab, var(--primary) 95%, transparent);
|
|
236
252
|
}
|
|
237
253
|
|
|
238
254
|
.modalOverlay {
|
|
@@ -355,6 +371,10 @@ textarea:focus {
|
|
|
355
371
|
}
|
|
356
372
|
|
|
357
373
|
@keyframes fadeIn {
|
|
358
|
-
from {
|
|
359
|
-
|
|
360
|
-
}
|
|
374
|
+
from {
|
|
375
|
+
opacity: 0;
|
|
376
|
+
}
|
|
377
|
+
to {
|
|
378
|
+
opacity: 1;
|
|
379
|
+
}
|
|
380
|
+
}
|