@striae-org/striae 4.2.0 → 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/LICENSE +1 -1
- package/app/components/actions/case-manage.ts +50 -17
- package/app/components/audit/viewer/audit-entries-list.tsx +5 -2
- package/app/components/audit/viewer/use-audit-viewer-data.ts +6 -3
- package/app/components/audit/viewer/use-audit-viewer-export.ts +1 -1
- package/app/components/canvas/confirmation/confirmation.tsx +6 -2
- package/app/components/colors/colors.module.css +4 -3
- 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.tsx +34 -9
- package/app/components/sidebar/cases/case-sidebar.tsx +93 -73
- package/app/components/sidebar/cases/cases-modal.module.css +312 -10
- package/app/components/sidebar/cases/cases-modal.tsx +737 -116
- package/app/components/sidebar/cases/cases.module.css +43 -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 +482 -177
- package/app/components/sidebar/notes/addl-notes-modal.tsx +82 -0
- 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-sidebar.tsx → notes-editor-form.tsx} +77 -76
- package/app/components/sidebar/notes/notes-editor-modal.tsx +5 -7
- package/app/components/sidebar/notes/notes.module.css +262 -14
- 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 +15 -1
- package/app/{tailwind.css → global.css} +1 -3
- package/app/hooks/useCaseListPreferences.ts +99 -0
- package/app/hooks/useFileListPreferences.ts +106 -0
- package/app/hooks/useOverlayDismiss.ts +6 -4
- package/app/root.tsx +1 -1
- package/app/routes/striae/striae.tsx +7 -0
- package/app/services/audit/audit.service.ts +2 -2
- package/app/services/audit/builders/audit-event-builders-case-file.ts +1 -1
- 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 +295 -0
- package/app/utils/data/data-operations.ts +17 -861
- package/app/utils/data/file-filters.ts +201 -0
- package/app/utils/data/index.ts +11 -1
- package/app/utils/data/operations/batch-operations.ts +113 -0
- package/app/utils/data/operations/case-operations.ts +168 -0
- package/app/utils/data/operations/confirmation-summary-operations.ts +301 -0
- package/app/utils/data/operations/file-annotation-operations.ts +196 -0
- package/app/utils/data/operations/index.ts +7 -0
- package/app/utils/data/operations/signing-operations.ts +225 -0
- package/app/utils/data/operations/types.ts +42 -0
- package/app/utils/data/operations/validation-operations.ts +48 -0
- package/app/utils/forensics/export-verification.ts +40 -111
- package/functions/api/_shared/firebase-auth.ts +2 -7
- package/functions/api/image/[[path]].ts +23 -22
- package/functions/api/pdf/[[path]].ts +27 -8
- package/package.json +7 -13
- package/scripts/deploy-primershear-emails.sh +1 -1
- package/worker-configuration.d.ts +2 -2
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/image-worker.example.ts +16 -5
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/package.json +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/src/formats/format-striae.ts +84 -124
- package/workers/pdf-worker/src/pdf-worker.example.ts +58 -61
- package/workers/pdf-worker/src/report-layout.ts +227 -0
- package/workers/pdf-worker/src/report-types.ts +23 -3
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +1 -1
- package/workers/user-worker/src/user-worker.example.ts +17 -0
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/NOTICE +0 -13
- package/app/components/sidebar/notes/notes-modal.tsx +0 -52
- package/postcss.config.js +0 -6
- package/tailwind.config.ts +0 -22
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@striae-org/striae",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"app/entry.server.tsx",
|
|
45
45
|
"app/root.tsx",
|
|
46
46
|
"app/routes.ts",
|
|
47
|
-
"app/
|
|
47
|
+
"app/global.css",
|
|
48
48
|
"react-router.config.ts",
|
|
49
49
|
"load-context.ts",
|
|
50
50
|
"functions/",
|
|
@@ -54,24 +54,20 @@
|
|
|
54
54
|
"workers/*/src/*.example.ts",
|
|
55
55
|
"workers/*/src/*.example.js",
|
|
56
56
|
"workers/*/src/*.ts",
|
|
57
|
-
"workers/pdf-worker/scripts/*.js",
|
|
58
|
-
"workers/pdf-worker/src/assets/icon-256.png",
|
|
57
|
+
"workers/pdf-worker/scripts/*.js",
|
|
59
58
|
"!workers/*/src/*worker.ts",
|
|
60
|
-
"workers/pdf-worker/src/assets/generated-assets.ts",
|
|
59
|
+
"workers/pdf-worker/src/assets/generated-assets.example.ts",
|
|
61
60
|
"workers/pdf-worker/src/formats/format-striae.ts",
|
|
62
61
|
"workers/pdf-worker/src/report-types.ts",
|
|
63
62
|
"workers/*/wrangler.jsonc.example",
|
|
64
63
|
".env.example",
|
|
65
64
|
"primershear.emails.example",
|
|
66
65
|
"firebase.json",
|
|
67
|
-
"postcss.config.js",
|
|
68
|
-
"tailwind.config.ts",
|
|
69
66
|
"tsconfig.json",
|
|
70
67
|
"vite.config.ts",
|
|
71
68
|
"worker-configuration.d.ts",
|
|
72
69
|
"wrangler.toml.example",
|
|
73
|
-
"LICENSE"
|
|
74
|
-
"NOTICE"
|
|
70
|
+
"LICENSE"
|
|
75
71
|
],
|
|
76
72
|
"sideEffects": false,
|
|
77
73
|
"type": "module",
|
|
@@ -129,15 +125,13 @@
|
|
|
129
125
|
"@types/react-dom": "^19.2.3",
|
|
130
126
|
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
|
131
127
|
"@typescript-eslint/parser": "^8.57.1",
|
|
132
|
-
"autoprefixer": "^10.4.27",
|
|
133
128
|
"eslint": "^9.39.4",
|
|
134
129
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
135
130
|
"eslint-plugin-import": "^2.32.0",
|
|
136
131
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
137
132
|
"eslint-plugin-react": "^7.37.5",
|
|
138
133
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
139
|
-
"
|
|
140
|
-
"tailwindcss": "^3.4.19",
|
|
134
|
+
"modern-normalize": "^3.0.1",
|
|
141
135
|
"typescript": "^5.9.3",
|
|
142
136
|
"vite": "^6.4.1",
|
|
143
137
|
"vite-tsconfig-paths": "^6.1.1",
|
|
@@ -157,4 +151,4 @@
|
|
|
157
151
|
"node": ">=20.0.0"
|
|
158
152
|
},
|
|
159
153
|
"packageManager": "npm@11.11.0"
|
|
160
|
-
}
|
|
154
|
+
}
|
|
@@ -82,7 +82,7 @@ fi
|
|
|
82
82
|
|
|
83
83
|
# Strip comment lines and blank lines, then join with commas
|
|
84
84
|
# Use || true to avoid failure if paste gets no input (handles empty file gracefully)
|
|
85
|
-
PRIMERSHEAR_EMAILS=$(grep -v '
|
|
85
|
+
PRIMERSHEAR_EMAILS=$(grep -v '^[[:space:]]*#' "$EMAILS_FILE" | grep -v '^[[:space:]]*$' | paste -sd ',' - || true)
|
|
86
86
|
|
|
87
87
|
if [ -z "$PRIMERSHEAR_EMAILS" ]; then
|
|
88
88
|
echo -e "${YELLOW}⚠️ primershear.emails contains no active email addresses.${NC}"
|
|
@@ -5485,7 +5485,7 @@ type AIGatewayHeaders = {
|
|
|
5485
5485
|
[key: string]: string | number | boolean | object;
|
|
5486
5486
|
};
|
|
5487
5487
|
type AIGatewayUniversalRequest = {
|
|
5488
|
-
provider: AIGatewayProviders | string;
|
|
5488
|
+
provider: AIGatewayProviders | string;
|
|
5489
5489
|
endpoint: string;
|
|
5490
5490
|
headers: Partial<AIGatewayHeaders>;
|
|
5491
5491
|
query: unknown;
|
|
@@ -5501,7 +5501,7 @@ declare abstract class AiGateway {
|
|
|
5501
5501
|
gateway?: UniversalGatewayOptions;
|
|
5502
5502
|
extraHeaders?: object;
|
|
5503
5503
|
}): Promise<Response>;
|
|
5504
|
-
getUrl(provider?: AIGatewayProviders | string): Promise<string>;
|
|
5504
|
+
getUrl(provider?: AIGatewayProviders | string): Promise<string>;
|
|
5505
5505
|
}
|
|
5506
5506
|
interface AutoRAGInternalError extends Error {
|
|
5507
5507
|
}
|
|
@@ -145,17 +145,28 @@ async function handleImageServing(request: Request, env: Env): Promise<Response>
|
|
|
145
145
|
|
|
146
146
|
const url = new URL(request.url);
|
|
147
147
|
const encodedPath = url.pathname.slice(1);
|
|
148
|
-
|
|
148
|
+
if (!encodedPath) {
|
|
149
|
+
return createResponse({ error: 'Image delivery URL is required' }, 400);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let decodedPath: string;
|
|
149
153
|
|
|
150
154
|
try {
|
|
151
155
|
decodedPath = decodeURIComponent(encodedPath);
|
|
152
156
|
} catch {
|
|
153
|
-
|
|
157
|
+
return createResponse({ error: 'Image delivery URL must be URL-encoded' }, 400);
|
|
154
158
|
}
|
|
155
159
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
let imageDeliveryURL: URL;
|
|
161
|
+
try {
|
|
162
|
+
imageDeliveryURL = new URL(decodedPath);
|
|
163
|
+
} catch {
|
|
164
|
+
return createResponse({ error: 'Image delivery URL is invalid' }, 400);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (imageDeliveryURL.protocol !== 'https:' || imageDeliveryURL.hostname !== 'imagedelivery.net') {
|
|
168
|
+
return createResponse({ error: 'Image delivery URL must target imagedelivery.net over HTTPS' }, 400);
|
|
169
|
+
}
|
|
159
170
|
|
|
160
171
|
return generateSignedUrl(imageDeliveryURL, env);
|
|
161
172
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { PDFGenerationData, ReportRenderer } from '../report-types';
|
|
1
|
+
import type { PDFGenerationData, ReportPdfOptionsBuilder, ReportRenderer } from '../report-types';
|
|
2
2
|
import { ICON_256 } from '../assets/generated-assets';
|
|
3
|
+
import { buildRepeatedChromePdfOptions, escapeHtml } from '../report-layout';
|
|
3
4
|
|
|
4
5
|
export const renderReport: ReportRenderer = (data: PDFGenerationData): string => {
|
|
5
|
-
const { imageUrl,
|
|
6
|
+
const { imageUrl, annotationData, activeAnnotations } = data;
|
|
6
7
|
const annotationsSet = new Set(activeAnnotations);
|
|
8
|
+
const hasImage = Boolean(imageUrl && imageUrl !== '/clear.jpg');
|
|
9
|
+
const safeText = (value: unknown): string => escapeHtml(String(value ?? ''));
|
|
7
10
|
|
|
8
11
|
// Programmatically determine if a color is dark and needs a light background
|
|
9
12
|
const needsLightBackground = (color: string | undefined): boolean => {
|
|
@@ -63,12 +66,6 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
63
66
|
return luminance < 0.5;
|
|
64
67
|
};
|
|
65
68
|
|
|
66
|
-
// Use passed currentDate or generate fallback
|
|
67
|
-
const displayDate = currentDate || (() => {
|
|
68
|
-
const now = new Date();
|
|
69
|
-
return `${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getDate().toString().padStart(2, '0')}/${now.getFullYear()}`;
|
|
70
|
-
})();
|
|
71
|
-
|
|
72
69
|
return `
|
|
73
70
|
<!DOCTYPE html>
|
|
74
71
|
<html lang="en">
|
|
@@ -77,42 +74,23 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
77
74
|
<style>
|
|
78
75
|
html, body {
|
|
79
76
|
width: 100%;
|
|
80
|
-
height: 100%;
|
|
81
77
|
margin: 0;
|
|
82
78
|
font-family: Arial, sans-serif;
|
|
83
79
|
background-color: white;
|
|
84
|
-
display: flex;
|
|
85
|
-
flex-direction: column;
|
|
86
|
-
min-height: 100vh;
|
|
87
|
-
}
|
|
88
|
-
.header {
|
|
89
|
-
display: flex;
|
|
90
|
-
align-items: center;
|
|
91
|
-
margin-bottom: 15px;
|
|
92
|
-
border-bottom: 2px solid #333;
|
|
93
|
-
padding-bottom: 8px;
|
|
94
|
-
position: relative;
|
|
95
80
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
display: flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
justify-content: space-between;
|
|
81
|
+
body {
|
|
82
|
+
color: #333;
|
|
101
83
|
}
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
84
|
+
.report-body {
|
|
85
|
+
width: 100%;
|
|
86
|
+
box-sizing: border-box;
|
|
105
87
|
}
|
|
106
|
-
.case-number {
|
|
107
|
-
font-size: 16px;
|
|
108
|
-
font-weight: bold;
|
|
109
|
-
color: #333;
|
|
110
|
-
text-align: right;
|
|
111
|
-
}
|
|
112
88
|
.image-container {
|
|
113
89
|
width: 100%;
|
|
114
|
-
margin:
|
|
90
|
+
margin: 0 0 10px;
|
|
115
91
|
position: relative;
|
|
92
|
+
page-break-inside: avoid;
|
|
93
|
+
break-inside: avoid;
|
|
116
94
|
}
|
|
117
95
|
.image-wrapper {
|
|
118
96
|
display: flex;
|
|
@@ -176,6 +154,8 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
176
154
|
width: 100%;
|
|
177
155
|
margin-top: 12px;
|
|
178
156
|
min-height: 50px;
|
|
157
|
+
page-break-inside: avoid;
|
|
158
|
+
break-inside: avoid;
|
|
179
159
|
}
|
|
180
160
|
.support-level-annotation {
|
|
181
161
|
flex: 1;
|
|
@@ -241,10 +221,21 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
241
221
|
}
|
|
242
222
|
.confirmation-section {
|
|
243
223
|
margin-top: 20px;
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
gap: 16px;
|
|
227
|
+
}
|
|
228
|
+
.notes-page {
|
|
229
|
+
page-break-before: always;
|
|
230
|
+
break-before: page;
|
|
231
|
+
}
|
|
232
|
+
.confirmation-summary {
|
|
244
233
|
display: flex;
|
|
245
234
|
justify-content: flex-start;
|
|
246
235
|
align-items: flex-start;
|
|
247
236
|
gap: 20px;
|
|
237
|
+
page-break-inside: avoid;
|
|
238
|
+
break-inside: avoid;
|
|
248
239
|
}
|
|
249
240
|
.confirmation-box {
|
|
250
241
|
background: #ffffff;
|
|
@@ -253,6 +244,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
253
244
|
padding: 15px;
|
|
254
245
|
width: 280px;
|
|
255
246
|
font-family: 'Inter', Arial, sans-serif;
|
|
247
|
+
box-sizing: border-box;
|
|
256
248
|
}
|
|
257
249
|
.confirmation-label {
|
|
258
250
|
font-size: 14px;
|
|
@@ -280,6 +272,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
280
272
|
padding: 15px;
|
|
281
273
|
width: 280px;
|
|
282
274
|
font-family: 'Inter', Arial, sans-serif;
|
|
275
|
+
box-sizing: border-box;
|
|
283
276
|
}
|
|
284
277
|
.confirmation-title {
|
|
285
278
|
font-size: 14px;
|
|
@@ -322,64 +315,32 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
322
315
|
letter-spacing: 1px;
|
|
323
316
|
}
|
|
324
317
|
.additional-notes-section {
|
|
325
|
-
|
|
318
|
+
border: 1px solid #d7dbe0;
|
|
319
|
+
border-radius: 8px;
|
|
320
|
+
background: #fafbfc;
|
|
321
|
+
padding: 16px 18px;
|
|
322
|
+
box-sizing: border-box;
|
|
326
323
|
font-family: 'Inter', Arial, sans-serif;
|
|
327
|
-
font-size: 14px;
|
|
328
|
-
line-height: 1.6;
|
|
329
324
|
color: #333;
|
|
330
|
-
white-space: pre-wrap;
|
|
331
|
-
word-wrap: break-word;
|
|
332
|
-
text-indent: 0 !important;
|
|
333
|
-
padding: 0;
|
|
334
|
-
margin: 0;
|
|
335
|
-
margin-left: 20px;
|
|
336
|
-
flex-shrink: 0;
|
|
337
|
-
text-align: left;
|
|
338
|
-
display: block;
|
|
339
325
|
}
|
|
340
|
-
.
|
|
341
|
-
margin
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
align-items: center;
|
|
347
|
-
font-family: 'Inter', Arial, sans-serif;
|
|
348
|
-
font-size: 11px;
|
|
326
|
+
.additional-notes-title {
|
|
327
|
+
margin: 0 0 10px;
|
|
328
|
+
font-size: 12px;
|
|
329
|
+
font-weight: 700;
|
|
330
|
+
letter-spacing: 0.08em;
|
|
331
|
+
text-transform: uppercase;
|
|
349
332
|
color: #666;
|
|
350
333
|
}
|
|
351
|
-
.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
font-weight: 500;
|
|
362
|
-
flex: 1;
|
|
363
|
-
text-align: left;
|
|
364
|
-
display: flex;
|
|
365
|
-
align-items: center;
|
|
366
|
-
gap: 6px;
|
|
367
|
-
}
|
|
368
|
-
.footer-brand-icon {
|
|
369
|
-
width: 14px;
|
|
370
|
-
height: 14px;
|
|
371
|
-
object-fit: contain;
|
|
372
|
-
}
|
|
373
|
-
.footer-center {
|
|
374
|
-
font-weight: 600;
|
|
375
|
-
flex: 1;
|
|
376
|
-
text-align: center;
|
|
377
|
-
color: #333;
|
|
378
|
-
}
|
|
379
|
-
.footer-right {
|
|
380
|
-
font-style: italic;
|
|
381
|
-
flex: 1;
|
|
382
|
-
text-align: right;
|
|
334
|
+
.additional-notes-body {
|
|
335
|
+
margin: 0;
|
|
336
|
+
font-size: 14px;
|
|
337
|
+
line-height: 1.6;
|
|
338
|
+
white-space: pre-wrap;
|
|
339
|
+
overflow-wrap: anywhere;
|
|
340
|
+
word-break: break-word;
|
|
341
|
+
text-indent: 0 !important;
|
|
342
|
+
orphans: 3;
|
|
343
|
+
widows: 3;
|
|
383
344
|
}
|
|
384
345
|
.index-section {
|
|
385
346
|
text-align: center;
|
|
@@ -401,19 +362,12 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
401
362
|
</style>
|
|
402
363
|
</head>
|
|
403
364
|
<body>
|
|
404
|
-
<div class="
|
|
405
|
-
<div class="content-wrapper">
|
|
406
|
-
<div class="header">
|
|
407
|
-
<div class="header-content">
|
|
408
|
-
<div class="date">${displayDate}</div>
|
|
409
|
-
${caseNumber ? `<div class="case-number">${caseNumber}</div>` : '<div class="case-number"></div>'}
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
365
|
+
<div class="report-body">
|
|
412
366
|
|
|
413
|
-
${
|
|
367
|
+
${hasImage ? `
|
|
414
368
|
${annotationData && annotationsSet?.has('index') && annotationData.indexType === 'number' && annotationData.indexNumber ? `
|
|
415
369
|
<div class="index-section">
|
|
416
|
-
Index: ${annotationData.indexNumber}
|
|
370
|
+
Index: ${safeText(annotationData.indexNumber)}
|
|
417
371
|
</div>
|
|
418
372
|
` : ''}
|
|
419
373
|
|
|
@@ -425,12 +379,12 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
425
379
|
<div class="annotations-overlay">
|
|
426
380
|
<div class="left-annotation" style="${needsLightBackground(annotationData.caseFontColor || '#FFDE21') ? 'background: rgba(255, 255, 255, 0.9); border: 2px solid rgba(0, 0, 0, 0.2); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);' : ''}">
|
|
427
381
|
<div class="case-text" style="color: ${annotationData.caseFontColor || '#FFDE21'};">
|
|
428
|
-
${annotationData.leftCase}${annotationData.leftItem ? ` ${annotationData.leftItem}` : ''}
|
|
382
|
+
${safeText(annotationData.leftCase)}${annotationData.leftItem ? ` ${safeText(annotationData.leftItem)}` : ''}
|
|
429
383
|
</div>
|
|
430
384
|
</div>
|
|
431
385
|
<div class="right-annotation" style="${needsLightBackground(annotationData.caseFontColor || '#FFDE21') ? 'background: rgba(255, 255, 255, 0.9); border: 2px solid rgba(0, 0, 0, 0.2); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);' : ''}">
|
|
432
386
|
<div class="case-text" style="color: ${annotationData.caseFontColor || '#FFDE21'};">
|
|
433
|
-
${annotationData.rightCase}${annotationData.rightItem ? ` ${annotationData.rightItem}` : ''}
|
|
387
|
+
${safeText(annotationData.rightCase)}${annotationData.rightItem ? ` ${safeText(annotationData.rightItem)}` : ''}
|
|
434
388
|
</div>
|
|
435
389
|
</div>
|
|
436
390
|
</div>
|
|
@@ -456,7 +410,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
456
410
|
${annotationData && annotationsSet?.has('id') ? `
|
|
457
411
|
<div class="support-level-annotation">
|
|
458
412
|
<div class="support-level-text" style="color: ${annotationData.supportLevel === 'ID' ? '#28a745' : annotationData.supportLevel === 'Exclusion' ? '#dc3545' : '#ffc107'}; background: ${annotationData.supportLevel === 'Inconclusive' ? 'rgba(120, 120, 120, 0.95)' : 'rgba(240, 240, 240, 0.95)'};">
|
|
459
|
-
${annotationData.supportLevel === 'ID' ? 'Identification' : annotationData.supportLevel}
|
|
413
|
+
${safeText(annotationData.supportLevel === 'ID' ? 'Identification' : annotationData.supportLevel)}
|
|
460
414
|
</div>
|
|
461
415
|
</div>
|
|
462
416
|
` : '<div class="support-level-annotation"></div>'}
|
|
@@ -464,7 +418,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
464
418
|
${annotationData && annotationsSet?.has('class') ? `
|
|
465
419
|
<div class="class-annotation">
|
|
466
420
|
<div class="class-text-annotation">
|
|
467
|
-
${annotationData.customClass || annotationData.classType}${annotationData.classNote ? ` (${annotationData.classNote})` : ''}
|
|
421
|
+
${safeText(annotationData.customClass || annotationData.classType)}${annotationData.classNote ? ` (${safeText(annotationData.classNote)})` : ''}
|
|
468
422
|
</div>
|
|
469
423
|
</div>
|
|
470
424
|
` : '<div class="class-annotation"></div>'}
|
|
@@ -483,20 +437,21 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
483
437
|
${annotationData && ((annotationData.includeConfirmation === true) || annotationData.additionalNotes) ? `
|
|
484
438
|
<div class="confirmation-section">
|
|
485
439
|
${annotationData && (annotationData.includeConfirmation === true) ? `
|
|
440
|
+
<div class="confirmation-summary">
|
|
486
441
|
${annotationData.confirmationData ? `
|
|
487
442
|
<div class="confirmation-data">
|
|
488
443
|
<div class="confirmation-title">IDENTIFICATION CONFIRMED</div>
|
|
489
444
|
<div class="confirmation-field">
|
|
490
|
-
<div class="confirmation-name">${annotationData.confirmationData.fullName}, ${annotationData.confirmationData.badgeId}</div>
|
|
445
|
+
<div class="confirmation-name">${safeText(annotationData.confirmationData.fullName)}, ${safeText(annotationData.confirmationData.badgeId)}</div>
|
|
491
446
|
</div>
|
|
492
447
|
<div class="confirmation-field">
|
|
493
|
-
<div class="confirmation-company">${annotationData.confirmationData.confirmedByCompany || 'N/A'}</div>
|
|
448
|
+
<div class="confirmation-company">${safeText(annotationData.confirmationData.confirmedByCompany || 'N/A')}</div>
|
|
494
449
|
</div>
|
|
495
450
|
<div class="confirmation-field">
|
|
496
|
-
<div class="confirmation-timestamp">${annotationData.confirmationData.timestamp}</div>
|
|
451
|
+
<div class="confirmation-timestamp">${safeText(annotationData.confirmationData.timestamp)}</div>
|
|
497
452
|
</div>
|
|
498
453
|
<div class="confirmation-field">
|
|
499
|
-
<div class="confirmation-id">ID: ${annotationData.confirmationData.confirmationId}</div>
|
|
454
|
+
<div class="confirmation-id">ID: ${safeText(annotationData.confirmationData.confirmationId)}</div>
|
|
500
455
|
</div>
|
|
501
456
|
</div>
|
|
502
457
|
` : `
|
|
@@ -507,30 +462,35 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
|
|
|
507
462
|
<div class="confirmation-line"></div>
|
|
508
463
|
</div>
|
|
509
464
|
`}
|
|
510
|
-
|
|
465
|
+
</div>
|
|
466
|
+
` : ''}
|
|
511
467
|
|
|
512
468
|
${annotationData && annotationsSet?.has('notes') && annotationData.additionalNotes && annotationData.additionalNotes.trim() ? `
|
|
513
|
-
<
|
|
514
|
-
|
|
469
|
+
<section class="additional-notes-section ${hasImage || annotationData.includeConfirmation === true ? 'notes-page' : ''}">
|
|
470
|
+
<h2 class="additional-notes-title">Additional Notes</h2>
|
|
471
|
+
<p class="additional-notes-body">${escapeHtml(annotationData.additionalNotes.trim())}</p>
|
|
472
|
+
</section>
|
|
473
|
+
` : ''}
|
|
515
474
|
</div>
|
|
516
475
|
` : ''}
|
|
517
476
|
|
|
518
477
|
</div>
|
|
519
|
-
</div>
|
|
520
|
-
|
|
521
|
-
<div class="footer">
|
|
522
|
-
<div class="footer-left">
|
|
523
|
-
<span>Notes formatted by Striae</span>
|
|
524
|
-
<img class="footer-brand-icon" src="${ICON_256}" alt="Striae icon" />
|
|
525
|
-
</div>
|
|
526
|
-
<div class="footer-center">
|
|
527
|
-
${userCompany ? userCompany : ''}
|
|
528
|
-
</div>
|
|
529
|
-
<div class="footer-right">
|
|
530
|
-
${notesUpdatedFormatted ? `Notes updated ${notesUpdatedFormatted}` : ''}
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
478
|
</body>
|
|
534
479
|
</html>
|
|
535
480
|
`;
|
|
536
|
-
};
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
export const getPdfOptions: ReportPdfOptionsBuilder = (data: PDFGenerationData) => buildRepeatedChromePdfOptions({
|
|
484
|
+
headerLeft: data.currentDate,
|
|
485
|
+
headerRight: data.caseNumber,
|
|
486
|
+
headerDetailLeft: [data.annotationData?.leftCase, data.annotationData?.leftItem].filter(Boolean).join(' / ')
|
|
487
|
+
? `Left Case / Item: ${[data.annotationData?.leftCase, data.annotationData?.leftItem].filter(Boolean).join(' / ')}`
|
|
488
|
+
: undefined,
|
|
489
|
+
headerDetailRight: [data.annotationData?.rightCase, data.annotationData?.rightItem].filter(Boolean).join(' / ')
|
|
490
|
+
? `Right Case / Item: ${[data.annotationData?.rightCase, data.annotationData?.rightItem].filter(Boolean).join(' / ')}`
|
|
491
|
+
: undefined,
|
|
492
|
+
footerLeft: 'Notes formatted by Striae',
|
|
493
|
+
footerCenter: data.userCompany,
|
|
494
|
+
footerRight: data.notesUpdatedFormatted ? `Notes updated ${data.notesUpdatedFormatted}` : undefined,
|
|
495
|
+
footerLeftImageSrc: ICON_256,
|
|
496
|
+
});
|