@striae-org/striae 4.2.1 → 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/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/sidebar/cases/case-sidebar.tsx +49 -3
- package/app/components/sidebar/cases/cases-modal.module.css +312 -10
- package/app/components/sidebar/cases/cases-modal.tsx +690 -110
- package/app/components/sidebar/cases/cases.module.css +23 -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 +452 -145
- 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-editor-form.tsx +43 -5
- package/app/components/sidebar/notes/notes.module.css +236 -4
- package/app/components/sidebar/notes/use-class-details-state.ts +371 -0
- package/app/components/sidebar/sidebar-container.tsx +1 -0
- package/app/components/sidebar/sidebar.tsx +12 -1
- package/app/hooks/useCaseListPreferences.ts +99 -0
- package/app/hooks/useFileListPreferences.ts +106 -0
- package/app/routes/striae/striae.tsx +1 -0
- package/app/types/annotations.ts +48 -1
- package/app/utils/data/case-filters.ts +127 -0
- package/app/utils/data/confirmation-summary/summary-core.ts +18 -2
- package/app/utils/data/file-filters.ts +201 -0
- package/functions/api/image/[[path]].ts +4 -0
- package/package.json +3 -4
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/src/formats/format-striae.ts +84 -118
- package/workers/pdf-worker/src/pdf-worker.example.ts +28 -10
- package/workers/pdf-worker/src/report-layout.ts +227 -0
- package/workers/pdf-worker/src/report-types.ts +20 -0
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- 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
|
@@ -1,52 +1,5 @@
|
|
|
1
|
-
.overlay {
|
|
2
|
-
position: fixed;
|
|
3
|
-
inset: 0;
|
|
4
|
-
background: rgba(0, 0, 0, 0.45);
|
|
5
|
-
display: flex;
|
|
6
|
-
align-items: center;
|
|
7
|
-
justify-content: center;
|
|
8
|
-
z-index: 120;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
1
|
.modal {
|
|
12
|
-
position: relative;
|
|
13
2
|
width: min(560px, calc(100vw - 2rem));
|
|
14
|
-
background: #ffffff;
|
|
15
|
-
border-radius: 12px;
|
|
16
|
-
border: 1px solid #d9e0e7;
|
|
17
|
-
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
|
|
18
|
-
padding: 1.1rem;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.title {
|
|
22
|
-
margin: 0;
|
|
23
|
-
color: #212529;
|
|
24
|
-
font-size: 1.02rem;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.subtitle {
|
|
28
|
-
margin: 0.4rem 0 0.9rem;
|
|
29
|
-
color: #6c757d;
|
|
30
|
-
font-size: 0.85rem;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.warningPanel {
|
|
34
|
-
border: 1px solid color-mix(in lab, #dc3545 25%, transparent);
|
|
35
|
-
background: color-mix(in lab, #dc3545 7%, #ffffff);
|
|
36
|
-
border-radius: 10px;
|
|
37
|
-
padding: 0.75rem;
|
|
38
|
-
margin-bottom: 0.8rem;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.warningPanel p {
|
|
42
|
-
margin: 0;
|
|
43
|
-
color: #3f2a2e;
|
|
44
|
-
font-size: 0.86rem;
|
|
45
|
-
line-height: 1.35;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.warningPanel p + p {
|
|
49
|
-
margin-top: 0.45rem;
|
|
50
3
|
}
|
|
51
4
|
|
|
52
5
|
.reasonLabel {
|
|
@@ -74,37 +27,8 @@
|
|
|
74
27
|
box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2);
|
|
75
28
|
}
|
|
76
29
|
|
|
77
|
-
.actions {
|
|
78
|
-
display: flex;
|
|
79
|
-
justify-content: flex-end;
|
|
80
|
-
gap: 0.65rem;
|
|
81
|
-
margin-top: 1rem;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.cancelButton,
|
|
85
|
-
.confirmButton {
|
|
86
|
-
border: 1px solid transparent;
|
|
87
|
-
border-radius: 8px;
|
|
88
|
-
padding: 0.55rem 0.9rem;
|
|
89
|
-
font-size: 0.86rem;
|
|
90
|
-
font-weight: 500;
|
|
91
|
-
cursor: pointer;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.cancelButton {
|
|
95
|
-
background: #f3f4f6;
|
|
96
|
-
color: #3c4651;
|
|
97
|
-
border-color: #d6dce2;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
30
|
.confirmButton {
|
|
101
31
|
background: #dc3545;
|
|
102
32
|
color: #ffffff;
|
|
103
33
|
border-color: #c82333;
|
|
104
34
|
}
|
|
105
|
-
|
|
106
|
-
.cancelButton:disabled,
|
|
107
|
-
.confirmButton:disabled {
|
|
108
|
-
cursor: not-allowed;
|
|
109
|
-
opacity: 0.6;
|
|
110
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
|
+
import sharedStyles from './case-modal-shared.module.css';
|
|
3
4
|
import styles from './archive-case-modal.module.css';
|
|
4
5
|
|
|
5
6
|
interface ArchiveCaseModalProps {
|
|
@@ -65,18 +66,18 @@ export const ArchiveCaseModal = ({
|
|
|
65
66
|
|
|
66
67
|
return (
|
|
67
68
|
<div
|
|
68
|
-
className={
|
|
69
|
+
className={sharedStyles.overlay}
|
|
69
70
|
aria-label="Close archive case dialog"
|
|
70
71
|
{...overlayProps}
|
|
71
72
|
>
|
|
72
|
-
<div className={styles.modal} role="dialog" aria-modal="true" aria-label="Archive Case">
|
|
73
|
+
<div className={`${sharedStyles.modal} ${styles.modal}`} role="dialog" aria-modal="true" aria-label="Archive Case">
|
|
73
74
|
<button {...getCloseButtonProps({ ariaLabel: 'Close archive case dialog' })}>
|
|
74
75
|
×
|
|
75
76
|
</button>
|
|
76
|
-
<h3 className={
|
|
77
|
-
<p className={
|
|
77
|
+
<h3 className={sharedStyles.title}>Archive Case</h3>
|
|
78
|
+
<p className={sharedStyles.subtitle}>Case: {currentCase}</p>
|
|
78
79
|
|
|
79
|
-
<div className={
|
|
80
|
+
<div className={sharedStyles.warningPanel}>
|
|
80
81
|
<p>
|
|
81
82
|
Archiving a case permanently renders it read-only.
|
|
82
83
|
</p>
|
|
@@ -103,10 +104,10 @@ export const ArchiveCaseModal = ({
|
|
|
103
104
|
rows={3}
|
|
104
105
|
/>
|
|
105
106
|
|
|
106
|
-
<div className={
|
|
107
|
+
<div className={sharedStyles.actions}>
|
|
107
108
|
<button
|
|
108
109
|
type="button"
|
|
109
|
-
className={
|
|
110
|
+
className={sharedStyles.cancelButton}
|
|
110
111
|
onClick={requestClose}
|
|
111
112
|
disabled={isCloseBlocked}
|
|
112
113
|
>
|
|
@@ -114,7 +115,7 @@ export const ArchiveCaseModal = ({
|
|
|
114
115
|
</button>
|
|
115
116
|
<button
|
|
116
117
|
type="button"
|
|
117
|
-
className={styles.confirmButton}
|
|
118
|
+
className={`${sharedStyles.confirmButton} ${styles.confirmButton}`}
|
|
118
119
|
onClick={() => {
|
|
119
120
|
void handleSubmit();
|
|
120
121
|
}}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
.overlay {
|
|
2
|
+
position: fixed;
|
|
3
|
+
inset: 0;
|
|
4
|
+
background: rgba(0, 0, 0, 0.45);
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
z-index: 120;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.modal {
|
|
12
|
+
position: relative;
|
|
13
|
+
background: #ffffff;
|
|
14
|
+
border-radius: var(--spaceXS);
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
border: 1px solid #d9e0e7;
|
|
17
|
+
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
|
|
18
|
+
padding: 1.1rem;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.title {
|
|
22
|
+
margin: 0;
|
|
23
|
+
color: #212529;
|
|
24
|
+
font-size: 1.02rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.subtitle {
|
|
28
|
+
margin: 0.4rem 0 0.9rem;
|
|
29
|
+
color: #6c757d;
|
|
30
|
+
font-size: 0.85rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.input {
|
|
34
|
+
width: 100%;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
border: 1px solid #cdd5dd;
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
padding: 0.6rem 0.75rem;
|
|
39
|
+
font-size: 0.92rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.input:focus {
|
|
43
|
+
outline: none;
|
|
44
|
+
border-color: #1f6feb;
|
|
45
|
+
box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.warningPanel {
|
|
49
|
+
border: 1px solid color-mix(in lab, #dc3545 25%, transparent);
|
|
50
|
+
background: color-mix(in lab, #dc3545 7%, #ffffff);
|
|
51
|
+
border-radius: 10px;
|
|
52
|
+
padding: 0.75rem;
|
|
53
|
+
margin-bottom: 0.8rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.warningPanel p {
|
|
57
|
+
margin: 0;
|
|
58
|
+
color: #3f2a2e;
|
|
59
|
+
font-size: 0.86rem;
|
|
60
|
+
line-height: 1.35;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.warningPanel p + p {
|
|
64
|
+
margin-top: 0.45rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.actions {
|
|
68
|
+
display: flex;
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
gap: 0.65rem;
|
|
71
|
+
margin-top: 1rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.cancelButton,
|
|
75
|
+
.confirmButton {
|
|
76
|
+
border: 1px solid transparent;
|
|
77
|
+
border-radius: 8px;
|
|
78
|
+
padding: 0.55rem 0.9rem;
|
|
79
|
+
font-size: 0.86rem;
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.cancelButton {
|
|
85
|
+
background: #f3f4f6;
|
|
86
|
+
color: #3c4651;
|
|
87
|
+
border-color: #d6dce2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.cancelButton:disabled,
|
|
91
|
+
.confirmButton:disabled {
|
|
92
|
+
cursor: not-allowed;
|
|
93
|
+
opacity: 0.6;
|
|
94
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
2
|
+
import sharedStyles from './case-modal-shared.module.css';
|
|
3
|
+
import styles from './delete-case-modal.module.css';
|
|
4
|
+
|
|
5
|
+
interface DeleteCaseModalProps {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
currentCase: string;
|
|
8
|
+
isSubmitting?: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
onSubmit: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DeleteCaseModal = ({
|
|
14
|
+
isOpen,
|
|
15
|
+
currentCase,
|
|
16
|
+
isSubmitting = false,
|
|
17
|
+
onClose,
|
|
18
|
+
onSubmit,
|
|
19
|
+
}: DeleteCaseModalProps) => {
|
|
20
|
+
const isCloseBlocked = isSubmitting;
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
requestClose,
|
|
24
|
+
overlayProps,
|
|
25
|
+
getCloseButtonProps,
|
|
26
|
+
} = useOverlayDismiss({
|
|
27
|
+
isOpen,
|
|
28
|
+
onClose,
|
|
29
|
+
canDismiss: !isCloseBlocked,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!isOpen) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={sharedStyles.overlay}
|
|
39
|
+
aria-label="Close delete case dialog"
|
|
40
|
+
{...overlayProps}
|
|
41
|
+
>
|
|
42
|
+
<div className={`${sharedStyles.modal} ${styles.modal}`} role="dialog" aria-modal="true" aria-label="Delete Case">
|
|
43
|
+
<button {...getCloseButtonProps({ ariaLabel: 'Close delete case dialog' })}>
|
|
44
|
+
×
|
|
45
|
+
</button>
|
|
46
|
+
|
|
47
|
+
<h3 className={sharedStyles.title}>Delete Case</h3>
|
|
48
|
+
<p className={sharedStyles.subtitle}>Case: {currentCase}</p>
|
|
49
|
+
|
|
50
|
+
<div className={sharedStyles.warningPanel}>
|
|
51
|
+
<p>This action permanently deletes the case and all associated files.</p>
|
|
52
|
+
<p>This operation cannot be undone.</p>
|
|
53
|
+
<p>Any image assets that are already missing will be skipped automatically.</p>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div className={sharedStyles.actions}>
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
className={sharedStyles.cancelButton}
|
|
60
|
+
onClick={requestClose}
|
|
61
|
+
disabled={isCloseBlocked}
|
|
62
|
+
>
|
|
63
|
+
Cancel
|
|
64
|
+
</button>
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
className={`${sharedStyles.confirmButton} ${styles.confirmButton}`}
|
|
68
|
+
onClick={() => {
|
|
69
|
+
void onSubmit();
|
|
70
|
+
}}
|
|
71
|
+
disabled={isSubmitting}
|
|
72
|
+
>
|
|
73
|
+
{isSubmitting ? 'Deleting...' : 'Confirm Delete'}
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
position: relative;
|
|
13
13
|
width: min(460px, calc(100vw - 2rem));
|
|
14
14
|
background: #ffffff;
|
|
15
|
-
border-radius:
|
|
15
|
+
border-radius: var(--spaceXS);
|
|
16
|
+
overflow: hidden;
|
|
16
17
|
border: 1px solid #d9e0e7;
|
|
17
18
|
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
|
|
18
19
|
padding: 1.1rem;
|
|
@@ -1,71 +1,5 @@
|
|
|
1
|
-
.overlay {
|
|
2
|
-
position: fixed;
|
|
3
|
-
inset: 0;
|
|
4
|
-
background: rgba(0, 0, 0, 0.45);
|
|
5
|
-
display: flex;
|
|
6
|
-
align-items: center;
|
|
7
|
-
justify-content: center;
|
|
8
|
-
z-index: 120;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
1
|
.modal {
|
|
12
|
-
position: relative;
|
|
13
2
|
width: min(460px, calc(100vw - 2rem));
|
|
14
|
-
background: #ffffff;
|
|
15
|
-
border-radius: 12px;
|
|
16
|
-
border: 1px solid #d9e0e7;
|
|
17
|
-
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
|
|
18
|
-
padding: 1.1rem;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.title {
|
|
22
|
-
margin: 0;
|
|
23
|
-
color: #212529;
|
|
24
|
-
font-size: 1.02rem;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.subtitle {
|
|
28
|
-
margin: 0.4rem 0 0.9rem;
|
|
29
|
-
color: #6c757d;
|
|
30
|
-
font-size: 0.85rem;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.input {
|
|
34
|
-
width: 100%;
|
|
35
|
-
box-sizing: border-box;
|
|
36
|
-
border: 1px solid #cdd5dd;
|
|
37
|
-
border-radius: 8px;
|
|
38
|
-
padding: 0.6rem 0.75rem;
|
|
39
|
-
font-size: 0.92rem;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.input:focus {
|
|
43
|
-
outline: none;
|
|
44
|
-
border-color: #1f6feb;
|
|
45
|
-
box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.actions {
|
|
49
|
-
display: flex;
|
|
50
|
-
justify-content: flex-end;
|
|
51
|
-
gap: 0.65rem;
|
|
52
|
-
margin-top: 1rem;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.cancelButton,
|
|
56
|
-
.confirmButton {
|
|
57
|
-
border: 1px solid transparent;
|
|
58
|
-
border-radius: 8px;
|
|
59
|
-
padding: 0.55rem 0.9rem;
|
|
60
|
-
font-size: 0.86rem;
|
|
61
|
-
font-weight: 500;
|
|
62
|
-
cursor: pointer;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.cancelButton {
|
|
66
|
-
background: #f3f4f6;
|
|
67
|
-
color: #3c4651;
|
|
68
|
-
border-color: #d6dce2;
|
|
69
3
|
}
|
|
70
4
|
|
|
71
5
|
.confirmButton {
|
|
@@ -73,9 +7,3 @@
|
|
|
73
7
|
color: #3f2f00;
|
|
74
8
|
border-color: #e8b103;
|
|
75
9
|
}
|
|
76
|
-
|
|
77
|
-
.cancelButton:disabled,
|
|
78
|
-
.confirmButton:disabled {
|
|
79
|
-
cursor: not-allowed;
|
|
80
|
-
opacity: 0.6;
|
|
81
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
|
+
import sharedStyles from './case-modal-shared.module.css';
|
|
3
4
|
import styles from './rename-case-modal.module.css';
|
|
4
5
|
|
|
5
6
|
interface RenameCaseModalProps {
|
|
@@ -59,22 +60,22 @@ export const RenameCaseModal = ({
|
|
|
59
60
|
|
|
60
61
|
return (
|
|
61
62
|
<div
|
|
62
|
-
className={
|
|
63
|
+
className={sharedStyles.overlay}
|
|
63
64
|
aria-label="Close rename case dialog"
|
|
64
65
|
{...overlayProps}
|
|
65
66
|
>
|
|
66
|
-
<div className={styles.modal} role="dialog" aria-modal="true" aria-label="Rename Case">
|
|
67
|
+
<div className={`${sharedStyles.modal} ${styles.modal}`} role="dialog" aria-modal="true" aria-label="Rename Case">
|
|
67
68
|
<button {...getCloseButtonProps({ ariaLabel: 'Close rename case dialog' })}>
|
|
68
69
|
×
|
|
69
70
|
</button>
|
|
70
|
-
<h3 className={
|
|
71
|
-
<p className={
|
|
71
|
+
<h3 className={sharedStyles.title}>Rename Case</h3>
|
|
72
|
+
<p className={sharedStyles.subtitle}>Current case: {currentCase}</p>
|
|
72
73
|
<input
|
|
73
74
|
ref={inputRef}
|
|
74
75
|
type="text"
|
|
75
76
|
value={newCaseName}
|
|
76
77
|
onChange={(event) => setNewCaseName(event.target.value)}
|
|
77
|
-
className={
|
|
78
|
+
className={sharedStyles.input}
|
|
78
79
|
placeholder="New case number"
|
|
79
80
|
disabled={isSubmitting}
|
|
80
81
|
onKeyDown={(event) => {
|
|
@@ -83,10 +84,10 @@ export const RenameCaseModal = ({
|
|
|
83
84
|
}
|
|
84
85
|
}}
|
|
85
86
|
/>
|
|
86
|
-
<div className={
|
|
87
|
+
<div className={sharedStyles.actions}>
|
|
87
88
|
<button
|
|
88
89
|
type="button"
|
|
89
|
-
className={
|
|
90
|
+
className={sharedStyles.cancelButton}
|
|
90
91
|
onClick={requestClose}
|
|
91
92
|
disabled={isCloseBlocked}
|
|
92
93
|
>
|
|
@@ -94,7 +95,7 @@ export const RenameCaseModal = ({
|
|
|
94
95
|
</button>
|
|
95
96
|
<button
|
|
96
97
|
type="button"
|
|
97
|
-
className={styles.confirmButton}
|
|
98
|
+
className={`${sharedStyles.confirmButton} ${styles.confirmButton}`}
|
|
98
99
|
onClick={() => void handleSubmit()}
|
|
99
100
|
disabled={isSubmitting || !newCaseName.trim()}
|
|
100
101
|
>
|
|
@@ -1,8 +1,10 @@
|
|
|
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,
|
|
@@ -25,12 +27,14 @@ interface CaseSidebarProps {
|
|
|
25
27
|
setFiles: React.Dispatch<React.SetStateAction<FileData[]>>;
|
|
26
28
|
currentCase: string | null;
|
|
27
29
|
isReadOnly?: boolean;
|
|
30
|
+
isArchivedCase?: boolean;
|
|
28
31
|
isConfirmed?: boolean;
|
|
29
32
|
confirmationSaveVersion?: number;
|
|
30
33
|
selectedFileId?: string;
|
|
31
34
|
isUploading?: boolean;
|
|
32
35
|
onUploadStatusChange?: (isUploading: boolean) => void;
|
|
33
36
|
onUploadComplete?: (result: { successCount: number; failedFiles: string[] }) => void;
|
|
37
|
+
onExportNotification?: (message: string, type: 'success' | 'error') => void;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export const CaseSidebar = ({
|
|
@@ -44,18 +48,21 @@ export const CaseSidebar = ({
|
|
|
44
48
|
setFiles,
|
|
45
49
|
currentCase,
|
|
46
50
|
isReadOnly = false,
|
|
51
|
+
isArchivedCase = false,
|
|
47
52
|
isConfirmed = false,
|
|
48
53
|
confirmationSaveVersion = 0,
|
|
49
54
|
selectedFileId,
|
|
50
55
|
isUploading = false,
|
|
51
56
|
onUploadStatusChange,
|
|
52
|
-
onUploadComplete
|
|
57
|
+
onUploadComplete,
|
|
58
|
+
onExportNotification
|
|
53
59
|
}: CaseSidebarProps) => {
|
|
54
60
|
|
|
55
61
|
const [, setFileError] = useState('');
|
|
56
62
|
const [canUploadNewFile, setCanUploadNewFile] = useState(true);
|
|
57
63
|
const [uploadFileError, setUploadFileError] = useState('');
|
|
58
64
|
const [isFilesModalOpen, setIsFilesModalOpen] = useState(false);
|
|
65
|
+
const [isExportingConfirmations, setIsExportingConfirmations] = useState(false);
|
|
59
66
|
const [deletingFileId, setDeletingFileId] = useState<string | null>(null);
|
|
60
67
|
const [fileConfirmationStatus, setFileConfirmationStatus] = useState<{
|
|
61
68
|
[fileId: string]: { includeConfirmation: boolean; isConfirmed: boolean }
|
|
@@ -223,6 +230,26 @@ const handleImageSelect = (file: FileData) => {
|
|
|
223
230
|
setImageLoaded(false);
|
|
224
231
|
};
|
|
225
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
|
+
|
|
226
253
|
const selectedFileConfirmationState = selectedFileId
|
|
227
254
|
? fileConfirmationStatus[selectedFileId]
|
|
228
255
|
: undefined;
|
|
@@ -253,6 +280,14 @@ const handleImageSelect = (file: FileData) => {
|
|
|
253
280
|
? 'Select an image first'
|
|
254
281
|
: undefined;
|
|
255
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
|
+
|
|
256
291
|
return (
|
|
257
292
|
<>
|
|
258
293
|
<div className={styles.caseSection}>
|
|
@@ -371,14 +406,25 @@ return (
|
|
|
371
406
|
)}
|
|
372
407
|
</div>
|
|
373
408
|
<div className={styles.sidebarToggle}>
|
|
374
|
-
|
|
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
|
|
375
420
|
onClick={onNotesClick}
|
|
376
421
|
disabled={isImageNotesDisabled}
|
|
377
422
|
title={imageNotesTitle}
|
|
378
423
|
>
|
|
379
424
|
Image Notes
|
|
380
425
|
</button>
|
|
381
|
-
|
|
426
|
+
)}
|
|
427
|
+
</div>
|
|
382
428
|
</div>
|
|
383
429
|
</>
|
|
384
430
|
);
|