This commit is contained in:
Stan
2026-04-19 21:14:16 +02:00
parent 0c74a75126
commit 28d167f11f
42 changed files with 5681 additions and 55 deletions
+100
View File
@@ -0,0 +1,100 @@
/*
* Report and image-rules validation. These are pure functions with no DOM or
* state dependencies so they can be tested independently and kept in sync with
* server-side validation in src/routes/configRoutes.js (A7).
*/
/* ── Report validation ──────────────────────────────────────────────────── */
/*
* Returns an array of human-readable issue strings. The caller decides whether
* issues are informational or blocking.
*/
export function validateReport(report, template, attachments) {
const issues = [];
for (const section of template.definition.sections || []) {
for (const field of section.fields || []) {
const value = report.answers[field.id];
const required = Boolean(field.required || evaluateRequiredWhen(field.requiredWhen, report.answers));
if (field.type === 'attachment') {
const fieldAttachments = attachments.filter((item) => item.fieldId === field.id);
if (required && fieldAttachments.length === 0) {
issues.push(`${field.label}: at least one image is required.`);
}
continue;
}
if (required && isBlankValue(value, field.type)) {
issues.push(`${field.label}: value is required.`);
}
if (field.type === 'number' && value !== '' && value != null) {
if (Number.isNaN(Number(value))) {
issues.push(`${field.label}: number is invalid.`);
}
if (field.validation?.min !== undefined && Number(value) < field.validation.min) {
issues.push(`${field.label}: must be at least ${field.validation.min}.`);
}
}
}
}
return issues;
}
/* ── Image-rules validation (mirrors server-side logic in configRoutes) ── */
export function validateImageRulesPayload(payload) {
if (!payload.name) {
return 'Policy name is required';
}
if (!payload.allowedMimeTypes.length) {
return 'Add at least one MIME type';
}
if (!Number.isFinite(payload.maxFileSizeBytes) || payload.maxFileSizeBytes <= 0) {
return 'Max file size must be greater than 0';
}
if (!Number.isInteger(payload.maxWidthPx) || payload.maxWidthPx <= 0) {
return 'Max width must be a positive number';
}
if (!Number.isInteger(payload.maxHeightPx) || payload.maxHeightPx <= 0) {
return 'Max height must be a positive number';
}
if (!Number.isInteger(payload.jpegQuality) || payload.jpegQuality < 1 || payload.jpegQuality > 100) {
return 'JPEG quality must be between 1 and 100';
}
if (!Number.isInteger(payload.maxAttachmentsPerField) || payload.maxAttachmentsPerField <= 0) {
return 'Max attachments must be a positive number';
}
return null;
}
/* ── Helpers ────────────────────────────────────────────────────────────── */
export function evaluateRequiredWhen(requiredWhen, answers) {
if (!requiredWhen?.field) {
return false;
}
return answers[requiredWhen.field] === requiredWhen.equals;
}
export function isBlankValue(value, type) {
if (type === 'checkbox') {
return false;
}
return value === '' || value == null;
}