@gov-cy/govcy-express-services 1.8.2 → 1.9.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/README.md +77 -0
- package/package.json +1 -1
- package/src/govcyTaskListHandler.mjs +0 -0
- package/src/middleware/govcyFormsPostHandler.mjs +10 -0
- package/src/middleware/govcyPageHandler.mjs +6 -0
- package/src/middleware/govcyReviewPostHandler.mjs +6 -0
- package/src/middleware/govcyServiceEligibilityHandler.mjs +3 -3
- package/src/middleware/govcyTaskListHandler.mjs +560 -0
- package/src/resources/govcyResources.mjs +94 -10
- package/src/utils/govcyCustomPages.mjs +65 -23
- package/src/utils/govcyDataLayer.mjs +37 -12
- package/src/utils/govcySubmitData.mjs +10 -1
- package/src/utils/govcyTaskList.mjs +284 -0
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ This readme file describes the definition of these APIs and how to use them. If
|
|
|
34
34
|
- [cyLogin Access Policies](#cylogin-access-policies)
|
|
35
35
|
- [🧩 Dynamic services](#-dynamic-services)
|
|
36
36
|
- [Pages](#pages)
|
|
37
|
+
- [Task list pages](#task-list-pages)
|
|
37
38
|
- [Form vs static pages](#form-vs-static-pages)
|
|
38
39
|
- [Update my details pages](#update-my-details-pages)
|
|
39
40
|
- [Multiple things pages (repeating group of inputs)](#multiple-things-pages-repeating-group-of-inputs)
|
|
@@ -942,6 +943,82 @@ Lets break down the JSON config for this page:
|
|
|
942
943
|
|
|
943
944
|
------------------------------------------
|
|
944
945
|
|
|
946
|
+
#### Task list pages
|
|
947
|
+
|
|
948
|
+

|
|
949
|
+
|
|
950
|
+
Task lists act as navigation hubs that show the completion state of one or more pages in the journey. They are defined by adding a `taskList` block alongside `pageData`:
|
|
951
|
+
|
|
952
|
+
```json
|
|
953
|
+
{
|
|
954
|
+
"pageData": {
|
|
955
|
+
"url": "task-list",
|
|
956
|
+
"title": {
|
|
957
|
+
"el": "Η αίτησή σας",
|
|
958
|
+
"en": "Your application"
|
|
959
|
+
},
|
|
960
|
+
"layout": "layouts/govcyBase.njk",
|
|
961
|
+
"mainLayout": "two-third",
|
|
962
|
+
"nextPage": "options"
|
|
963
|
+
},
|
|
964
|
+
"taskList": {
|
|
965
|
+
"topElements": [
|
|
966
|
+
{
|
|
967
|
+
"element": "progressList",
|
|
968
|
+
"params": { "id": "steps", "current": "2", "total": "3" }
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
"element": "textElement",
|
|
972
|
+
"params": {
|
|
973
|
+
"type": "h1",
|
|
974
|
+
"text": {
|
|
975
|
+
"el": "Αίτηση για ISBN (Task List)",
|
|
976
|
+
"en": "Apply for an ISBN (Task List)"
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
],
|
|
981
|
+
"taskPages": [
|
|
982
|
+
"index",
|
|
983
|
+
"book-title",
|
|
984
|
+
"publishing-details",
|
|
985
|
+
"authors",
|
|
986
|
+
"book-theme",
|
|
987
|
+
"book-series",
|
|
988
|
+
"reprint",
|
|
989
|
+
"translation",
|
|
990
|
+
"book-form",
|
|
991
|
+
"physical-description",
|
|
992
|
+
"collaborators"
|
|
993
|
+
],
|
|
994
|
+
"hasBackLink": true,
|
|
995
|
+
"showSkippedTasks": true,
|
|
996
|
+
"linkToContinue": false
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
- `taskPages` is an ordered list of page URLs. Each entry becomes a GOV.CY task row with the page title, link, and status.
|
|
1002
|
+
- `showSkippedTasks` toggles whether conditionally hidden pages appear in the UI with a “Not applicable” tag.
|
|
1003
|
+
- `linkToContinue` (default `false`) enables the renderer’s “Continue without completing all sections” link when POST validation fails.
|
|
1004
|
+
- `topElements` and `hasBackLink` reuse the usual renderer elements and layout helpers so the page can include progress lists, headings, or lead paragraphs.
|
|
1005
|
+
|
|
1006
|
+
##### How statuses are calculated
|
|
1007
|
+
|
|
1008
|
+
Task list statuses are produced by the same validation logic as the Review page (`computePageTaskStatus`). Each page can be in one of four states: `NOT_STARTED`, `IN_PROGRESS`, `COMPLETED`, or `SKIPPED` (when a page is hidden by conditions or a custom page opts out). The helper inspects the stored session data per page type:
|
|
1009
|
+
|
|
1010
|
+
| Page type | When it becomes **NOT_STARTED** | When it is **IN_PROGRESS** | When it is **COMPLETED** |
|
|
1011
|
+
|-----------|---------------------------------|----------------------------|--------------------------|
|
|
1012
|
+
| **Standard form pages** | No `formData` captured yet | `formData` exists but field validation fails | Field validation succeeds (identical to Review) |
|
|
1013
|
+
| **Multiple things hubs** | No items and the hub hasn’t been posted yet | Fails per-item or min/max validation | All min/max checks pass or the user explicitly posts an empty-but-allowed hub |
|
|
1014
|
+
| **Update my details** | Data has not been fetched/posted | API result present but local validations fail | Data fetched and passes the same validation used on Review |
|
|
1015
|
+
| **Custom pages** | Missing status in custom session store | `setCustomPageTaskStatus` sets `IN_PROGRESS` | Custom implementation marks it `COMPLETED` |
|
|
1016
|
+
| **Nested task lists** | Any child task is `NOT_STARTED` | Mixed child statuses or explicit `IN_PROGRESS` | Every child task is `COMPLETED` or `SKIPPED` |
|
|
1017
|
+
|
|
1018
|
+
Because the logic reuses the Review pipeline, you only have to define task lists in JSON; they immediately reflect file uploads, conditional skips, posted multiple entries, and custom page states without extra code.
|
|
1019
|
+
|
|
1020
|
+
POSTing a task-list page re-runs these computations. If every section is complete, the user is redirected to `pageData.nextPage` (or `/review` when the request came from `?route=review`). Otherwise, the middleware stores a per-task error summary in the session so the renderer can show contextual guidance. Optional `linkToContinue` text and the global “Complete all sections before continuing.” message are also inserted automatically.
|
|
1021
|
+
|
|
945
1022
|
#### Form vs static pages
|
|
946
1023
|
|
|
947
1024
|
- If the `pageTemplate` includes a `form` element in the `main` section and `button` element, the system will treat it as form and will:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gov-cy/govcy-express-services",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
|
|
5
5
|
"author": "DMRID - DSF Team",
|
|
6
6
|
"license": "MIT",
|
|
File without changes
|
|
@@ -8,6 +8,7 @@ import { getFormData } from "../utils/govcyFormHandling.mjs"
|
|
|
8
8
|
import { evaluatePageConditions } from "../utils/govcyExpressions.mjs";
|
|
9
9
|
import { tempSaveIfConfigured } from "../utils/govcyTempSave.mjs";
|
|
10
10
|
import { validateMultipleThings } from "../utils/govcyMultipleThingsValidation.mjs";
|
|
11
|
+
import { handleTaskListPost } from "./govcyTaskListHandler.mjs";
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -32,6 +33,11 @@ export function govcyFormsPostHandler() {
|
|
|
32
33
|
return res.redirect(govcyResources.constructPageUrl(siteId, conditionResult.redirect));
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// ----- Task list handling (no <form> block)
|
|
37
|
+
if (page.taskList) {
|
|
38
|
+
return handleTaskListPost(req, res, next, { page, service, siteId, pageUrl });
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
// 🔍 Find the form definition inside `pageTemplate.sections`
|
|
36
42
|
let formElement = null;
|
|
37
43
|
for (const section of page.pageTemplate.sections) {
|
|
@@ -61,6 +67,9 @@ export function govcyFormsPostHandler() {
|
|
|
61
67
|
return res.redirect(govcyResources.constructErrorSummaryUrl(req.originalUrl));
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
// Mark hub as posted so task lists can treat optional hubs as complete
|
|
71
|
+
dataLayer.setPagePosted(req.session, siteId, pageUrl, true);
|
|
72
|
+
|
|
64
73
|
// No validation errors, proceed to next page
|
|
65
74
|
logger.debug("✅ multipleThings hub validated successfully:", items, req);
|
|
66
75
|
logger.info("✅ multipleThings hub validated successfully:", req.originalUrl);
|
|
@@ -126,3 +135,4 @@ export function govcyFormsPostHandler() {
|
|
|
126
135
|
}
|
|
127
136
|
};
|
|
128
137
|
}
|
|
138
|
+
|
|
@@ -6,6 +6,7 @@ import { logger } from "../utils/govcyLogger.mjs";
|
|
|
6
6
|
import { evaluatePageConditions } from "../utils/govcyExpressions.mjs";
|
|
7
7
|
import { govcyMultipleThingsHubHandler } from "./govcyMultipleThingsHubHandler.mjs";
|
|
8
8
|
import { govcyUpdateMyDetailsHandler } from "./govcyUpdateMyDetails.mjs";
|
|
9
|
+
import { govcyTaskListHandler } from "./govcyTaskListHandler.mjs";
|
|
9
10
|
// import {flattenContext, evaluateExpressionWithFlattening, evaluatePageConditions } from "../utils/govcyExpressions.mjs";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -39,6 +40,11 @@ export function govcyPageHandler() {
|
|
|
39
40
|
return res.redirect(`/${req.params.siteId}/${result.redirect}`);
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// ----- `taskList` handling
|
|
44
|
+
if (page.taskList) {
|
|
45
|
+
return govcyTaskListHandler(req, res, next, page, serviceCopy);
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
// ----- `updateMyDetails` handling
|
|
43
49
|
if (page.updateMyDetails) {
|
|
44
50
|
return await govcyUpdateMyDetailsHandler(req, res, next, page, serviceCopy);
|
|
@@ -38,6 +38,12 @@ export function govcyReviewPostHandler() {
|
|
|
38
38
|
updateMyDetailsData = dataLayer.getPageUpdateMyDetails(req.session, siteId, pageUrl);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// ----- taskList handling
|
|
42
|
+
// Task list pages are navigation wrappers; skip validation here
|
|
43
|
+
if (page.taskList) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
// ----- Conditional logic comes here
|
|
42
48
|
// ✅ Skip validation if page is conditionally excluded
|
|
43
49
|
const conditionResult = evaluatePageConditions(page, req.session, siteId, req);
|
|
@@ -26,8 +26,8 @@ export function govcyServiceEligibilityHandler(checkForForm = false) {
|
|
|
26
26
|
if (!pageUrl) pageUrl = "index";
|
|
27
27
|
// 🔍 Find the page by pageUrl
|
|
28
28
|
const page = getPageConfigData(service, pageUrl);
|
|
29
|
-
// ----- `updateMyDetails` handling.
|
|
30
|
-
if (!page.updateMyDetails) {
|
|
29
|
+
// ----- `updateMyDetails` & `taskList` handling. Always run eligibility for these types
|
|
30
|
+
if (!page.updateMyDetails && !page.taskList) {
|
|
31
31
|
if (!page || !page.pageTemplate) return next(); // Defensive: skip if no template
|
|
32
32
|
// Deep copy pageTemplate to avoid modifying the original
|
|
33
33
|
const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
|
|
@@ -106,4 +106,4 @@ export function govcyServiceEligibilityHandler(checkForForm = false) {
|
|
|
106
106
|
return next(err); // Pass error to govcyHttpErrorHandler
|
|
107
107
|
}
|
|
108
108
|
};
|
|
109
|
-
}
|
|
109
|
+
}
|