@gov-cy/govcy-express-services 1.2.0 → 1.3.0-alpha.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/README.md +1 -0
- package/package.json +6 -2
- package/src/index.mjs +190 -49
- package/src/middleware/govcyConfigSiteData.mjs +4 -0
- package/src/middleware/govcyFileDeleteHandler.mjs +41 -19
- package/src/middleware/govcyFileUpload.mjs +14 -1
- package/src/middleware/govcyFileViewHandler.mjs +20 -1
- package/src/middleware/govcyFormsPostHandler.mjs +76 -47
- package/src/middleware/govcyHttpErrorHandler.mjs +2 -0
- package/src/middleware/govcyMultipleThingsDeleteHandler.mjs +233 -0
- package/src/middleware/govcyMultipleThingsHubHandler.mjs +247 -0
- package/src/middleware/govcyMultipleThingsItemPage.mjs +460 -0
- package/src/middleware/govcyPageHandler.mjs +17 -5
- package/src/middleware/govcyPageRender.mjs +8 -0
- package/src/middleware/govcyReviewPageHandler.mjs +58 -32
- package/src/middleware/govcyReviewPostHandler.mjs +47 -23
- package/src/middleware/govcyRoutePageHandler.mjs +3 -1
- package/src/middleware/govcySuccessPageHandler.mjs +0 -5
- package/src/public/css/govcyExpress.css +33 -0
- package/src/public/img/Plus_24x24.svg +8 -0
- package/src/public/js/govcyFiles.js +92 -71
- package/src/resources/govcyResources.mjs +128 -0
- package/src/utils/govcyApiDetection.mjs +1 -1
- package/src/utils/govcyDataLayer.mjs +208 -67
- package/src/utils/govcyFormHandling.mjs +100 -27
- package/src/utils/govcyHandleFiles.mjs +58 -13
- package/src/utils/govcyMultipleThingsValidation.mjs +132 -0
- package/src/utils/govcySubmitData.mjs +221 -88
- package/src/utils/govcyValidator.mjs +19 -7
|
@@ -20,24 +20,53 @@ import * as govcyResources from "../resources/govcyResources.mjs";
|
|
|
20
20
|
* @param {string} lang The language
|
|
21
21
|
* @param {Object} fileInputElements The file input elements
|
|
22
22
|
* @param {string} routeParam The route parameter
|
|
23
|
+
* @param {string} mode The mode, either "single" (default), "add", or "edit"
|
|
24
|
+
* @param {number|null} index The index of the item being edited (null for single or add mode)
|
|
23
25
|
*/
|
|
24
|
-
export function populateFormData(
|
|
26
|
+
export function populateFormData(
|
|
27
|
+
formElements,
|
|
28
|
+
theData,
|
|
29
|
+
validationErrors,
|
|
30
|
+
store = {},
|
|
31
|
+
siteId = "",
|
|
32
|
+
pageUrl = "",
|
|
33
|
+
lang = "el",
|
|
34
|
+
fileInputElements = null,
|
|
35
|
+
routeParam = "",
|
|
36
|
+
mode = "single",
|
|
37
|
+
index = null
|
|
38
|
+
) {
|
|
25
39
|
const inputElements = ALLOWED_FORM_ELEMENTS;
|
|
26
40
|
const isRootCall = !fileInputElements;
|
|
41
|
+
let elementId = "";
|
|
42
|
+
let firstElementId = "";
|
|
43
|
+
|
|
27
44
|
if (isRootCall) {
|
|
28
45
|
fileInputElements = {};
|
|
29
46
|
}
|
|
30
47
|
// Recursively populate form data with session data
|
|
31
48
|
formElements.forEach(element => {
|
|
32
49
|
if (inputElements.includes(element.element)) {
|
|
50
|
+
// Get the element ID and field name
|
|
51
|
+
elementId = (element.element === "checkboxes" || element.element === "radios") // if checkboxes or radios
|
|
52
|
+
? `${element.params.id}-option-1` // use the id of the first option
|
|
53
|
+
: (element.element === "dateInput") //if dateInput
|
|
54
|
+
? `${element.params.id}_day` // use the id of the day input
|
|
55
|
+
: element.params.id; // else use the id of the element
|
|
56
|
+
|
|
33
57
|
const fieldName = element.params.name;
|
|
34
58
|
|
|
59
|
+
// Store the ID of the first input element (for error summary link)
|
|
60
|
+
if (!firstElementId) {
|
|
61
|
+
firstElementId = elementId;
|
|
62
|
+
}
|
|
63
|
+
|
|
35
64
|
// Handle `dateInput` separately
|
|
36
65
|
if (element.element === "dateInput") {
|
|
37
66
|
element.params.dayValue = theData[`${fieldName}_day`] || "";
|
|
38
67
|
element.params.monthValue = theData[`${fieldName}_month`] || "";
|
|
39
68
|
element.params.yearValue = theData[`${fieldName}_year`] || "";
|
|
40
|
-
|
|
69
|
+
//Handle `datePicker` separately
|
|
41
70
|
} else if (element.element === "datePicker") {
|
|
42
71
|
const val = theData[fieldName];
|
|
43
72
|
|
|
@@ -68,25 +97,49 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
68
97
|
} else if (element.element === "fileInput") {
|
|
69
98
|
// For fileInput, we change the element.element to "fileView" and set the
|
|
70
99
|
// fileId and sha256 from the session store
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
// const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, fieldName);
|
|
101
|
+
|
|
102
|
+
// 1) Prefer file from theData (could be draft in add mode, or item object in edit)
|
|
103
|
+
let fileData = theData[fieldName];
|
|
104
|
+
|
|
105
|
+
// 2) If not found, fall back to dataLayer (normal page behaviour)
|
|
106
|
+
if (!fileData) {
|
|
107
|
+
if (mode === "edit" && index !== null) {
|
|
108
|
+
// In edit mode, try to get the file for the specific item index
|
|
109
|
+
fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, fieldName, index);
|
|
110
|
+
} else {
|
|
111
|
+
// In single or add mode, get the file normally
|
|
112
|
+
fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, fieldName);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
75
116
|
if (fileData) {
|
|
76
117
|
element.element = "fileView";
|
|
77
118
|
element.params.fileId = fileData.fileId;
|
|
78
119
|
element.params.sha256 = fileData.sha256;
|
|
79
120
|
element.params.visuallyHiddenText = element.params.label;
|
|
80
|
-
|
|
81
|
-
|
|
121
|
+
|
|
122
|
+
// Build base path based on mode
|
|
123
|
+
let basePath = `/${siteId}/${pageUrl}`;
|
|
124
|
+
if (mode === "add") {
|
|
125
|
+
basePath += "/multiple/add";
|
|
126
|
+
} else if (mode === "edit" && index !== null) {
|
|
127
|
+
basePath += `/multiple/edit/${index}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// View link
|
|
131
|
+
element.params.viewHref = `${basePath}/view-file/${fieldName}`;
|
|
82
132
|
element.params.viewTarget = "_blank";
|
|
83
|
-
|
|
133
|
+
// Delete link (preserve ?route=review if present)
|
|
134
|
+
element.params.deleteHref = `${basePath}/delete-file/${fieldName}${(routeParam) ? `?route=${routeParam}` : ''}`
|
|
135
|
+
|
|
136
|
+
|
|
84
137
|
} else {
|
|
85
138
|
// TODO: Ask Andreas how to handle empty file inputs
|
|
86
139
|
element.params.value = "";
|
|
87
|
-
}
|
|
140
|
+
}
|
|
88
141
|
fileInputElements[fieldName] = element;
|
|
89
|
-
|
|
142
|
+
// Handle all other input elements (textInput, checkboxes, radios, etc.)
|
|
90
143
|
} else {
|
|
91
144
|
element.params.value = theData[fieldName] || "";
|
|
92
145
|
}
|
|
@@ -95,11 +148,11 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
95
148
|
if (validationErrors?.errors?.[fieldName]) {
|
|
96
149
|
element.params.error = validationErrors.errors[fieldName].message;
|
|
97
150
|
//populate the error summary
|
|
98
|
-
const elementId = (element.element === "checkboxes" || element.element === "radios") // if checkboxes or radios
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
151
|
+
// const elementId = (element.element === "checkboxes" || element.element === "radios") // if checkboxes or radios
|
|
152
|
+
// ? `${element.params.id}-option-1` // use the id of the first option
|
|
153
|
+
// : (element.element === "dateInput") //if dateInput
|
|
154
|
+
// ? `${element.params.id}_day` // use the id of the day input
|
|
155
|
+
// : element.params.id; // else use the id of the element
|
|
103
156
|
validationErrors.errorSummary.push({
|
|
104
157
|
link: `#${elementId}`,
|
|
105
158
|
text: validationErrors.errors[fieldName].message
|
|
@@ -111,8 +164,8 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
111
164
|
if (element.element === "radios" && element.params.items) {
|
|
112
165
|
element.params.items.forEach(item => {
|
|
113
166
|
if (item.conditionalElements) {
|
|
114
|
-
populateFormData(item.conditionalElements, theData, validationErrors,store, siteId
|
|
115
|
-
|
|
167
|
+
populateFormData(item.conditionalElements, theData, validationErrors, store, siteId, pageUrl, lang, fileInputElements, routeParam);
|
|
168
|
+
|
|
116
169
|
// Check if any conditional element has an error and add to the parent "conditionalHasErrors": true
|
|
117
170
|
if (item.conditionalElements.some(condEl => condEl.params?.error)) {
|
|
118
171
|
item.conditionalHasErrors = true;
|
|
@@ -121,6 +174,25 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
121
174
|
});
|
|
122
175
|
}
|
|
123
176
|
});
|
|
177
|
+
|
|
178
|
+
// 🔴 Handle _global validation errors (collection-level, not tied to a field)
|
|
179
|
+
if (isRootCall && validationErrors?.errors?._global) {
|
|
180
|
+
validationErrors.errorSummary = validationErrors.errorSummary || [];
|
|
181
|
+
|
|
182
|
+
// Decide where the link should point
|
|
183
|
+
let linkTarget = `#${firstElementId}`; // default anchor at top of the form
|
|
184
|
+
if (validationErrors.errors._global.pageUrl) {
|
|
185
|
+
// If pageUrl is provided (e.g. for max items), point back to hub
|
|
186
|
+
linkTarget = `${validationErrors.errors._global.pageUrl}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Push into the error summary
|
|
190
|
+
validationErrors.errorSummary.push({
|
|
191
|
+
link: linkTarget,
|
|
192
|
+
text: validationErrors.errors._global.message
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
124
196
|
// add file input elements's definition in js object
|
|
125
197
|
if (isRootCall && Object.keys(fileInputElements).length > 0) {
|
|
126
198
|
const scriptTag = `
|
|
@@ -155,9 +227,10 @@ export function populateFormData(formElements, theData, validationErrors, store
|
|
|
155
227
|
* @param {Object} store - The session store .
|
|
156
228
|
* @param {string} siteId - The site ID .
|
|
157
229
|
* @param {string} pageUrl - The page URL .
|
|
230
|
+
* @param {number|null} index - The index of the item being edited for multiple items
|
|
158
231
|
* @returns {Object} filteredData - The filtered form data.
|
|
159
232
|
*/
|
|
160
|
-
export function getFormData(elements, formData, store = {}, siteId = "", pageUrl = "") {
|
|
233
|
+
export function getFormData(elements, formData, store = {}, siteId = "", pageUrl = "", index = null) {
|
|
161
234
|
const filteredData = {};
|
|
162
235
|
elements.forEach(element => {
|
|
163
236
|
const { name } = element.params || {};
|
|
@@ -174,12 +247,12 @@ export function getFormData(elements, formData, store = {}, siteId = "", pageUrl
|
|
|
174
247
|
if (item.conditionalElements) {
|
|
175
248
|
Object.assign(
|
|
176
249
|
filteredData,
|
|
177
|
-
getFormData(item.conditionalElements, formData, store, siteId, pageUrl)
|
|
250
|
+
getFormData(item.conditionalElements, formData, store, siteId, pageUrl, index)
|
|
178
251
|
);
|
|
179
252
|
}
|
|
180
253
|
});
|
|
181
254
|
}
|
|
182
|
-
|
|
255
|
+
|
|
183
256
|
}
|
|
184
257
|
// Handle dateInput
|
|
185
258
|
else if (element.element === "dateInput") {
|
|
@@ -189,13 +262,13 @@ export function getFormData(elements, formData, store = {}, siteId = "", pageUrl
|
|
|
189
262
|
filteredData[`${name}_day`] = day !== undefined && day !== null ? day : "";
|
|
190
263
|
filteredData[`${name}_month`] = month !== undefined && month !== null ? month : "";
|
|
191
264
|
filteredData[`${name}_year`] = year !== undefined && year !== null ? year : "";
|
|
192
|
-
|
|
265
|
+
// handle fileInput
|
|
193
266
|
} else if (element.element === "fileInput") {
|
|
194
267
|
// fileInput elements are already stored in the store when it was uploaded
|
|
195
268
|
// so we just need to check if the file exists in the dataLayer in the store and add it the filteredData
|
|
196
269
|
// unneeded handle of `Attachment` at the end
|
|
197
270
|
// const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, name + "Attachment");
|
|
198
|
-
const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, name);
|
|
271
|
+
const fileData = dataLayer.getFormDataValue(store, siteId, pageUrl, name, index);
|
|
199
272
|
if (fileData) {
|
|
200
273
|
// unneeded handle of `Attachment` at the end
|
|
201
274
|
// filteredData[name + "Attachment"] = fileData;
|
|
@@ -206,7 +279,7 @@ export function getFormData(elements, formData, store = {}, siteId = "", pageUrl
|
|
|
206
279
|
// filteredData[name + "Attachment"] = ""; // or handle as needed
|
|
207
280
|
filteredData[name] = ""; // or handle as needed
|
|
208
281
|
}
|
|
209
|
-
|
|
282
|
+
// Handle other elements (e.g., textInput, textArea, datePicker)
|
|
210
283
|
} else {
|
|
211
284
|
filteredData[name] = formData[name] !== undefined && formData[name] !== null ? formData[name] : "";
|
|
212
285
|
}
|
|
@@ -216,9 +289,9 @@ export function getFormData(elements, formData, store = {}, siteId = "", pageUrl
|
|
|
216
289
|
// Object.assign(filteredData, getFormData(element.conditionalElements, formData));
|
|
217
290
|
// }
|
|
218
291
|
}
|
|
219
|
-
|
|
292
|
+
|
|
220
293
|
});
|
|
221
|
-
|
|
294
|
+
|
|
222
295
|
|
|
223
296
|
return filteredData;
|
|
224
|
-
}
|
|
297
|
+
}
|
|
@@ -18,9 +18,21 @@ import { logger } from './govcyLogger.mjs';
|
|
|
18
18
|
* @param {string} opts.pageUrl - Page URL
|
|
19
19
|
* @param {string} opts.elementName - Name of file input
|
|
20
20
|
* @param {object} opts.file - File object from multer (req.file)
|
|
21
|
+
* @param {string} opts.mode - Upload mode ("single" | "multipleThingsDraft" | "multipleThingsEdit")
|
|
22
|
+
* @param {number|null} opts.index - Numeric index for edit mode (0-based), or null
|
|
21
23
|
* @returns {Promise<{ status: number, data?: object, errorMessage?: string }>}
|
|
22
24
|
*/
|
|
23
|
-
export async function handleFileUpload({
|
|
25
|
+
export async function handleFileUpload({
|
|
26
|
+
service,
|
|
27
|
+
store,
|
|
28
|
+
siteId,
|
|
29
|
+
pageUrl,
|
|
30
|
+
elementName,
|
|
31
|
+
file,
|
|
32
|
+
mode = "single", // "single" | "multipleThingsDraft" | "multipleThingsEdit"
|
|
33
|
+
index = null // numeric index for edit mode
|
|
34
|
+
}) {
|
|
35
|
+
|
|
24
36
|
try {
|
|
25
37
|
// Validate essentials
|
|
26
38
|
// Early exit if key things are missing
|
|
@@ -187,23 +199,56 @@ export async function handleFileUpload({ service, store, siteId, pageUrl, elemen
|
|
|
187
199
|
|
|
188
200
|
// ✅ Success
|
|
189
201
|
// Store the file metadata in the session store
|
|
190
|
-
|
|
191
|
-
// dataLayer.storePageDataElement(store, siteId, pageUrl, elementName+"Attachment", {
|
|
192
|
-
dataLayer.storePageDataElement(store, siteId, pageUrl, elementName, {
|
|
202
|
+
const metadata = {
|
|
193
203
|
sha256: response.Data.sha256,
|
|
194
204
|
fileId: response.Data.fileId,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
fileName: response.Data.fileName || file.originalname || "",
|
|
206
|
+
mimeType: response.Data.contentType || file.mimetype || "",
|
|
207
|
+
fileSize: response.Data.fileSize || file.size || 0,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (mode === "multipleThingsDraft") {
|
|
211
|
+
// Store in draft object
|
|
212
|
+
let draft = dataLayer.getMultipleDraft(store, siteId, pageUrl);
|
|
213
|
+
if (!draft) draft = {};
|
|
214
|
+
draft[elementName] = {
|
|
201
215
|
sha256: response.Data.sha256,
|
|
202
|
-
filename: response.Data.fileName || '',
|
|
203
216
|
fileId: response.Data.fileId,
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
};
|
|
218
|
+
dataLayer.setMultipleDraft(store, siteId, pageUrl, draft);
|
|
219
|
+
logger.debug(`Stored file metadata in draft for ${siteId}/${pageUrl}`, metadata);
|
|
220
|
+
}
|
|
221
|
+
else if (mode === "multipleThingsEdit") {
|
|
222
|
+
// Store in item array
|
|
223
|
+
let items = dataLayer.getPageData(store, siteId, pageUrl);
|
|
224
|
+
if (!Array.isArray(items)) items = [];
|
|
225
|
+
if (index !== null && index >= 0 && index < items.length) {
|
|
226
|
+
items[index][elementName] = {
|
|
227
|
+
sha256: response.Data.sha256,
|
|
228
|
+
fileId: response.Data.fileId,
|
|
229
|
+
};
|
|
230
|
+
dataLayer.storePageData(store, siteId, pageUrl, items);
|
|
231
|
+
logger.debug(`Stored file metadata in item index=${index} for ${siteId}/${pageUrl}`, metadata);
|
|
232
|
+
} else {
|
|
233
|
+
return {
|
|
234
|
+
status: 400,
|
|
235
|
+
dataStatus: 412,
|
|
236
|
+
errorMessage: `Invalid index for multipleThingsEdit (index=${index})`
|
|
237
|
+
};
|
|
206
238
|
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Default: single-page behaviour
|
|
242
|
+
dataLayer.storePageDataElement(store, siteId, pageUrl, elementName, {
|
|
243
|
+
sha256: response.Data.sha256,
|
|
244
|
+
fileId: response.Data.fileId,
|
|
245
|
+
});
|
|
246
|
+
logger.debug(`Stored file metadata in single mode for ${siteId}/${pageUrl}`, metadata);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
status: 200,
|
|
251
|
+
data: metadata
|
|
207
252
|
};
|
|
208
253
|
|
|
209
254
|
} catch (err) {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// utils/govcyMultipleThingsValidation.mjs
|
|
2
|
+
import { validateFormElements } from "./govcyValidator.mjs";
|
|
3
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
4
|
+
import { getPageConfigData } from "../utils/govcyLoadConfigData.mjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate multipleThings items against the page's form definition.
|
|
8
|
+
* @param {object} page the page configuration object
|
|
9
|
+
* @param {array} items the array of items to validate
|
|
10
|
+
* @param {string} lang the language code for error messages
|
|
11
|
+
* @returns {object} errors object containing validation errors, if any
|
|
12
|
+
*/
|
|
13
|
+
export function validateMultipleThings(page, items, lang) {
|
|
14
|
+
const errors = {};
|
|
15
|
+
|
|
16
|
+
// 1. Min check
|
|
17
|
+
if (items.length < page.multipleThings.min) {
|
|
18
|
+
// Deep copy page title (so we don’t mutate template)
|
|
19
|
+
let minMsg = JSON.parse(JSON.stringify(govcyResources.staticResources.text.multipleThingsMinMessage));
|
|
20
|
+
|
|
21
|
+
// Replace label placeholders on page title
|
|
22
|
+
for (const lang of Object.keys(minMsg)) {
|
|
23
|
+
minMsg[lang] = minMsg[lang].replace("{{min}}", page.multipleThings.min);
|
|
24
|
+
}
|
|
25
|
+
errors._global = {
|
|
26
|
+
message: minMsg,
|
|
27
|
+
link: "#addNewItem0"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return errors; // early exit
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 2. Max check (rare, but safe)
|
|
34
|
+
if (items.length > page.multipleThings.max) {
|
|
35
|
+
// Deep copy page title (so we don’t mutate template)
|
|
36
|
+
let maxMsg = JSON.parse(JSON.stringify(govcyResources.staticResources.text.multipleThingsMaxMessage));
|
|
37
|
+
|
|
38
|
+
// Replace label placeholders on page title
|
|
39
|
+
for (const lang of Object.keys(maxMsg)) {
|
|
40
|
+
maxMsg[lang] = maxMsg[lang].replace("{{max}}", page.multipleThings.max);
|
|
41
|
+
}
|
|
42
|
+
errors._global = {
|
|
43
|
+
message: maxMsg,
|
|
44
|
+
link: "#multipleThingsList"
|
|
45
|
+
};
|
|
46
|
+
return errors; // early exit
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 3. Per-item validation
|
|
50
|
+
items.forEach((item, idx) => {
|
|
51
|
+
const formElement = page.pageTemplate.sections
|
|
52
|
+
.flatMap(s => s.elements)
|
|
53
|
+
.find(el => el.element === "form");
|
|
54
|
+
if (!formElement) return; // safety
|
|
55
|
+
|
|
56
|
+
const vErrors = validateFormElements(formElement.params.elements, item);
|
|
57
|
+
if (Object.keys(vErrors).length > 0) {
|
|
58
|
+
errors[idx] = vErrors;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return errors;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Normalize validation errors into a summary list array.
|
|
68
|
+
* Works for both hub- and review-level errors.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} service - The full service configuration object
|
|
71
|
+
* @param {object} hubErrors - Validation errors object (from dataLayer)
|
|
72
|
+
* @param {string} siteId - Current site id
|
|
73
|
+
* @param {string} pageUrl - Page url (for hub links)
|
|
74
|
+
* @param {object} req - Express request (for lang + route info)
|
|
75
|
+
* @param {string} route - Current route (e.g. "review" or "")
|
|
76
|
+
* @param {boolean} isHub - Whether this is for the hub page (true) or review page (false)
|
|
77
|
+
*
|
|
78
|
+
* @returns {Array<{text: object, link: string}>}
|
|
79
|
+
*/
|
|
80
|
+
export function buildMultipleThingsValidationSummary(service, hubErrors, siteId, pageUrl, req, route = "", isHub = true) {
|
|
81
|
+
let validationErrors = [];
|
|
82
|
+
|
|
83
|
+
// For each error
|
|
84
|
+
for (const [key, err] of Object.entries(hubErrors)) {
|
|
85
|
+
let pageTitle = { en: "", el: "", tr: "" };
|
|
86
|
+
if (!isHub) {
|
|
87
|
+
// 🔍 Find the page by pageUrl
|
|
88
|
+
const page = getPageConfigData(service, pageUrl);
|
|
89
|
+
for (const lang of Object.keys(page.multipleThings.listPage.title)) {
|
|
90
|
+
pageTitle[lang] = (page.multipleThings.listPage.title[lang] || "") + " - ";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (key === "_global") {
|
|
95
|
+
let msg = err.message;
|
|
96
|
+
// Replace label placeholders on page title
|
|
97
|
+
for (const lang of Object.keys(msg)) {
|
|
98
|
+
msg[lang] = pageTitle[lang]
|
|
99
|
+
+ (msg[lang] || '');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Add to validationErrors array
|
|
103
|
+
validationErrors.push({
|
|
104
|
+
text: msg,
|
|
105
|
+
link: (isHub
|
|
106
|
+
? err.link || `#multipleThingsList`
|
|
107
|
+
: `/${siteId}/${pageUrl}` + (route === "review" ? "?route=review" : ""))
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
// Deep copy page title (so we don’t mutate template)
|
|
111
|
+
let msg = JSON.parse(JSON.stringify(govcyResources.staticResources.text.multipleThingsItemsValidationPrefix));
|
|
112
|
+
const idx = parseInt(key, 10);
|
|
113
|
+
|
|
114
|
+
for (const fieldErr of Object.values(err)) {
|
|
115
|
+
// Replace label placeholders on page title
|
|
116
|
+
for (const lang of Object.keys(msg)) {
|
|
117
|
+
msg[lang] = pageTitle[lang]
|
|
118
|
+
+ msg[lang].replace("{{index}}", idx + 1)
|
|
119
|
+
+ (fieldErr.message?.[req.globalLang] || '');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Add to validationErrors array
|
|
123
|
+
validationErrors.push({
|
|
124
|
+
text: msg,
|
|
125
|
+
link: `/${siteId}/${pageUrl}/multiple/edit/${idx}${route === "review" ? "?route=review" : ""}`
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return validationErrors;
|
|
132
|
+
}
|