@striae-org/striae 4.2.1 → 4.3.1
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-import/confirmation-import.ts +20 -1
- package/app/components/actions/case-import/orchestrator.ts +3 -0
- package/app/components/actions/case-manage.ts +5 -1
- package/app/components/actions/confirm-export.ts +12 -3
- package/app/components/audit/viewer/audit-entries-list.tsx +20 -2
- package/app/components/audit/viewer/use-audit-viewer-export.ts +2 -2
- package/app/components/audit/viewer/use-audit-viewer-filters.ts +11 -1
- package/app/components/canvas/canvas.tsx +2 -1
- 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/navbar/navbar.module.css +11 -0
- package/app/components/navbar/navbar.tsx +38 -19
- package/app/components/sidebar/case-import/hooks/useImportExecution.ts +2 -0
- package/app/components/sidebar/cases/case-sidebar.tsx +27 -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 +2 -0
- package/app/components/sidebar/sidebar.tsx +8 -1
- package/app/hooks/useCaseListPreferences.ts +99 -0
- package/app/hooks/useFileListPreferences.ts +106 -0
- package/app/routes/striae/striae.tsx +45 -1
- package/app/services/audit/audit-export-csv.ts +4 -2
- package/app/services/audit/audit-export-report.ts +36 -4
- package/app/services/audit/audit.service.ts +2 -0
- package/app/services/audit/builders/audit-entry-builder.ts +1 -0
- package/app/services/audit/builders/audit-event-builders-workflow.ts +8 -2
- package/app/types/annotations.ts +48 -1
- package/app/types/audit.ts +1 -0
- 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/app/utils/forensics/confirmation-signature.ts +20 -5
- 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/src/signing-payload-utils.ts +5 -0
- 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
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
BulletAnnotationData,
|
|
4
|
+
CartridgeCaseAnnotationData,
|
|
5
|
+
ShotshellAnnotationData,
|
|
6
|
+
} from '~/types/annotations';
|
|
7
|
+
import {
|
|
8
|
+
ALL_CALIBERS,
|
|
9
|
+
BULLET_BARREL_TYPE_OPTIONS,
|
|
10
|
+
BULLET_CORE_METAL_OPTIONS,
|
|
11
|
+
BULLET_JACKET_METAL_OPTIONS,
|
|
12
|
+
BULLET_TYPE_OPTIONS,
|
|
13
|
+
CARTRIDGE_APERTURE_SHAPE_OPTIONS,
|
|
14
|
+
CARTRIDGE_FPI_SHAPE_OPTIONS,
|
|
15
|
+
CARTRIDGE_METAL_OPTIONS,
|
|
16
|
+
CARTRIDGE_PRIMER_TYPE_OPTIONS,
|
|
17
|
+
SHOTSHELL_GAUGES,
|
|
18
|
+
calculateBulletDiameter,
|
|
19
|
+
formatCalculatedDiameter,
|
|
20
|
+
isCustomValue,
|
|
21
|
+
} from './class-details-shared';
|
|
22
|
+
|
|
23
|
+
interface UseClassDetailsStateParams {
|
|
24
|
+
bulletData?: BulletAnnotationData;
|
|
25
|
+
cartridgeCaseData?: CartridgeCaseAnnotationData;
|
|
26
|
+
shotshellData?: ShotshellAnnotationData;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface BuildSaveDataParams {
|
|
30
|
+
showBullet: boolean;
|
|
31
|
+
showCartridge: boolean;
|
|
32
|
+
showShotshell: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface BuildSaveDataResult {
|
|
36
|
+
bulletData: BulletAnnotationData | undefined;
|
|
37
|
+
cartridgeCaseData: CartridgeCaseAnnotationData | undefined;
|
|
38
|
+
shotshellData: ShotshellAnnotationData | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface BulletDetailsState {
|
|
42
|
+
caliber: string;
|
|
43
|
+
caliberIsCustom: boolean;
|
|
44
|
+
mass: string;
|
|
45
|
+
diameter: string;
|
|
46
|
+
lgNumber: string;
|
|
47
|
+
lgDirection: string;
|
|
48
|
+
lWidths: string[];
|
|
49
|
+
gWidths: string[];
|
|
50
|
+
jacketMetal: string;
|
|
51
|
+
jacketMetalIsCustom: boolean;
|
|
52
|
+
coreMetal: string;
|
|
53
|
+
coreMetalIsCustom: boolean;
|
|
54
|
+
bulletType: string;
|
|
55
|
+
bulletTypeIsCustom: boolean;
|
|
56
|
+
barrelType: string;
|
|
57
|
+
barrelTypeIsCustom: boolean;
|
|
58
|
+
lgCount: number;
|
|
59
|
+
calculatedDiameter: number | null;
|
|
60
|
+
setCaliber: (value: string) => void;
|
|
61
|
+
setCaliberIsCustom: (value: boolean) => void;
|
|
62
|
+
setMass: (value: string) => void;
|
|
63
|
+
setDiameter: (value: string) => void;
|
|
64
|
+
setLgNumber: (value: string) => void;
|
|
65
|
+
setLgDirection: (value: string) => void;
|
|
66
|
+
updateLWidth: (index: number, value: string) => void;
|
|
67
|
+
updateGWidth: (index: number, value: string) => void;
|
|
68
|
+
setJacketMetal: (value: string) => void;
|
|
69
|
+
setJacketMetalIsCustom: (value: boolean) => void;
|
|
70
|
+
setCoreMetal: (value: string) => void;
|
|
71
|
+
setCoreMetalIsCustom: (value: boolean) => void;
|
|
72
|
+
setBulletType: (value: string) => void;
|
|
73
|
+
setBulletTypeIsCustom: (value: boolean) => void;
|
|
74
|
+
setBarrelType: (value: string) => void;
|
|
75
|
+
setBarrelTypeIsCustom: (value: boolean) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CartridgeCaseDetailsState {
|
|
79
|
+
caliber: string;
|
|
80
|
+
caliberIsCustom: boolean;
|
|
81
|
+
brand: string;
|
|
82
|
+
metal: string;
|
|
83
|
+
metalIsCustom: boolean;
|
|
84
|
+
primerType: string;
|
|
85
|
+
primerTypeIsCustom: boolean;
|
|
86
|
+
fpiShape: string;
|
|
87
|
+
fpiShapeIsCustom: boolean;
|
|
88
|
+
apertureShape: string;
|
|
89
|
+
apertureShapeIsCustom: boolean;
|
|
90
|
+
hasFpDrag: boolean;
|
|
91
|
+
hasExtractorMarks: boolean;
|
|
92
|
+
hasEjectorMarks: boolean;
|
|
93
|
+
hasChamberMarks: boolean;
|
|
94
|
+
hasMagazineLipMarks: boolean;
|
|
95
|
+
hasPrimerShear: boolean;
|
|
96
|
+
hasEjectionPortMarks: boolean;
|
|
97
|
+
setCaliber: (value: string) => void;
|
|
98
|
+
setCaliberIsCustom: (value: boolean) => void;
|
|
99
|
+
setBrand: (value: string) => void;
|
|
100
|
+
setMetal: (value: string) => void;
|
|
101
|
+
setMetalIsCustom: (value: boolean) => void;
|
|
102
|
+
setPrimerType: (value: string) => void;
|
|
103
|
+
setPrimerTypeIsCustom: (value: boolean) => void;
|
|
104
|
+
setFpiShape: (value: string) => void;
|
|
105
|
+
setFpiShapeIsCustom: (value: boolean) => void;
|
|
106
|
+
setApertureShape: (value: string) => void;
|
|
107
|
+
setApertureShapeIsCustom: (value: boolean) => void;
|
|
108
|
+
setHasFpDrag: (value: boolean) => void;
|
|
109
|
+
setHasExtractorMarks: (value: boolean) => void;
|
|
110
|
+
setHasEjectorMarks: (value: boolean) => void;
|
|
111
|
+
setHasChamberMarks: (value: boolean) => void;
|
|
112
|
+
setHasMagazineLipMarks: (value: boolean) => void;
|
|
113
|
+
setHasPrimerShear: (value: boolean) => void;
|
|
114
|
+
setHasEjectionPortMarks: (value: boolean) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ShotshellDetailsState {
|
|
118
|
+
gauge: string;
|
|
119
|
+
gaugeIsCustom: boolean;
|
|
120
|
+
shotSize: string;
|
|
121
|
+
metal: string;
|
|
122
|
+
metalIsCustom: boolean;
|
|
123
|
+
brand: string;
|
|
124
|
+
fpiShape: string;
|
|
125
|
+
fpiShapeIsCustom: boolean;
|
|
126
|
+
hasExtractorMarks: boolean;
|
|
127
|
+
hasEjectorMarks: boolean;
|
|
128
|
+
hasChamberMarks: boolean;
|
|
129
|
+
setGauge: (value: string) => void;
|
|
130
|
+
setGaugeIsCustom: (value: boolean) => void;
|
|
131
|
+
setShotSize: (value: string) => void;
|
|
132
|
+
setMetal: (value: string) => void;
|
|
133
|
+
setMetalIsCustom: (value: boolean) => void;
|
|
134
|
+
setBrand: (value: string) => void;
|
|
135
|
+
setFpiShape: (value: string) => void;
|
|
136
|
+
setFpiShapeIsCustom: (value: boolean) => void;
|
|
137
|
+
setHasExtractorMarks: (value: boolean) => void;
|
|
138
|
+
setHasEjectorMarks: (value: boolean) => void;
|
|
139
|
+
setHasChamberMarks: (value: boolean) => void;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const useClassDetailsState = ({
|
|
143
|
+
bulletData,
|
|
144
|
+
cartridgeCaseData,
|
|
145
|
+
shotshellData,
|
|
146
|
+
}: UseClassDetailsStateParams) => {
|
|
147
|
+
const [bCaliber, setBCaliber] = useState(() => bulletData?.caliber || '');
|
|
148
|
+
const [bCaliberIsCustom, setBCaliberIsCustom] = useState(() => isCustomValue(bulletData?.caliber, ALL_CALIBERS));
|
|
149
|
+
const [bMass, setBMass] = useState(() => bulletData?.mass || '');
|
|
150
|
+
const [bDiameter, setBDiameter] = useState(() => bulletData?.diameter || '');
|
|
151
|
+
const [bLgNumber, setBLgNumber] = useState(() => bulletData?.lgNumber !== undefined ? String(bulletData.lgNumber) : '');
|
|
152
|
+
const [bLgDirection, setBLgDirection] = useState(() => bulletData?.lgDirection || '');
|
|
153
|
+
const [bLWidths, setBLWidths] = useState<string[]>(() => bulletData?.lWidths || []);
|
|
154
|
+
const [bGWidths, setBGWidths] = useState<string[]>(() => bulletData?.gWidths || []);
|
|
155
|
+
const [bJacketMetal, setBJacketMetal] = useState(() => bulletData?.jacketMetal || '');
|
|
156
|
+
const [bJacketMetalIsCustom, setBJacketMetalIsCustom] = useState(() => isCustomValue(bulletData?.jacketMetal, BULLET_JACKET_METAL_OPTIONS));
|
|
157
|
+
const [bCoreMetal, setBCoreMetal] = useState(() => bulletData?.coreMetal || '');
|
|
158
|
+
const [bCoreMetalIsCustom, setBCoreMetalIsCustom] = useState(() => isCustomValue(bulletData?.coreMetal, BULLET_CORE_METAL_OPTIONS));
|
|
159
|
+
const [bBulletType, setBBulletType] = useState(() => bulletData?.bulletType || '');
|
|
160
|
+
const [bBulletTypeIsCustom, setBBulletTypeIsCustom] = useState(() => isCustomValue(bulletData?.bulletType, BULLET_TYPE_OPTIONS));
|
|
161
|
+
const [bBarrelType, setBBarrelType] = useState(() => bulletData?.barrelType || '');
|
|
162
|
+
const [bBarrelTypeIsCustom, setBBarrelTypeIsCustom] = useState(() => isCustomValue(bulletData?.barrelType, BULLET_BARREL_TYPE_OPTIONS));
|
|
163
|
+
|
|
164
|
+
const [cCaliber, setCCaliber] = useState(() => cartridgeCaseData?.caliber || '');
|
|
165
|
+
const [cCaliberIsCustom, setCCaliberIsCustom] = useState(() => isCustomValue(cartridgeCaseData?.caliber, ALL_CALIBERS));
|
|
166
|
+
const [cBrand, setCBrand] = useState(() => cartridgeCaseData?.brand || '');
|
|
167
|
+
const [cMetal, setCMetal] = useState(() => cartridgeCaseData?.metal || '');
|
|
168
|
+
const [cMetalIsCustom, setCMetalIsCustom] = useState(() => isCustomValue(cartridgeCaseData?.metal, CARTRIDGE_METAL_OPTIONS));
|
|
169
|
+
const [cPrimerType, setCPrimerType] = useState(() => cartridgeCaseData?.primerType || '');
|
|
170
|
+
const [cPrimerTypeIsCustom, setCPrimerTypeIsCustom] = useState(() => isCustomValue(cartridgeCaseData?.primerType, CARTRIDGE_PRIMER_TYPE_OPTIONS));
|
|
171
|
+
const [cFpiShape, setCFpiShape] = useState(() => cartridgeCaseData?.fpiShape || '');
|
|
172
|
+
const [cFpiShapeIsCustom, setCFpiShapeIsCustom] = useState(() => isCustomValue(cartridgeCaseData?.fpiShape, CARTRIDGE_FPI_SHAPE_OPTIONS));
|
|
173
|
+
const [cApertureShape, setCApertureShape] = useState(() => cartridgeCaseData?.apertureShape || '');
|
|
174
|
+
const [cApertureShapeIsCustom, setCApertureShapeIsCustom] = useState(() => isCustomValue(cartridgeCaseData?.apertureShape, CARTRIDGE_APERTURE_SHAPE_OPTIONS));
|
|
175
|
+
const [cHasFpDrag, setCHasFpDrag] = useState(() => cartridgeCaseData?.hasFpDrag ?? false);
|
|
176
|
+
const [cHasExtractorMarks, setCHasExtractorMarks] = useState(() => cartridgeCaseData?.hasExtractorMarks ?? false);
|
|
177
|
+
const [cHasEjectorMarks, setCHasEjectorMarks] = useState(() => cartridgeCaseData?.hasEjectorMarks ?? false);
|
|
178
|
+
const [cHasChamberMarks, setCHasChamberMarks] = useState(() => cartridgeCaseData?.hasChamberMarks ?? false);
|
|
179
|
+
const [cHasMagazineLipMarks, setCHasMagazineLipMarks] = useState(() => cartridgeCaseData?.hasMagazineLipMarks ?? false);
|
|
180
|
+
const [cHasPrimerShear, setCHasPrimerShear] = useState(() => cartridgeCaseData?.hasPrimerShear ?? false);
|
|
181
|
+
const [cHasEjectionPortMarks, setCHasEjectionPortMarks] = useState(() => cartridgeCaseData?.hasEjectionPortMarks ?? false);
|
|
182
|
+
|
|
183
|
+
const [sGauge, setSGauge] = useState(() => shotshellData?.gauge || '');
|
|
184
|
+
const [sGaugeIsCustom, setSGaugeIsCustom] = useState(() => isCustomValue(shotshellData?.gauge, SHOTSHELL_GAUGES));
|
|
185
|
+
const [sShotSize, setSShotSize] = useState(() => shotshellData?.shotSize || '');
|
|
186
|
+
const [sMetal, setSMetal] = useState(() => shotshellData?.metal || '');
|
|
187
|
+
const [sMetalIsCustom, setSMetalIsCustom] = useState(() => isCustomValue(shotshellData?.metal, CARTRIDGE_METAL_OPTIONS));
|
|
188
|
+
const [sBrand, setSBrand] = useState(() => shotshellData?.brand || '');
|
|
189
|
+
const [sFpiShape, setSFpiShape] = useState(() => shotshellData?.fpiShape || '');
|
|
190
|
+
const [sFpiShapeIsCustom, setSFpiShapeIsCustom] = useState(() => isCustomValue(shotshellData?.fpiShape, CARTRIDGE_FPI_SHAPE_OPTIONS));
|
|
191
|
+
const [sHasExtractorMarks, setSHasExtractorMarks] = useState(() => shotshellData?.hasExtractorMarks ?? false);
|
|
192
|
+
const [sHasEjectorMarks, setSHasEjectorMarks] = useState(() => shotshellData?.hasEjectorMarks ?? false);
|
|
193
|
+
const [sHasChamberMarks, setSHasChamberMarks] = useState(() => shotshellData?.hasChamberMarks ?? false);
|
|
194
|
+
|
|
195
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
196
|
+
|
|
197
|
+
const lgCount = Math.max(0, Math.min(30, Number(bLgNumber) || 0));
|
|
198
|
+
const calculatedDiameter = calculateBulletDiameter(lgCount, bLWidths, bGWidths);
|
|
199
|
+
|
|
200
|
+
const updateLWidth = (index: number, value: string) => {
|
|
201
|
+
setBLWidths((previous) => {
|
|
202
|
+
const next = [...previous];
|
|
203
|
+
next[index] = value;
|
|
204
|
+
return next;
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const updateGWidth = (index: number, value: string) => {
|
|
209
|
+
setBGWidths((previous) => {
|
|
210
|
+
const next = [...previous];
|
|
211
|
+
next[index] = value;
|
|
212
|
+
return next;
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const buildSaveData = ({
|
|
217
|
+
showBullet,
|
|
218
|
+
showCartridge,
|
|
219
|
+
showShotshell,
|
|
220
|
+
}: BuildSaveDataParams): BuildSaveDataResult => ({
|
|
221
|
+
bulletData: showBullet ? {
|
|
222
|
+
caliber: bCaliber || undefined,
|
|
223
|
+
mass: bMass || undefined,
|
|
224
|
+
diameter: bDiameter || undefined,
|
|
225
|
+
calcDiameter: calculatedDiameter !== null ? formatCalculatedDiameter(calculatedDiameter) : undefined,
|
|
226
|
+
lgNumber: bLgNumber ? Number(bLgNumber) : undefined,
|
|
227
|
+
lgDirection: bLgDirection || undefined,
|
|
228
|
+
lWidths: bLWidths.some(Boolean) ? bLWidths : undefined,
|
|
229
|
+
gWidths: bGWidths.some(Boolean) ? bGWidths : undefined,
|
|
230
|
+
jacketMetal: bJacketMetal || undefined,
|
|
231
|
+
coreMetal: bCoreMetal || undefined,
|
|
232
|
+
bulletType: bBulletType || undefined,
|
|
233
|
+
barrelType: bBarrelType || undefined,
|
|
234
|
+
} : undefined,
|
|
235
|
+
cartridgeCaseData: showCartridge ? {
|
|
236
|
+
caliber: cCaliber || undefined,
|
|
237
|
+
brand: cBrand || undefined,
|
|
238
|
+
metal: cMetal || undefined,
|
|
239
|
+
primerType: cPrimerType || undefined,
|
|
240
|
+
fpiShape: cFpiShape || undefined,
|
|
241
|
+
apertureShape: cApertureShape || undefined,
|
|
242
|
+
hasFpDrag: cHasFpDrag || undefined,
|
|
243
|
+
hasExtractorMarks: cHasExtractorMarks || undefined,
|
|
244
|
+
hasEjectorMarks: cHasEjectorMarks || undefined,
|
|
245
|
+
hasChamberMarks: cHasChamberMarks || undefined,
|
|
246
|
+
hasMagazineLipMarks: cHasMagazineLipMarks || undefined,
|
|
247
|
+
hasPrimerShear: cHasPrimerShear || undefined,
|
|
248
|
+
hasEjectionPortMarks: cHasEjectionPortMarks || undefined,
|
|
249
|
+
} : undefined,
|
|
250
|
+
shotshellData: showShotshell ? {
|
|
251
|
+
gauge: sGauge || undefined,
|
|
252
|
+
shotSize: sShotSize || undefined,
|
|
253
|
+
metal: sMetal || undefined,
|
|
254
|
+
brand: sBrand || undefined,
|
|
255
|
+
fpiShape: sFpiShape || undefined,
|
|
256
|
+
hasExtractorMarks: sHasExtractorMarks || undefined,
|
|
257
|
+
hasEjectorMarks: sHasEjectorMarks || undefined,
|
|
258
|
+
hasChamberMarks: sHasChamberMarks || undefined,
|
|
259
|
+
} : undefined,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const bullet: BulletDetailsState = {
|
|
263
|
+
caliber: bCaliber,
|
|
264
|
+
caliberIsCustom: bCaliberIsCustom,
|
|
265
|
+
mass: bMass,
|
|
266
|
+
diameter: bDiameter,
|
|
267
|
+
lgNumber: bLgNumber,
|
|
268
|
+
lgDirection: bLgDirection,
|
|
269
|
+
lWidths: bLWidths,
|
|
270
|
+
gWidths: bGWidths,
|
|
271
|
+
jacketMetal: bJacketMetal,
|
|
272
|
+
jacketMetalIsCustom: bJacketMetalIsCustom,
|
|
273
|
+
coreMetal: bCoreMetal,
|
|
274
|
+
coreMetalIsCustom: bCoreMetalIsCustom,
|
|
275
|
+
bulletType: bBulletType,
|
|
276
|
+
bulletTypeIsCustom: bBulletTypeIsCustom,
|
|
277
|
+
barrelType: bBarrelType,
|
|
278
|
+
barrelTypeIsCustom: bBarrelTypeIsCustom,
|
|
279
|
+
lgCount,
|
|
280
|
+
calculatedDiameter,
|
|
281
|
+
setCaliber: setBCaliber,
|
|
282
|
+
setCaliberIsCustom: setBCaliberIsCustom,
|
|
283
|
+
setMass: setBMass,
|
|
284
|
+
setDiameter: setBDiameter,
|
|
285
|
+
setLgNumber: setBLgNumber,
|
|
286
|
+
setLgDirection: setBLgDirection,
|
|
287
|
+
updateLWidth,
|
|
288
|
+
updateGWidth,
|
|
289
|
+
setJacketMetal: setBJacketMetal,
|
|
290
|
+
setJacketMetalIsCustom: setBJacketMetalIsCustom,
|
|
291
|
+
setCoreMetal: setBCoreMetal,
|
|
292
|
+
setCoreMetalIsCustom: setBCoreMetalIsCustom,
|
|
293
|
+
setBulletType: setBBulletType,
|
|
294
|
+
setBulletTypeIsCustom: setBBulletTypeIsCustom,
|
|
295
|
+
setBarrelType: setBBarrelType,
|
|
296
|
+
setBarrelTypeIsCustom: setBBarrelTypeIsCustom,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const cartridgeCase: CartridgeCaseDetailsState = {
|
|
300
|
+
caliber: cCaliber,
|
|
301
|
+
caliberIsCustom: cCaliberIsCustom,
|
|
302
|
+
brand: cBrand,
|
|
303
|
+
metal: cMetal,
|
|
304
|
+
metalIsCustom: cMetalIsCustom,
|
|
305
|
+
primerType: cPrimerType,
|
|
306
|
+
primerTypeIsCustom: cPrimerTypeIsCustom,
|
|
307
|
+
fpiShape: cFpiShape,
|
|
308
|
+
fpiShapeIsCustom: cFpiShapeIsCustom,
|
|
309
|
+
apertureShape: cApertureShape,
|
|
310
|
+
apertureShapeIsCustom: cApertureShapeIsCustom,
|
|
311
|
+
hasFpDrag: cHasFpDrag,
|
|
312
|
+
hasExtractorMarks: cHasExtractorMarks,
|
|
313
|
+
hasEjectorMarks: cHasEjectorMarks,
|
|
314
|
+
hasChamberMarks: cHasChamberMarks,
|
|
315
|
+
hasMagazineLipMarks: cHasMagazineLipMarks,
|
|
316
|
+
hasPrimerShear: cHasPrimerShear,
|
|
317
|
+
hasEjectionPortMarks: cHasEjectionPortMarks,
|
|
318
|
+
setCaliber: setCCaliber,
|
|
319
|
+
setCaliberIsCustom: setCCaliberIsCustom,
|
|
320
|
+
setBrand: setCBrand,
|
|
321
|
+
setMetal: setCMetal,
|
|
322
|
+
setMetalIsCustom: setCMetalIsCustom,
|
|
323
|
+
setPrimerType: setCPrimerType,
|
|
324
|
+
setPrimerTypeIsCustom: setCPrimerTypeIsCustom,
|
|
325
|
+
setFpiShape: setCFpiShape,
|
|
326
|
+
setFpiShapeIsCustom: setCFpiShapeIsCustom,
|
|
327
|
+
setApertureShape: setCApertureShape,
|
|
328
|
+
setApertureShapeIsCustom: setCApertureShapeIsCustom,
|
|
329
|
+
setHasFpDrag: setCHasFpDrag,
|
|
330
|
+
setHasExtractorMarks: setCHasExtractorMarks,
|
|
331
|
+
setHasEjectorMarks: setCHasEjectorMarks,
|
|
332
|
+
setHasChamberMarks: setCHasChamberMarks,
|
|
333
|
+
setHasMagazineLipMarks: setCHasMagazineLipMarks,
|
|
334
|
+
setHasPrimerShear: setCHasPrimerShear,
|
|
335
|
+
setHasEjectionPortMarks: setCHasEjectionPortMarks,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const shotshell: ShotshellDetailsState = {
|
|
339
|
+
gauge: sGauge,
|
|
340
|
+
gaugeIsCustom: sGaugeIsCustom,
|
|
341
|
+
shotSize: sShotSize,
|
|
342
|
+
metal: sMetal,
|
|
343
|
+
metalIsCustom: sMetalIsCustom,
|
|
344
|
+
brand: sBrand,
|
|
345
|
+
fpiShape: sFpiShape,
|
|
346
|
+
fpiShapeIsCustom: sFpiShapeIsCustom,
|
|
347
|
+
hasExtractorMarks: sHasExtractorMarks,
|
|
348
|
+
hasEjectorMarks: sHasEjectorMarks,
|
|
349
|
+
hasChamberMarks: sHasChamberMarks,
|
|
350
|
+
setGauge: setSGauge,
|
|
351
|
+
setGaugeIsCustom: setSGaugeIsCustom,
|
|
352
|
+
setShotSize: setSShotSize,
|
|
353
|
+
setMetal: setSMetal,
|
|
354
|
+
setMetalIsCustom: setSMetalIsCustom,
|
|
355
|
+
setBrand: setSBrand,
|
|
356
|
+
setFpiShape: setSFpiShape,
|
|
357
|
+
setFpiShapeIsCustom: setSFpiShapeIsCustom,
|
|
358
|
+
setHasExtractorMarks: setSHasExtractorMarks,
|
|
359
|
+
setHasEjectorMarks: setSHasEjectorMarks,
|
|
360
|
+
setHasChamberMarks: setSHasChamberMarks,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
bullet,
|
|
365
|
+
cartridgeCase,
|
|
366
|
+
shotshell,
|
|
367
|
+
isSaving,
|
|
368
|
+
setIsSaving,
|
|
369
|
+
buildSaveData,
|
|
370
|
+
};
|
|
371
|
+
};
|
|
@@ -24,10 +24,12 @@ interface SidebarContainerProps {
|
|
|
24
24
|
setShowNotes: (show: boolean) => void;
|
|
25
25
|
onAnnotationRefresh?: () => void;
|
|
26
26
|
isReadOnly?: boolean;
|
|
27
|
+
isArchivedCase?: boolean;
|
|
27
28
|
isConfirmed?: boolean;
|
|
28
29
|
confirmationSaveVersion?: number;
|
|
29
30
|
isUploading?: boolean;
|
|
30
31
|
onUploadStatusChange?: (isUploading: boolean) => void;
|
|
32
|
+
onOpenCaseExport?: () => void;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export const SidebarContainer: React.FC<SidebarContainerProps> = (props) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { User } from 'firebase/auth';
|
|
2
|
+
import type React from 'react';
|
|
2
3
|
import { useState, useCallback } from 'react';
|
|
3
4
|
import styles from './sidebar.module.css';
|
|
4
5
|
import { CaseSidebar } from './cases/case-sidebar';
|
|
@@ -19,10 +20,12 @@ interface SidebarProps {
|
|
|
19
20
|
setShowNotes: (show: boolean) => void;
|
|
20
21
|
onAnnotationRefresh?: () => void;
|
|
21
22
|
isReadOnly?: boolean;
|
|
23
|
+
isArchivedCase?: boolean;
|
|
22
24
|
isConfirmed?: boolean;
|
|
23
25
|
confirmationSaveVersion?: number;
|
|
24
26
|
isUploading?: boolean;
|
|
25
27
|
onUploadStatusChange?: (isUploading: boolean) => void;
|
|
28
|
+
onOpenCaseExport?: () => void;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export const Sidebar = ({
|
|
@@ -37,10 +40,12 @@ export const Sidebar = ({
|
|
|
37
40
|
setFiles,
|
|
38
41
|
setShowNotes,
|
|
39
42
|
isReadOnly = false,
|
|
43
|
+
isArchivedCase = false,
|
|
40
44
|
isConfirmed = false,
|
|
41
45
|
confirmationSaveVersion = 0,
|
|
42
46
|
isUploading: initialIsUploading = false,
|
|
43
47
|
onUploadStatusChange,
|
|
48
|
+
onOpenCaseExport,
|
|
44
49
|
}: SidebarProps) => {
|
|
45
50
|
const [isUploading, setIsUploading] = useState(initialIsUploading);
|
|
46
51
|
const [toastMessage, setToastMessage] = useState('');
|
|
@@ -69,7 +74,7 @@ export const Sidebar = ({
|
|
|
69
74
|
setToastMessage(`${result.successCount} file${result.successCount !== 1 ? 's' : ''} uploaded!`);
|
|
70
75
|
}
|
|
71
76
|
setIsToastVisible(true);
|
|
72
|
-
}, []);
|
|
77
|
+
}, []);
|
|
73
78
|
|
|
74
79
|
return (
|
|
75
80
|
<div className={styles.sidebar}>
|
|
@@ -84,12 +89,14 @@ export const Sidebar = ({
|
|
|
84
89
|
setFiles={setFiles}
|
|
85
90
|
onNotesClick={() => setShowNotes(true)}
|
|
86
91
|
isReadOnly={isReadOnly}
|
|
92
|
+
isArchivedCase={isArchivedCase}
|
|
87
93
|
isConfirmed={isConfirmed}
|
|
88
94
|
confirmationSaveVersion={confirmationSaveVersion}
|
|
89
95
|
selectedFileId={imageId}
|
|
90
96
|
isUploading={isUploading}
|
|
91
97
|
onUploadStatusChange={handleUploadStatusChange}
|
|
92
98
|
onUploadComplete={handleUploadComplete}
|
|
99
|
+
onOpenCaseExport={onOpenCaseExport}
|
|
93
100
|
/>
|
|
94
101
|
<Toast
|
|
95
102
|
message={toastMessage}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type CasesModalPreferences,
|
|
4
|
+
type CasesModalSortBy,
|
|
5
|
+
type CasesModalConfirmationFilter,
|
|
6
|
+
} from '~/utils/data/case-filters';
|
|
7
|
+
|
|
8
|
+
const CASES_MODAL_PREFERENCES_STORAGE_KEY = 'striae.casesModal.preferences';
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_CASES_MODAL_PREFERENCES: CasesModalPreferences = {
|
|
11
|
+
sortBy: 'recent',
|
|
12
|
+
confirmationFilter: 'all',
|
|
13
|
+
showArchivedOnly: false,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function parseStoredPreferences(value: string | null): CasesModalPreferences {
|
|
17
|
+
if (!value) {
|
|
18
|
+
return DEFAULT_CASES_MODAL_PREFERENCES;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(value) as Partial<CasesModalPreferences>;
|
|
23
|
+
|
|
24
|
+
const sortBy: CasesModalSortBy =
|
|
25
|
+
parsed.sortBy === 'alphabetical' || parsed.sortBy === 'recent'
|
|
26
|
+
? parsed.sortBy
|
|
27
|
+
: DEFAULT_CASES_MODAL_PREFERENCES.sortBy;
|
|
28
|
+
|
|
29
|
+
const confirmationFilter: CasesModalConfirmationFilter =
|
|
30
|
+
parsed.confirmationFilter === 'pending' ||
|
|
31
|
+
parsed.confirmationFilter === 'confirmed' ||
|
|
32
|
+
parsed.confirmationFilter === 'none-requested' ||
|
|
33
|
+
parsed.confirmationFilter === 'all'
|
|
34
|
+
? parsed.confirmationFilter
|
|
35
|
+
: DEFAULT_CASES_MODAL_PREFERENCES.confirmationFilter;
|
|
36
|
+
|
|
37
|
+
const showArchivedOnly =
|
|
38
|
+
typeof parsed.showArchivedOnly === 'boolean'
|
|
39
|
+
? parsed.showArchivedOnly
|
|
40
|
+
: DEFAULT_CASES_MODAL_PREFERENCES.showArchivedOnly;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
sortBy,
|
|
44
|
+
confirmationFilter,
|
|
45
|
+
showArchivedOnly,
|
|
46
|
+
};
|
|
47
|
+
} catch {
|
|
48
|
+
return DEFAULT_CASES_MODAL_PREFERENCES;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function loadCasesModalPreferences(): CasesModalPreferences {
|
|
53
|
+
if (typeof window === 'undefined') {
|
|
54
|
+
return DEFAULT_CASES_MODAL_PREFERENCES;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parseStoredPreferences(window.localStorage.getItem(CASES_MODAL_PREFERENCES_STORAGE_KEY));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useCaseListPreferences() {
|
|
61
|
+
const [preferences, setPreferences] = useState<CasesModalPreferences>(() =>
|
|
62
|
+
loadCasesModalPreferences()
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (typeof window === 'undefined') {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
window.localStorage.setItem(
|
|
71
|
+
CASES_MODAL_PREFERENCES_STORAGE_KEY,
|
|
72
|
+
JSON.stringify(preferences)
|
|
73
|
+
);
|
|
74
|
+
}, [preferences]);
|
|
75
|
+
|
|
76
|
+
const setSortBy = (sortBy: CasesModalSortBy) => {
|
|
77
|
+
setPreferences((current) => ({ ...current, sortBy }));
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const setConfirmationFilter = (confirmationFilter: CasesModalConfirmationFilter) => {
|
|
81
|
+
setPreferences((current) => ({ ...current, confirmationFilter }));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const setShowArchivedOnly = (showArchivedOnly: boolean) => {
|
|
85
|
+
setPreferences((current) => ({ ...current, showArchivedOnly }));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const resetPreferences = () => {
|
|
89
|
+
setPreferences(DEFAULT_CASES_MODAL_PREFERENCES);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
preferences,
|
|
94
|
+
setSortBy,
|
|
95
|
+
setConfirmationFilter,
|
|
96
|
+
setShowArchivedOnly,
|
|
97
|
+
resetPreferences,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type FilesModalPreferences,
|
|
4
|
+
type FilesModalSortBy,
|
|
5
|
+
type FilesModalConfirmationFilter,
|
|
6
|
+
type FilesModalClassTypeFilter,
|
|
7
|
+
} from '~/utils/data/file-filters';
|
|
8
|
+
|
|
9
|
+
const FILES_MODAL_PREFERENCES_STORAGE_KEY = 'striae.filesModal.preferences';
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_FILES_MODAL_PREFERENCES: FilesModalPreferences = {
|
|
12
|
+
sortBy: 'recent',
|
|
13
|
+
confirmationFilter: 'all',
|
|
14
|
+
classTypeFilter: 'all',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function parseStoredPreferences(value: string | null): FilesModalPreferences {
|
|
18
|
+
if (!value) {
|
|
19
|
+
return DEFAULT_FILES_MODAL_PREFERENCES;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(value) as Partial<FilesModalPreferences>;
|
|
24
|
+
|
|
25
|
+
const sortBy: FilesModalSortBy =
|
|
26
|
+
parsed.sortBy === 'filename' ||
|
|
27
|
+
parsed.sortBy === 'confirmation' ||
|
|
28
|
+
parsed.sortBy === 'classType' ||
|
|
29
|
+
parsed.sortBy === 'recent'
|
|
30
|
+
? parsed.sortBy
|
|
31
|
+
: DEFAULT_FILES_MODAL_PREFERENCES.sortBy;
|
|
32
|
+
|
|
33
|
+
const confirmationFilter: FilesModalConfirmationFilter =
|
|
34
|
+
parsed.confirmationFilter === 'pending' ||
|
|
35
|
+
parsed.confirmationFilter === 'confirmed' ||
|
|
36
|
+
parsed.confirmationFilter === 'none-requested' ||
|
|
37
|
+
parsed.confirmationFilter === 'all'
|
|
38
|
+
? parsed.confirmationFilter
|
|
39
|
+
: DEFAULT_FILES_MODAL_PREFERENCES.confirmationFilter;
|
|
40
|
+
|
|
41
|
+
const classTypeFilter: FilesModalClassTypeFilter =
|
|
42
|
+
parsed.classTypeFilter === 'Bullet' ||
|
|
43
|
+
parsed.classTypeFilter === 'Cartridge Case' ||
|
|
44
|
+
parsed.classTypeFilter === 'Shotshell' ||
|
|
45
|
+
parsed.classTypeFilter === 'Other' ||
|
|
46
|
+
parsed.classTypeFilter === 'all'
|
|
47
|
+
? parsed.classTypeFilter
|
|
48
|
+
: parsed.classTypeFilter === 'unset'
|
|
49
|
+
? 'Other'
|
|
50
|
+
: DEFAULT_FILES_MODAL_PREFERENCES.classTypeFilter;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
sortBy,
|
|
54
|
+
confirmationFilter,
|
|
55
|
+
classTypeFilter,
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
return DEFAULT_FILES_MODAL_PREFERENCES;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function loadFilesModalPreferences(): FilesModalPreferences {
|
|
63
|
+
if (typeof window === 'undefined') {
|
|
64
|
+
return DEFAULT_FILES_MODAL_PREFERENCES;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return parseStoredPreferences(window.localStorage.getItem(FILES_MODAL_PREFERENCES_STORAGE_KEY));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useFileListPreferences() {
|
|
71
|
+
const [preferences, setPreferences] = useState<FilesModalPreferences>(() =>
|
|
72
|
+
loadFilesModalPreferences()
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (typeof window === 'undefined') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
window.localStorage.setItem(FILES_MODAL_PREFERENCES_STORAGE_KEY, JSON.stringify(preferences));
|
|
81
|
+
}, [preferences]);
|
|
82
|
+
|
|
83
|
+
const setSortBy = (sortBy: FilesModalSortBy) => {
|
|
84
|
+
setPreferences((current) => ({ ...current, sortBy }));
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const setConfirmationFilter = (confirmationFilter: FilesModalConfirmationFilter) => {
|
|
88
|
+
setPreferences((current) => ({ ...current, confirmationFilter }));
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const setClassTypeFilter = (classTypeFilter: FilesModalClassTypeFilter) => {
|
|
92
|
+
setPreferences((current) => ({ ...current, classTypeFilter }));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const resetPreferences = () => {
|
|
96
|
+
setPreferences(DEFAULT_FILES_MODAL_PREFERENCES);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
preferences,
|
|
101
|
+
setSortBy,
|
|
102
|
+
setConfirmationFilter,
|
|
103
|
+
setClassTypeFilter,
|
|
104
|
+
resetPreferences,
|
|
105
|
+
};
|
|
106
|
+
}
|