@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
@@ -0,0 +1,146 @@
1
+ import type { ReactNode } from 'react';
2
+ import styles from './notes.module.css';
3
+ import { CUSTOM, handleSelectWithCustom } from './class-details-shared';
4
+
5
+ interface BaseFieldProps {
6
+ label: string;
7
+ disabled: boolean;
8
+ fullWidth?: boolean;
9
+ }
10
+
11
+ interface TextFieldProps extends BaseFieldProps {
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ placeholder?: string;
15
+ type?: 'text' | 'number';
16
+ min?: number;
17
+ }
18
+
19
+ interface SelectFieldProps extends BaseFieldProps {
20
+ value: string;
21
+ onChange: (value: string) => void;
22
+ placeholder: string;
23
+ children: ReactNode;
24
+ }
25
+
26
+ interface SelectWithCustomFieldProps extends SelectFieldProps {
27
+ isCustom: boolean;
28
+ onCustomChange: (value: boolean) => void;
29
+ customPlaceholder: string;
30
+ }
31
+
32
+ interface CheckboxFieldProps {
33
+ label: string;
34
+ checked: boolean;
35
+ onChange: (checked: boolean) => void;
36
+ disabled: boolean;
37
+ }
38
+
39
+ const fieldClassName = (fullWidth = false): string =>
40
+ fullWidth ? `${styles.classDetailsField} ${styles.classDetailsFieldFull}` : styles.classDetailsField;
41
+
42
+ export const TextField = ({
43
+ label,
44
+ value,
45
+ onChange,
46
+ placeholder,
47
+ disabled,
48
+ fullWidth = false,
49
+ type = 'text',
50
+ min,
51
+ }: TextFieldProps) => (
52
+ <div className={fieldClassName(fullWidth)}>
53
+ <span className={styles.classDetailsLabel}>{label}</span>
54
+ <input
55
+ type={type}
56
+ min={min}
57
+ aria-label={label}
58
+ value={value}
59
+ onChange={(event) => onChange(event.target.value)}
60
+ className={styles.classDetailsInput}
61
+ disabled={disabled}
62
+ placeholder={placeholder}
63
+ />
64
+ </div>
65
+ );
66
+
67
+ export const SelectField = ({
68
+ label,
69
+ value,
70
+ onChange,
71
+ placeholder,
72
+ children,
73
+ disabled,
74
+ fullWidth = false,
75
+ }: SelectFieldProps) => (
76
+ <div className={fieldClassName(fullWidth)}>
77
+ <span className={styles.classDetailsLabel}>{label}</span>
78
+ <select
79
+ aria-label={label}
80
+ value={value}
81
+ onChange={(event) => onChange(event.target.value)}
82
+ className={styles.classDetailsInput}
83
+ disabled={disabled}
84
+ >
85
+ <option value="">{placeholder}</option>
86
+ {children}
87
+ </select>
88
+ </div>
89
+ );
90
+
91
+ export const SelectWithCustomField = ({
92
+ label,
93
+ value,
94
+ isCustom,
95
+ onChange,
96
+ onCustomChange,
97
+ placeholder,
98
+ customPlaceholder,
99
+ children,
100
+ disabled,
101
+ fullWidth = false,
102
+ }: SelectWithCustomFieldProps) => (
103
+ <div className={fieldClassName(fullWidth)}>
104
+ <span className={styles.classDetailsLabel}>{label}</span>
105
+ <select
106
+ aria-label={label}
107
+ value={isCustom ? CUSTOM : value}
108
+ onChange={(event) => handleSelectWithCustom(event.target.value, onChange, onCustomChange)}
109
+ className={styles.classDetailsInput}
110
+ disabled={disabled}
111
+ >
112
+ <option value="">{placeholder}</option>
113
+ {children}
114
+ <option value={CUSTOM}>Other / Custom...</option>
115
+ </select>
116
+ {isCustom && (
117
+ <input
118
+ type="text"
119
+ aria-label={label}
120
+ value={value}
121
+ onChange={(event) => onChange(event.target.value)}
122
+ className={styles.classDetailsInput}
123
+ disabled={disabled}
124
+ placeholder={customPlaceholder}
125
+ />
126
+ )}
127
+ </div>
128
+ );
129
+
130
+ export const CheckboxField = ({
131
+ label,
132
+ checked,
133
+ onChange,
134
+ disabled,
135
+ }: CheckboxFieldProps) => (
136
+ <label className={styles.classDetailsCheckboxLabel}>
137
+ <input
138
+ type="checkbox"
139
+ aria-label={label}
140
+ checked={checked}
141
+ onChange={(event) => onChange(event.target.checked)}
142
+ disabled={disabled}
143
+ />
144
+ <span>{label}</span>
145
+ </label>
146
+ );
@@ -0,0 +1,147 @@
1
+ import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
2
+ import type {
3
+ BulletAnnotationData,
4
+ CartridgeCaseAnnotationData,
5
+ ShotshellAnnotationData,
6
+ } from '~/types/annotations';
7
+ import {
8
+ type ClassType,
9
+ } from './class-details-shared';
10
+ import { BulletSection, CartridgeCaseSection, ShotshellSection } from './class-details-sections';
11
+ import { useClassDetailsState } from './use-class-details-state';
12
+ import styles from './notes.module.css';
13
+
14
+ interface ClassDetailsModalProps {
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ classType: ClassType | '';
18
+ bulletData?: BulletAnnotationData;
19
+ cartridgeCaseData?: CartridgeCaseAnnotationData;
20
+ shotshellData?: ShotshellAnnotationData;
21
+ onSave: (
22
+ bulletData: BulletAnnotationData | undefined,
23
+ cartridgeCaseData: CartridgeCaseAnnotationData | undefined,
24
+ shotshellData: ShotshellAnnotationData | undefined,
25
+ ) => void;
26
+ showNotification?: (message: string, type: 'success' | 'error' | 'warning') => void;
27
+ isReadOnly?: boolean;
28
+ }
29
+
30
+ const ClassDetailsModalContent = ({
31
+ isOpen,
32
+ onClose,
33
+ classType,
34
+ bulletData,
35
+ cartridgeCaseData,
36
+ shotshellData,
37
+ onSave,
38
+ showNotification,
39
+ isReadOnly = false,
40
+ }: ClassDetailsModalProps) => {
41
+ const {
42
+ bullet,
43
+ cartridgeCase,
44
+ shotshell,
45
+ isSaving,
46
+ setIsSaving,
47
+ buildSaveData,
48
+ } = useClassDetailsState({
49
+ bulletData,
50
+ cartridgeCaseData,
51
+ shotshellData,
52
+ });
53
+
54
+ const { requestClose, overlayProps, getCloseButtonProps } = useOverlayDismiss({ isOpen, onClose });
55
+
56
+ if (!isOpen) return null;
57
+
58
+ const showBullet = classType === 'Bullet' || classType === 'Other' || classType === '';
59
+ const showCartridge = classType === 'Cartridge Case' || classType === 'Other' || classType === '';
60
+ const showShotshell = classType === 'Shotshell' || classType === 'Other' || classType === '';
61
+ const showHeaders = classType === 'Other' || classType === '';
62
+
63
+ const handleSave = async () => {
64
+ setIsSaving(true);
65
+ try {
66
+ const {
67
+ bulletData: newBulletData,
68
+ cartridgeCaseData: newCartridgeCaseData,
69
+ shotshellData: newShotshellData,
70
+ } = buildSaveData({
71
+ showBullet,
72
+ showCartridge,
73
+ showShotshell,
74
+ });
75
+
76
+ await Promise.resolve(onSave(newBulletData, newCartridgeCaseData, newShotshellData));
77
+ showNotification?.('Class details saved.', 'success');
78
+ requestClose();
79
+ } catch (error) {
80
+ const message = error instanceof Error ? error.message : 'Failed to save class details.';
81
+ showNotification?.(message, 'error');
82
+ } finally {
83
+ setIsSaving(false);
84
+ }
85
+ };
86
+
87
+ return (
88
+ <div
89
+ className={styles.modalOverlay}
90
+ aria-label="Close class details dialog"
91
+ {...overlayProps}
92
+ >
93
+ <div className={`${styles.modal} ${styles.classDetailsModal}`}>
94
+ <button {...getCloseButtonProps({ ariaLabel: 'Close class details dialog' })}>×</button>
95
+ <h5 className={styles.modalTitle}>Class Characteristic Details</h5>
96
+ <div className={styles.classDetailsContent}>
97
+ {showBullet && (
98
+ <BulletSection
99
+ showHeader={showHeaders}
100
+ isReadOnly={isReadOnly}
101
+ bullet={bullet}
102
+ />
103
+ )}
104
+
105
+ {showCartridge && (
106
+ <CartridgeCaseSection
107
+ showHeader={showHeaders}
108
+ isReadOnly={isReadOnly}
109
+ cartridgeCase={cartridgeCase}
110
+ />
111
+ )}
112
+
113
+ {showShotshell && (
114
+ <ShotshellSection
115
+ showHeader={showHeaders}
116
+ isReadOnly={isReadOnly}
117
+ shotshell={shotshell}
118
+ />
119
+ )}
120
+ </div>
121
+ <div className={styles.modalButtons}>
122
+ <button
123
+ onClick={handleSave}
124
+ className={styles.saveButton}
125
+ disabled={isSaving || isReadOnly}
126
+ aria-busy={isSaving}
127
+ >
128
+ {isSaving ? 'Saving...' : 'Save'}
129
+ </button>
130
+ <button
131
+ onClick={requestClose}
132
+ className={styles.cancelButton}
133
+ disabled={isSaving}
134
+ >
135
+ Cancel
136
+ </button>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ );
141
+ };
142
+
143
+ export const ClassDetailsModal = (props: ClassDetailsModalProps) => {
144
+ if (!props.isOpen) return null;
145
+
146
+ return <ClassDetailsModalContent {...props} />;
147
+ };