@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.
Files changed (47) hide show
  1. package/app/components/navbar/case-modals/archive-case-modal.module.css +0 -76
  2. package/app/components/navbar/case-modals/archive-case-modal.tsx +9 -8
  3. package/app/components/navbar/case-modals/case-modal-shared.module.css +94 -0
  4. package/app/components/navbar/case-modals/delete-case-modal.module.css +9 -0
  5. package/app/components/navbar/case-modals/delete-case-modal.tsx +79 -0
  6. package/app/components/navbar/case-modals/open-case-modal.module.css +2 -1
  7. package/app/components/navbar/case-modals/rename-case-modal.module.css +0 -72
  8. package/app/components/navbar/case-modals/rename-case-modal.tsx +9 -8
  9. package/app/components/sidebar/cases/case-sidebar.tsx +49 -3
  10. package/app/components/sidebar/cases/cases-modal.module.css +312 -10
  11. package/app/components/sidebar/cases/cases-modal.tsx +690 -110
  12. package/app/components/sidebar/cases/cases.module.css +23 -0
  13. package/app/components/sidebar/files/delete-files-modal.module.css +26 -0
  14. package/app/components/sidebar/files/delete-files-modal.tsx +94 -0
  15. package/app/components/sidebar/files/files-modal.module.css +285 -44
  16. package/app/components/sidebar/files/files-modal.tsx +452 -145
  17. package/app/components/sidebar/notes/class-details-fields.tsx +146 -0
  18. package/app/components/sidebar/notes/class-details-modal.tsx +147 -0
  19. package/app/components/sidebar/notes/class-details-sections.tsx +561 -0
  20. package/app/components/sidebar/notes/class-details-shared.ts +239 -0
  21. package/app/components/sidebar/notes/notes-editor-form.tsx +43 -5
  22. package/app/components/sidebar/notes/notes.module.css +236 -4
  23. package/app/components/sidebar/notes/use-class-details-state.ts +371 -0
  24. package/app/components/sidebar/sidebar-container.tsx +1 -0
  25. package/app/components/sidebar/sidebar.tsx +12 -1
  26. package/app/hooks/useCaseListPreferences.ts +99 -0
  27. package/app/hooks/useFileListPreferences.ts +106 -0
  28. package/app/routes/striae/striae.tsx +1 -0
  29. package/app/types/annotations.ts +48 -1
  30. package/app/utils/data/case-filters.ts +127 -0
  31. package/app/utils/data/confirmation-summary/summary-core.ts +18 -2
  32. package/app/utils/data/file-filters.ts +201 -0
  33. package/functions/api/image/[[path]].ts +4 -0
  34. package/package.json +3 -4
  35. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  36. package/workers/data-worker/wrangler.jsonc.example +1 -1
  37. package/workers/image-worker/wrangler.jsonc.example +1 -1
  38. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  39. package/workers/pdf-worker/src/formats/format-striae.ts +84 -118
  40. package/workers/pdf-worker/src/pdf-worker.example.ts +28 -10
  41. package/workers/pdf-worker/src/report-layout.ts +227 -0
  42. package/workers/pdf-worker/src/report-types.ts +20 -0
  43. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  44. package/workers/user-worker/wrangler.jsonc.example +1 -1
  45. package/wrangler.toml.example +1 -1
  46. package/workers/pdf-worker/src/assets/icon-256.png +0 -0
  47. /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={styles.overlay}
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={styles.title}>Archive Case</h3>
77
- <p className={styles.subtitle}>Case: {currentCase}</p>
77
+ <h3 className={sharedStyles.title}>Archive Case</h3>
78
+ <p className={sharedStyles.subtitle}>Case: {currentCase}</p>
78
79
 
79
- <div className={styles.warningPanel}>
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={styles.actions}>
107
+ <div className={sharedStyles.actions}>
107
108
  <button
108
109
  type="button"
109
- className={styles.cancelButton}
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,9 @@
1
+ .modal {
2
+ width: min(560px, calc(100vw - 2rem));
3
+ }
4
+
5
+ .confirmButton {
6
+ background: #dc3545;
7
+ color: #ffffff;
8
+ border-color: #c82333;
9
+ }
@@ -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: 12px;
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={styles.overlay}
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={styles.title}>Rename Case</h3>
71
- <p className={styles.subtitle}>Current case: {currentCase}</p>
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={styles.input}
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={styles.actions}>
87
+ <div className={sharedStyles.actions}>
87
88
  <button
88
89
  type="button"
89
- className={styles.cancelButton}
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
- <button
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
- </div>
426
+ )}
427
+ </div>
382
428
  </div>
383
429
  </>
384
430
  );