@gov-cy/govcy-express-services 1.3.0-alpha → 1.3.0-alpha.2
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 +250 -371
- package/package.json +6 -3
- package/src/middleware/govcyFileDeleteHandler.mjs +8 -0
- package/src/middleware/govcyFileViewHandler.mjs +5 -0
- package/src/middleware/govcyHttpErrorHandler.mjs +2 -0
- package/src/middleware/govcyMultipleThingsDeleteHandler.mjs +2 -2
- package/src/middleware/govcyPageRender.mjs +2 -0
- package/src/middleware/govcyRoutePageHandler.mjs +2 -0
- package/src/public/css/govcyExpress.css +33 -0
- package/src/resources/govcyResources.mjs +16 -6
- package/src/utils/govcyFormHandling.mjs +7 -1
- package/src/utils/govcyHandleFiles.mjs +10 -0
- package/src/utils/govcyValidator.mjs +30 -4
package/README.md
CHANGED
|
@@ -31,6 +31,11 @@ The APIs used for submission, temporary save and file uploads are not part of th
|
|
|
31
31
|
- [📦 Full installation guide](#-full-installation-guide)
|
|
32
32
|
- [🛠️ Usage](#%EF%B8%8F-usage)
|
|
33
33
|
- [🧩 Dynamic services](#-dynamic-services)
|
|
34
|
+
- [Pages](#pages)
|
|
35
|
+
- [Form vs static pages](#form-vs-static-pages)
|
|
36
|
+
- [Multiple things pages (repeating group of inputs)](#multiple-things-pages-repeating-group-of-inputs)
|
|
37
|
+
- [Review page](#review-page)
|
|
38
|
+
- [Success page](#success-page)
|
|
34
39
|
- [🛡️ Site eligibility checks](#%EF%B8%8F-site-eligibility-checks)
|
|
35
40
|
- [📤 Site submissions](#-site-submissions)
|
|
36
41
|
- [✅ Input validations](#-input-validations)
|
|
@@ -854,7 +859,7 @@ Here's an example of a page defined in the JSON file:
|
|
|
854
859
|
|
|
855
860
|
The above `page` JSON generates a page that looks like the following screenshot:
|
|
856
861
|
|
|
857
|
-

|
|
862
|
+

|
|
858
863
|
|
|
859
864
|
The JSON structure is based on the [govcy-frontend-renderer's JSON template](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/README.md#json-template-example).
|
|
860
865
|
|
|
@@ -871,7 +876,7 @@ Lets break down the JSON config for this page:
|
|
|
871
876
|
- `sections` is an array of sections, which is an array of elements. Sections allowed: `beforeMain`, `main`, `afterMain`.
|
|
872
877
|
- `elements` is an array of elements for the said section. Seem more details on the [govcy-frontend-renderer's design elements documentation](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md).
|
|
873
878
|
|
|
874
|
-
|
|
879
|
+
#### Form vs static pages
|
|
875
880
|
|
|
876
881
|
- If the `pageTemplate` includes a `form` element in the `main` section and `button` element, the system will treat it as form and will:
|
|
877
882
|
- Perform the eligibility checks
|
|
@@ -904,6 +909,226 @@ The [start page](https://gov-cy.github.io/govcy-design-system-docs/patterns/serv
|
|
|
904
909
|
- Check out the [govcy-frontend-renderer's design elements](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md) for more details on the supported elements and their parameters.
|
|
905
910
|
- Check out the [input validations section](#-input-validations) for more details on how to add validations to the JSON file.
|
|
906
911
|
|
|
912
|
+
#### Multiple things pages (repeating group of inputs)
|
|
913
|
+

|
|
914
|
+
|
|
915
|
+
Some services need to collect **multiple entries of the same structure**, for example, academic qualifications, addresses, or dependents. The framework supports this through the `multipleThings` block in a page config. It uses the same input method of a normal form page, but the user can add more than one entries.
|
|
916
|
+
|
|
917
|
+
When enabled, the framework automatically generates a **hub page** when the user visits the:
|
|
918
|
+
|
|
919
|
+
```ruby
|
|
920
|
+
/:siteId/:pageUrl
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
This hub page allows users to:
|
|
924
|
+
- 📋 **View the list** of entries (hub page)
|
|
925
|
+
- ➕ **Add** a new entry
|
|
926
|
+
- ✏️ **Change** an existing entry
|
|
927
|
+
- ❌ **Remove** an existing entry
|
|
928
|
+
- ✅ **Continue** when they are finished
|
|
929
|
+
|
|
930
|
+
**Multiple things - example JSON config**
|
|
931
|
+
Here’s how to define a page that collects multiple academic qualifications:
|
|
932
|
+
|
|
933
|
+
```json
|
|
934
|
+
{
|
|
935
|
+
"pageData": {
|
|
936
|
+
"url": "qualifications", // Page URL
|
|
937
|
+
"title": { // Page title of the input pages (add, edit)
|
|
938
|
+
"el": "Ακαδημαϊκά και επαγγελματικά προσόντα",
|
|
939
|
+
"en": "Academic and professional qualifications",
|
|
940
|
+
"tr": ""
|
|
941
|
+
},
|
|
942
|
+
"layout": "layouts/govcyBase.njk", // Page layout for all pages (add, edit, hub page)
|
|
943
|
+
"mainLayout": "two-third", // Page main layout for all pages (add, edit, hub page)
|
|
944
|
+
"nextPage": "memberships" // The next page's URL
|
|
945
|
+
},
|
|
946
|
+
"pageTemplate" : { // Page template for the input pages (add, edit)
|
|
947
|
+
...
|
|
948
|
+
},
|
|
949
|
+
"multipleThings": { // Multiple things configuration
|
|
950
|
+
"min": 1, // the minimum number of entries
|
|
951
|
+
"max": 5, // the maximum number of entries
|
|
952
|
+
"dedupe": true, // whether to check for duplicates
|
|
953
|
+
"itemTitleTemplate": "{{title | trim}} - {{year | trim}}", // the title template for each entry
|
|
954
|
+
"listPage": { // the hub page
|
|
955
|
+
"title": { // the hub page title
|
|
956
|
+
"el": "Ακαδημαϊκά προσόντα",
|
|
957
|
+
"en": "Academic details"
|
|
958
|
+
},
|
|
959
|
+
"topElements": [ // the hub page top elements
|
|
960
|
+
{
|
|
961
|
+
"element": "progressList",
|
|
962
|
+
"params": {
|
|
963
|
+
"id": "progress",
|
|
964
|
+
"current": "2",
|
|
965
|
+
"total": "6",
|
|
966
|
+
"showSteps": true
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
"element": "textElement",
|
|
971
|
+
"params": {
|
|
972
|
+
"id": "header",
|
|
973
|
+
"type": "h1",
|
|
974
|
+
"text": {
|
|
975
|
+
"el": "Ποια είναι τα προσόντα σας;",
|
|
976
|
+
"en": "What are your qualifications?",
|
|
977
|
+
"tr": ""
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
"element": "textElement",
|
|
983
|
+
"params": {
|
|
984
|
+
"id": "instructions",
|
|
985
|
+
"type": "p",
|
|
986
|
+
"text": {
|
|
987
|
+
"el": "Προσθέστε τουλάχιστον ένα τα ακαδημαϊκό ή επαγγελματικό προσόν για να συνεχίσετε. Μπορείτε να προσθέσετε μέχρι 5.",
|
|
988
|
+
"en": "Add at least one academic or professional qualifications to proceed. You can up to 5."
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
],
|
|
993
|
+
"emptyState": { // the hub page empty state
|
|
994
|
+
"en": "No qualifications added yet.",
|
|
995
|
+
"el": "Δεν έχετε προσθέσει ακόμη προσόντα."
|
|
996
|
+
},
|
|
997
|
+
"addButtonText": { // the hub page add button text
|
|
998
|
+
"en": "➕ Add qualifications",
|
|
999
|
+
"el": "➕ Προσθήκη προσόντος"
|
|
1000
|
+
},
|
|
1001
|
+
"addButtonPlacement": "top", // the hub page add button placement
|
|
1002
|
+
"continueButtonText": { // the hub page continue button text
|
|
1003
|
+
"en": "Save and continue",
|
|
1004
|
+
"el": "Αποθήκευση και συνέχεια"
|
|
1005
|
+
},
|
|
1006
|
+
"hasBackLink": true // whether the hub page has a back link
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
Lets break down the JSON config for multiple things:
|
|
1013
|
+
- **multipleThings** are the page's definition for repeated group of inputs
|
|
1014
|
+
- `multipleThings.min` : The minimum items rule
|
|
1015
|
+
- `multipleThings.max` : The maximum items rule
|
|
1016
|
+
- `multipleThings.dedupe`: When `true` prevents duplicates, using the `itemTitleTemplate` template. Optional with default value `false`
|
|
1017
|
+
- `multipleThings.itemTitleTemplate`: The template (in Nunjucks form) used to display the items' on a list in the hub, review, success pages and email. Also used by the `dedupe` to compare for duplicates
|
|
1018
|
+
- `multipleThings.listPage`: The definition the list (hub) page that shows the list of items, with the actions (add, edit, delete, continue)
|
|
1019
|
+
- `multipleThings.listPage.title`: The title used in the hub page's meta data
|
|
1020
|
+
- `multipleThings.listPage.topElements`: The elements to be displayed on the top of the page (more details on the [govcy-frontend-renderer's design elements documentation](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md)):
|
|
1021
|
+
- `multipleThings.listPage.emptyState`: The message displayed when the count of items == 0. Optional, when not defined generic text is shown
|
|
1022
|
+
- `multipleThings.listPage.addButtonText`: The text displayed on the add link. Optional, when not defined generic text is shown
|
|
1023
|
+
- `multipleThings.listPage.addButtonPlacement`: Where to show the add link. Can be `top`, `bottom` or `both`. Optional, default is `bottom`
|
|
1024
|
+
- `multipleThings.listPage.continueButtonText`: The text displayed on the continue button text. Optional, default is `Continue`, `Συνέχεια`
|
|
1025
|
+
- `multipleThings.listPage.hasBackLink`: When `true` shows the standard back button. Optional, default is `true
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
**Multiple things - How it works**
|
|
1029
|
+
With multiple things pages, the system collects multiple set of the same type of data. The set of data are collected by a single page (similar to a normal page). The multiple things consist of 4 type of pages, the `hub`, `add`, `edit` and `delete` pages
|
|
1030
|
+
|
|
1031
|
+
**Hub page**: When the user navigates through the service at `/:siteId/:pageUrl` (either coming from _review_ or _linear_ flow), the system shows the hub page. The hub page in general shows the list of entries, add links, continue button and validates the input (see below more details)
|
|
1032
|
+
|
|
1033
|
+
`Hub page add links`: If the user has not reached the maximum number of allowed entries, the system shows add links on the hub page. There is an option for a custom add link text with `multipleThings.listPage.addButtonText`. There is an option for `top`, `bottom` or `both` placement with `multipleThings.listPage.addButtonPlacement`
|
|
1034
|
+
|
|
1035
|
+

|
|
1036
|
+
|
|
1037
|
+
`Hub empty state`: When no data are yet entered the hub shows an empty state message with an add link. There is an option for a custom empty state
|
|
1038
|
+
|
|
1039
|
+

|
|
1040
|
+
|
|
1041
|
+
`Hub list state`: When data exist, they are shown as a list with `change` and `delete` links. The list is created based on an the `multipleThings.itemTitleTemplate`, for example ` {{institution}} - {{title}} - {{year}}`
|
|
1042
|
+
|
|
1043
|
+

|
|
1044
|
+
|
|
1045
|
+
`Hub max state`: If the data entries reached the maximum limit, the `add links` are removed and a `max limit reached` message is shown.
|
|
1046
|
+
|
|
1047
|
+

|
|
1048
|
+
|
|
1049
|
+
`Hub page continue`: on Continue the system continues to the next page defined in the `pageData.nextPage`. If user came from the review page, it returns to the review.
|
|
1050
|
+
|
|
1051
|
+
`Hub page validations`: The hub page validates the following when the continue button is pressed.
|
|
1052
|
+
- `Minimum entries` have been entered. This is based on the `multipleThings.min`.
|
|
1053
|
+
- `Maximum limit` has not been exceeded.. This is based on the `multipleThings.max`.
|
|
1054
|
+
- `All entries validations pass`. This is based on the `pageTemplate` element validations.
|
|
1055
|
+
|
|
1056
|
+

|
|
1057
|
+
|
|
1058
|
+
**Add pages**
|
|
1059
|
+
|
|
1060
|
+

|
|
1061
|
+
|
|
1062
|
+
- Accessible through the add link or through the `/:siteId/:pageUrl/multiple/add` route
|
|
1063
|
+
- User is shown the item page (e.g. `academic-details`)
|
|
1064
|
+
- Files' data are stored in a **draft** until they click `Continue`
|
|
1065
|
+
- On `Continue`, the data and draft is pushed into the list
|
|
1066
|
+
- Behaves like a normal form page
|
|
1067
|
+
- On `Continue` if there are no validation errors, the user is navigated back to the hub and the added data is displayed on the list. If the user’s journey started from the review page, after clicking continue on the hub, it goes back to the review.
|
|
1068
|
+
- It performs all configured input validations defined PLUS
|
|
1069
|
+
- `Maximum limit` has not been exceeded.
|
|
1070
|
+
- `Dedupe validation`. If defined in the configuration the service checks if there is an identical entry. The check is made based on the item title template
|
|
1071
|
+
|
|
1072
|
+
**Edit pages**
|
|
1073
|
+
|
|
1074
|
+

|
|
1075
|
+
|
|
1076
|
+
- Accessible through the change link or through the `/:siteId/:pageUrl/multiple/edit/:index` route
|
|
1077
|
+
- User selects an existing item from the hub page
|
|
1078
|
+
- The item page loads pre-filled data
|
|
1079
|
+
- Behaves like a normal form page
|
|
1080
|
+
- On `Continue` if there are no validation errors, the entry is updated and the user is navigated back to the hub and the updated data is displayed on the list. If the user’s journey started from the review page, after clicking continue on the hub, it goes back to the review.
|
|
1081
|
+
- It performs all configured input validations defined PLUS
|
|
1082
|
+
- `Dedupe validation`. If defined in the configuration the service checks if there is an identical entry. The check is made based on the item title template
|
|
1083
|
+
|
|
1084
|
+
**Delete pages**
|
|
1085
|
+
|
|
1086
|
+

|
|
1087
|
+
|
|
1088
|
+
- Accessible through the remove link or through the `/:siteId/:pageUrl/multiple/delete/:index` route
|
|
1089
|
+
- A confirmation page is shown
|
|
1090
|
+
- On _Yes_, the item is removed from the list
|
|
1091
|
+
|
|
1092
|
+
**Review & Success pages**
|
|
1093
|
+
|
|
1094
|
+

|
|
1095
|
+
|
|
1096
|
+
- Each item is displayed in the summary list, grouped under the hub’s title
|
|
1097
|
+
|
|
1098
|
+
**Multiple things - data storage**
|
|
1099
|
+
|
|
1100
|
+
Form data for a `multipleThings` page is stored as an **array** in the session data layer:
|
|
1101
|
+
```json
|
|
1102
|
+
{
|
|
1103
|
+
"academic-details": {
|
|
1104
|
+
"formData": [
|
|
1105
|
+
{
|
|
1106
|
+
"title": "BSc Computer Science",
|
|
1107
|
+
"academicFile": {
|
|
1108
|
+
"fileId": "12345",
|
|
1109
|
+
"sha256": "abcdef..."
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
"title": "MSc Information Systems",
|
|
1114
|
+
"academicFile": {
|
|
1115
|
+
"fileId": "67890",
|
|
1116
|
+
"sha256": "ghijkl..."
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
]
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
**Notes on multiple things**
|
|
1125
|
+
|
|
1126
|
+
- Each `multipleThings` page must define its own item page (with fields and validations), used for the `add` add `edit` routes.
|
|
1127
|
+
- The hub page is generated automatically. It uses the `multipleThings.listPage` for the UI
|
|
1128
|
+
- Empty state is handled automatically.
|
|
1129
|
+
- `multipleDraft` is used internally to store file's data while the user is adding a new item.
|
|
1130
|
+
|
|
1131
|
+
|
|
907
1132
|
#### Review page
|
|
908
1133
|
|
|
909
1134
|
The `review` page is automatically generated by the project and includes the following sections:
|
|
@@ -914,7 +1139,7 @@ The `review` page is automatically generated by the project and includes the fol
|
|
|
914
1139
|
|
|
915
1140
|
Here's an example screenshot of review page
|
|
916
1141
|
|
|
917
|
-

|
|
1142
|
+

|
|
918
1143
|
|
|
919
1144
|
When the user clicks a change link, the user is redirected to the corresponding page in the service. After the user clicks on `continue` button the user is redirected back to the `review` page.
|
|
920
1145
|
|
|
@@ -930,7 +1155,7 @@ The `success` page is automatically generated by the project, is accessible only
|
|
|
930
1155
|
|
|
931
1156
|
Here's an example screenshot of success page
|
|
932
1157
|
|
|
933
|
-

|
|
1158
|
+

|
|
934
1159
|
|
|
935
1160
|
### 🛡️ Site eligibility checks
|
|
936
1161
|
|
|
@@ -1101,6 +1326,8 @@ HTTP/1.1 200 OK
|
|
|
1101
1326
|
- If no `eligibilityAPIEndpoints` are configured, the system will not check for service eligibility for the specific site.
|
|
1102
1327
|
- The response is normalized to always use PascalCase keys (`Succeeded`, `ErrorCode`, etc.), regardless of the backend’s casing.
|
|
1103
1328
|
- If `Succeeded` is false, the system will look up the `ErrorCode` in your config to determine which error page to show.
|
|
1329
|
+
- For standard eligibility checks see:
|
|
1330
|
+
- [Elegibility with Civil Registry](docs/Eligibility-civil-registry.md)
|
|
1104
1331
|
|
|
1105
1332
|
**Caching**
|
|
1106
1333
|
- The response from each eligibility endpoint is cached in the session for the number of minutes specified by `cashingTimeoutMinutes`.
|
|
@@ -1321,378 +1548,30 @@ The data is collected from the form elements and the data layer and are sent via
|
|
|
1321
1548
|
"date_on_contract": "date_other",
|
|
1322
1549
|
"date_contract": "16/04/2025",
|
|
1323
1550
|
"reason": "24324dssf"
|
|
1324
|
-
}
|
|
1551
|
+
},
|
|
1552
|
+
"academic-details": [ // Multiple things page
|
|
1553
|
+
{
|
|
1554
|
+
"title": "BSc Computer Science",
|
|
1555
|
+
"academicFile": {
|
|
1556
|
+
"fileId": "12345",
|
|
1557
|
+
"sha256": "abcdef..."
|
|
1558
|
+
}
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
"title": "MSc Information Systems",
|
|
1562
|
+
"academicFile": {
|
|
1563
|
+
"fileId": "67890",
|
|
1564
|
+
"sha256": "ghijkl..."
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
]
|
|
1325
1568
|
},
|
|
1326
1569
|
"submissionDataVersion": "1", // Submission data version
|
|
1327
1570
|
"rendererData": { // Summary list renderer data ready for rendering . Object, will be stringified
|
|
1328
|
-
|
|
1329
|
-
"params": {
|
|
1330
|
-
"items": [
|
|
1331
|
-
{
|
|
1332
|
-
"key": {
|
|
1333
|
-
"el": "Στοιχεία του εκπαιδευτικού",
|
|
1334
|
-
"en": "Educator's details",
|
|
1335
|
-
"tr": ""
|
|
1336
|
-
},
|
|
1337
|
-
"value": [
|
|
1338
|
-
{
|
|
1339
|
-
"element": "summaryList",
|
|
1340
|
-
"params": {
|
|
1341
|
-
"items": [
|
|
1342
|
-
{
|
|
1343
|
-
"key": {
|
|
1344
|
-
"el": "Ταυτοποίηση",
|
|
1345
|
-
"en": "Identification"
|
|
1346
|
-
},
|
|
1347
|
-
"value": [
|
|
1348
|
-
{
|
|
1349
|
-
"element": "textElement",
|
|
1350
|
-
"params": {
|
|
1351
|
-
"text": {
|
|
1352
|
-
"en": "Ταυτότητα, ARC",
|
|
1353
|
-
"el": "Ταυτότητα, ARC",
|
|
1354
|
-
"tr": "Ταυτότητα, ARC"
|
|
1355
|
-
},
|
|
1356
|
-
"type": "span"
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
]
|
|
1360
|
-
},
|
|
1361
|
-
{
|
|
1362
|
-
"key": {
|
|
1363
|
-
"el": "Εισαγάγετε αριθμό ταυτότητας",
|
|
1364
|
-
"en": "Enter ID number"
|
|
1365
|
-
},
|
|
1366
|
-
"value": [
|
|
1367
|
-
{
|
|
1368
|
-
"element": "textElement",
|
|
1369
|
-
"params": {
|
|
1370
|
-
"text": {
|
|
1371
|
-
"en": "121212",
|
|
1372
|
-
"el": "121212",
|
|
1373
|
-
"tr": "121212"
|
|
1374
|
-
},
|
|
1375
|
-
"type": "span"
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
]
|
|
1379
|
-
},
|
|
1380
|
-
{
|
|
1381
|
-
"key": {
|
|
1382
|
-
"el": "Αριθμός κοινωνικών ασφαλίσεων",
|
|
1383
|
-
"en": "Social Insurance Number"
|
|
1384
|
-
},
|
|
1385
|
-
"value": [
|
|
1386
|
-
{
|
|
1387
|
-
"element": "textElement",
|
|
1388
|
-
"params": {
|
|
1389
|
-
"text": {
|
|
1390
|
-
"en": "112121",
|
|
1391
|
-
"el": "112121",
|
|
1392
|
-
"tr": "112121"
|
|
1393
|
-
},
|
|
1394
|
-
"type": "span"
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
]
|
|
1398
|
-
}
|
|
1399
|
-
]
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
]
|
|
1403
|
-
},
|
|
1404
|
-
{
|
|
1405
|
-
"key": {
|
|
1406
|
-
"el": "Διορισμός εκπαιδευτικού",
|
|
1407
|
-
"en": "Teachers appointment",
|
|
1408
|
-
"tr": ""
|
|
1409
|
-
},
|
|
1410
|
-
"value": [
|
|
1411
|
-
{
|
|
1412
|
-
"element": "summaryList",
|
|
1413
|
-
"params": {
|
|
1414
|
-
"items": [
|
|
1415
|
-
{
|
|
1416
|
-
"key": {
|
|
1417
|
-
"el": "Τι διορισμό έχει ο εκπαιδευτικός;",
|
|
1418
|
-
"en": "What type of appointment does the teacher have?"
|
|
1419
|
-
},
|
|
1420
|
-
"value": [
|
|
1421
|
-
{
|
|
1422
|
-
"element": "textElement",
|
|
1423
|
-
"params": {
|
|
1424
|
-
"text": {
|
|
1425
|
-
"en": "Συμβασιούχος",
|
|
1426
|
-
"el": "Συμβασιούχος",
|
|
1427
|
-
"tr": "Συμβασιούχος"
|
|
1428
|
-
},
|
|
1429
|
-
"type": "span"
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
]
|
|
1433
|
-
},
|
|
1434
|
-
{
|
|
1435
|
-
"key": {
|
|
1436
|
-
"el": "Αριθμός φακέλου (ΠΜΠ)",
|
|
1437
|
-
"en": "File Number"
|
|
1438
|
-
},
|
|
1439
|
-
"value": [
|
|
1440
|
-
{
|
|
1441
|
-
"element": "textElement",
|
|
1442
|
-
"params": {
|
|
1443
|
-
"text": {
|
|
1444
|
-
"en": "1212",
|
|
1445
|
-
"el": "1212",
|
|
1446
|
-
"tr": "1212"
|
|
1447
|
-
},
|
|
1448
|
-
"type": "span"
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
]
|
|
1452
|
-
},
|
|
1453
|
-
{
|
|
1454
|
-
"key": {
|
|
1455
|
-
"el": "Ειδικότητα",
|
|
1456
|
-
"en": "Specialty"
|
|
1457
|
-
},
|
|
1458
|
-
"value": [
|
|
1459
|
-
{
|
|
1460
|
-
"element": "textElement",
|
|
1461
|
-
"params": {
|
|
1462
|
-
"text": {
|
|
1463
|
-
"en": "Καθηγητής",
|
|
1464
|
-
"el": "Καθηγητής",
|
|
1465
|
-
"tr": "Καθηγητής"
|
|
1466
|
-
},
|
|
1467
|
-
"type": "span"
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
]
|
|
1471
|
-
}
|
|
1472
|
-
]
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
]
|
|
1476
|
-
},
|
|
1477
|
-
{
|
|
1478
|
-
"key": {
|
|
1479
|
-
"el": "Ημερομηνία ανάληψης",
|
|
1480
|
-
"en": "Takeover date",
|
|
1481
|
-
"tr": ""
|
|
1482
|
-
},
|
|
1483
|
-
"value": [
|
|
1484
|
-
{
|
|
1485
|
-
"element": "summaryList",
|
|
1486
|
-
"params": {
|
|
1487
|
-
"items": [
|
|
1488
|
-
{
|
|
1489
|
-
"key": {
|
|
1490
|
-
"el": "Ημερομηνία ανάληψης",
|
|
1491
|
-
"en": "Start Date"
|
|
1492
|
-
},
|
|
1493
|
-
"value": [
|
|
1494
|
-
{
|
|
1495
|
-
"element": "textElement",
|
|
1496
|
-
"params": {
|
|
1497
|
-
"text": {
|
|
1498
|
-
"en": "16/04/2025",
|
|
1499
|
-
"el": "16/04/2025",
|
|
1500
|
-
"tr": "16/04/2025"
|
|
1501
|
-
},
|
|
1502
|
-
"type": "span"
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
]
|
|
1506
|
-
},
|
|
1507
|
-
{
|
|
1508
|
-
"key": {
|
|
1509
|
-
"el": "Η ημερομηνία αυτή είναι η ίδια με αυτή του συμβολαίου;",
|
|
1510
|
-
"en": "Is this date the same as the contract date?"
|
|
1511
|
-
},
|
|
1512
|
-
"value": [
|
|
1513
|
-
{
|
|
1514
|
-
"element": "textElement",
|
|
1515
|
-
"params": {
|
|
1516
|
-
"text": {
|
|
1517
|
-
"en": "Ναι, είναι η ίδια με αυτή του συμβολαίου",
|
|
1518
|
-
"el": "Ναι, είναι η ίδια με αυτή του συμβολαίου",
|
|
1519
|
-
"tr": "Ναι, είναι η ίδια με αυτή του συμβολαίου"
|
|
1520
|
-
},
|
|
1521
|
-
"type": "span"
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
]
|
|
1525
|
-
}
|
|
1526
|
-
]
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
]
|
|
1530
|
-
}
|
|
1531
|
-
]
|
|
1532
|
-
}
|
|
1571
|
+
...
|
|
1533
1572
|
},
|
|
1534
1573
|
"printFriendlyData": [ // Print friendly data. Object, will be stringified
|
|
1535
|
-
|
|
1536
|
-
"pageUrl": "index", // Page URL
|
|
1537
|
-
"pageTitle": { // Page title
|
|
1538
|
-
"el": "Στοιχεία του εκπαιδευτικού",
|
|
1539
|
-
"en": "Educator's details",
|
|
1540
|
-
"tr": ""
|
|
1541
|
-
},
|
|
1542
|
-
"fields": [ // Fields
|
|
1543
|
-
{
|
|
1544
|
-
"id": "id_select", // Field ID
|
|
1545
|
-
"label": { // Field label
|
|
1546
|
-
"el": "Ταυτοποίηση",
|
|
1547
|
-
"en": "Identification"
|
|
1548
|
-
},
|
|
1549
|
-
"value": ["id", "arc"], // Field value. // field level: checkboxes are ALWAYS arrays (may be []); radios/select/text are strings
|
|
1550
|
-
"valueLabel": [ // Field value label
|
|
1551
|
-
{
|
|
1552
|
-
"el": "Ταυτότητα",
|
|
1553
|
-
"en": "ID",
|
|
1554
|
-
"tr": ""
|
|
1555
|
-
},
|
|
1556
|
-
{
|
|
1557
|
-
"el": "ARC",
|
|
1558
|
-
"en": "ARC",
|
|
1559
|
-
"tr": ""
|
|
1560
|
-
}
|
|
1561
|
-
]
|
|
1562
|
-
},
|
|
1563
|
-
{
|
|
1564
|
-
"id": "id_number",
|
|
1565
|
-
"label": {
|
|
1566
|
-
"el": "Εισαγάγετε αριθμό ταυτότητας",
|
|
1567
|
-
"en": "Enter ID number"
|
|
1568
|
-
},
|
|
1569
|
-
"value": "654654",
|
|
1570
|
-
"valueLabel": {
|
|
1571
|
-
"el": "654654",
|
|
1572
|
-
"en": "654654"
|
|
1573
|
-
}
|
|
1574
|
-
},
|
|
1575
|
-
{
|
|
1576
|
-
"id": "aka",
|
|
1577
|
-
"label": {
|
|
1578
|
-
"el": "Αριθμός κοινωνικών ασφαλίσεων",
|
|
1579
|
-
"en": "Social Insurance Number"
|
|
1580
|
-
},
|
|
1581
|
-
"value": "232323",
|
|
1582
|
-
"valueLabel": {
|
|
1583
|
-
"el": "232323",
|
|
1584
|
-
"en": "232323"
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
]
|
|
1588
|
-
},
|
|
1589
|
-
{
|
|
1590
|
-
"pageUrl": "appointment",
|
|
1591
|
-
"pageTitle": {
|
|
1592
|
-
"el": "Διορισμός εκπαιδευτικού",
|
|
1593
|
-
"en": "Teachers appointment",
|
|
1594
|
-
"tr": ""
|
|
1595
|
-
},
|
|
1596
|
-
"fields": [
|
|
1597
|
-
{
|
|
1598
|
-
"id": "diorismos",
|
|
1599
|
-
"label": {
|
|
1600
|
-
"el": "Τι διορισμό έχει ο εκπαιδευτικός;",
|
|
1601
|
-
"en": "What type of appointment does the teacher have?"
|
|
1602
|
-
},
|
|
1603
|
-
"value": "monimos",
|
|
1604
|
-
"valueLabel": {
|
|
1605
|
-
"el": "Μόνιμος επί δοκιμασία",
|
|
1606
|
-
"en": "Permanent on probation",
|
|
1607
|
-
"tr": ""
|
|
1608
|
-
}
|
|
1609
|
-
},
|
|
1610
|
-
{
|
|
1611
|
-
"id": "fileno_monimos",
|
|
1612
|
-
"label": {
|
|
1613
|
-
"el": "Αριθμός φακέλου (ΠΜΠ)",
|
|
1614
|
-
"en": "File Number"
|
|
1615
|
-
},
|
|
1616
|
-
"value": "3233",
|
|
1617
|
-
"valueLabel": {
|
|
1618
|
-
"el": "3233",
|
|
1619
|
-
"en": "3233"
|
|
1620
|
-
}
|
|
1621
|
-
},
|
|
1622
|
-
{
|
|
1623
|
-
"id": "eidikotita_monimos",
|
|
1624
|
-
"label": {
|
|
1625
|
-
"el": "Ειδικότητα",
|
|
1626
|
-
"en": "Specialty"
|
|
1627
|
-
},
|
|
1628
|
-
"value": "1",
|
|
1629
|
-
"valueLabel": {
|
|
1630
|
-
"el": "Δάσκαλος",
|
|
1631
|
-
"en": "Elementary teacher",
|
|
1632
|
-
"tr": ""
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
]
|
|
1636
|
-
},
|
|
1637
|
-
{
|
|
1638
|
-
"pageUrl": "takeover",
|
|
1639
|
-
"pageTitle": {
|
|
1640
|
-
"el": "Ημερομηνία ανάληψης",
|
|
1641
|
-
"en": "Takeover date",
|
|
1642
|
-
"tr": ""
|
|
1643
|
-
},
|
|
1644
|
-
"fields": [
|
|
1645
|
-
{
|
|
1646
|
-
"id": "date_start",
|
|
1647
|
-
"label": {
|
|
1648
|
-
"el": "Ημερομηνία ανάληψης",
|
|
1649
|
-
"en": "Start Date"
|
|
1650
|
-
},
|
|
1651
|
-
"value": "2020-12-11",
|
|
1652
|
-
"valueLabel": {
|
|
1653
|
-
"el": "11/12/2020",
|
|
1654
|
-
"en": "11/12/2020"
|
|
1655
|
-
}
|
|
1656
|
-
},
|
|
1657
|
-
{
|
|
1658
|
-
"id": "date_on_contract",
|
|
1659
|
-
"label": {
|
|
1660
|
-
"el": "Η ημερομηνία αυτή είναι η ίδια με αυτή του συμβολαίου;",
|
|
1661
|
-
"en": "Is this date the same as the contract date?"
|
|
1662
|
-
},
|
|
1663
|
-
"value": "date_other",
|
|
1664
|
-
"valueLabel": {
|
|
1665
|
-
"el": "Όχι, αυτή είναι διαφορετική",
|
|
1666
|
-
"en": "No, this is different",
|
|
1667
|
-
"tr": ""
|
|
1668
|
-
}
|
|
1669
|
-
},
|
|
1670
|
-
{
|
|
1671
|
-
"id": "date_contract",
|
|
1672
|
-
"label": {
|
|
1673
|
-
"el": "Ημερομηνία συμβολαίου",
|
|
1674
|
-
"en": "Contract Date"
|
|
1675
|
-
},
|
|
1676
|
-
"value": "16/04/2025",
|
|
1677
|
-
"valueLabel": {
|
|
1678
|
-
"el": "16/04/2025",
|
|
1679
|
-
"en": "16/04/2025"
|
|
1680
|
-
}
|
|
1681
|
-
},
|
|
1682
|
-
{
|
|
1683
|
-
"id": "reason",
|
|
1684
|
-
"label": {
|
|
1685
|
-
"el": "Αιτιολόγηση καθυστέρησης στην ανάληψη καθηκόντων",
|
|
1686
|
-
"en": "Reason for delay in assuming duties"
|
|
1687
|
-
},
|
|
1688
|
-
"value": "24324dssf",
|
|
1689
|
-
"valueLabel": {
|
|
1690
|
-
"el": "24324dssf",
|
|
1691
|
-
"en": "24324dssf"
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
]
|
|
1695
|
-
}
|
|
1574
|
+
...
|
|
1696
1575
|
],
|
|
1697
1576
|
"rendererVersion": "1.14.1", // Renderer version
|
|
1698
1577
|
"designSystemsVersion": "3.1.0", // Design systems version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gov-cy/govcy-express-services",
|
|
3
|
-
"version": "1.3.0-alpha",
|
|
3
|
+
"version": "1.3.0-alpha.2",
|
|
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",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"coverage:badge": "coverage-badges --output ./coverage-badges.svg && npm run coverage:copy"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@gov-cy/dsf-email-templates": "^2.1.
|
|
55
|
-
"@gov-cy/govcy-frontend-renderer": "^1.
|
|
54
|
+
"@gov-cy/dsf-email-templates": "^2.1.1",
|
|
55
|
+
"@gov-cy/govcy-frontend-renderer": "^1.26.0",
|
|
56
56
|
"axios": "^1.9.0",
|
|
57
57
|
"cookie-parser": "^1.4.7",
|
|
58
58
|
"dotenv": "^16.3.1",
|
|
@@ -77,5 +77,8 @@
|
|
|
77
77
|
},
|
|
78
78
|
"engines": {
|
|
79
79
|
"node": ">=18.0.0"
|
|
80
|
+
},
|
|
81
|
+
"overrides": {
|
|
82
|
+
"tar-fs": "^3.1.1"
|
|
80
83
|
}
|
|
81
84
|
}
|
|
@@ -132,6 +132,10 @@ export function govcyFileDeletePageHandler() {
|
|
|
132
132
|
} else {
|
|
133
133
|
// normal page
|
|
134
134
|
actionPath = `${pageUrl}/delete-file/${elementName}`;
|
|
135
|
+
// if normal page but has multipleThings, block it
|
|
136
|
+
if (page?.multipleThings) {
|
|
137
|
+
return handleMiddlewareError(`Single mode delete file not allowed on multipleThings pages`, 404, next)
|
|
138
|
+
}
|
|
135
139
|
}
|
|
136
140
|
// Construct submit button
|
|
137
141
|
const formElement = {
|
|
@@ -243,6 +247,10 @@ export function govcyFileDeletePostHandler() {
|
|
|
243
247
|
} else {
|
|
244
248
|
// normal page
|
|
245
249
|
actionPath = `${pageUrl}`;
|
|
250
|
+
// if normal page but has multipleThings, block it
|
|
251
|
+
if (page?.multipleThings) {
|
|
252
|
+
return handleMiddlewareError(`Single mode delete file not allowed on multipleThings pages`, 404, next)
|
|
253
|
+
}
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
// the page base return url
|
|
@@ -62,6 +62,11 @@ export function govcyFileViewHandler() {
|
|
|
62
62
|
// return handleMiddlewareError(`Page condition evaluated to true on POST — skipping form save and redirecting`, 404, next);
|
|
63
63
|
// }
|
|
64
64
|
|
|
65
|
+
|
|
66
|
+
// If mode is `single` make sure it has no multipleThings
|
|
67
|
+
if (mode === "single" && page?.multipleThings) {
|
|
68
|
+
return handleMiddlewareError(`Single mode file view not allowed on multipleThings pages`, 404, next)
|
|
69
|
+
}
|
|
65
70
|
// Validate the field: Only allow delete if the page contains a fileInput with the given name
|
|
66
71
|
const fileInputElement = pageContainsFileInput(pageTemplateCopy, elementName);
|
|
67
72
|
if (!fileInputElement) {
|
|
@@ -63,6 +63,8 @@ export function govcyHttpErrorHandler(err, req, res, next) {
|
|
|
63
63
|
if (dataLayer.getUser(req.session)) {
|
|
64
64
|
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
65
65
|
}
|
|
66
|
+
// Add custom CSS path
|
|
67
|
+
pageData.site.customCSSFile = `/css/govcyExpress.css`;
|
|
66
68
|
const html = renderer.renderFromJSON(pageTemplate, pageData);
|
|
67
69
|
res.send(html);
|
|
68
70
|
}
|
|
@@ -81,8 +81,8 @@ export function govcyMultipleThingsDeletePageHandler() {
|
|
|
81
81
|
isPageHeading: true,
|
|
82
82
|
classes: "govcy-mb-6",
|
|
83
83
|
items: [
|
|
84
|
-
{ value: "yes", text: govcyResources.staticResources.text.
|
|
85
|
-
{ value: "no", text: govcyResources.staticResources.text.
|
|
84
|
+
{ value: "yes", text: govcyResources.staticResources.text.multipleThingsDeleteYesOption },
|
|
85
|
+
{ value: "no", text: govcyResources.staticResources.text.multipleThingsDeleteNoOption }
|
|
86
86
|
]
|
|
87
87
|
}
|
|
88
88
|
};
|
|
@@ -24,6 +24,8 @@ export function renderGovcyPage() {
|
|
|
24
24
|
if (dataLayer.getUser(req.session)) {
|
|
25
25
|
processedPage.pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
26
26
|
}
|
|
27
|
+
// Add custom CSS path
|
|
28
|
+
processedPage.pageData.site.customCSSFile = `/css/govcyExpress.css`;
|
|
27
29
|
const html = renderer.renderFromJSON(processedPage.pageTemplate, processedPage.pageData);
|
|
28
30
|
res.send(html);
|
|
29
31
|
};
|
|
@@ -47,6 +47,8 @@ export function govcyRoutePageHandler(req, res, next) {
|
|
|
47
47
|
if (dataLayer.getUser(req.session)) {
|
|
48
48
|
pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
|
|
49
49
|
}
|
|
50
|
+
// Add custom CSS path
|
|
51
|
+
pageData.site.customCSSFile = `/css/govcyExpress.css`;
|
|
50
52
|
const renderer = new govcyFrontendRenderer();
|
|
51
53
|
const html = renderer.renderFromJSON(pageTemplate, pageData);
|
|
52
54
|
res.send(html);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
dl.govcy-summary-list-row-internal:not(:first-of-type) {
|
|
3
|
+
margin-top: 0.5rem !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.list-inline-item:not(:last-child) {
|
|
7
|
+
margin-right: 0 !important;
|
|
8
|
+
}
|
|
9
|
+
.list-inline-item:not(:first-child) {
|
|
10
|
+
margin-left: .5rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.govcy-add-new-item {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
text-decoration: underline;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.govcy-add-new-item::before {
|
|
20
|
+
content: "";
|
|
21
|
+
display: inline-block;
|
|
22
|
+
width: 24px;
|
|
23
|
+
height: 24px;
|
|
24
|
+
background: url("/img/Plus_24x24.svg") no-repeat center center;
|
|
25
|
+
background-size: contain;
|
|
26
|
+
margin-right: 0.25rem; /* Fallback for browsers without flex gap */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (max-width: 767.98px) {
|
|
30
|
+
#multipleThingsList>tbody>tr>td {
|
|
31
|
+
padding: .5rem .5rem;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -172,9 +172,9 @@ export const staticResources = {
|
|
|
172
172
|
tr: "You did not add any entries yet."
|
|
173
173
|
},
|
|
174
174
|
multipleThingsAddEntry: {
|
|
175
|
-
en: "
|
|
176
|
-
el: "
|
|
177
|
-
tr: "
|
|
175
|
+
en: "Add new entry",
|
|
176
|
+
el: "Προσθήκη νέας καταχώρησης",
|
|
177
|
+
tr: "Add new entry"
|
|
178
178
|
},
|
|
179
179
|
multipleThingsDedupeMessage: {
|
|
180
180
|
en: "This entry already exists",
|
|
@@ -220,6 +220,16 @@ export const staticResources = {
|
|
|
220
220
|
en: "Entries",
|
|
221
221
|
el: "Καταχωρήσεις",
|
|
222
222
|
tr: "Entries"
|
|
223
|
+
},
|
|
224
|
+
multipleThingsDeleteYesOption: {
|
|
225
|
+
el:"Ναι, θέλω να διαγράψω την καταχώρηση",
|
|
226
|
+
en:"Yes, I want to delete this entry",
|
|
227
|
+
tr:"Yes, I want to delete this entry"
|
|
228
|
+
},
|
|
229
|
+
multipleThingsDeleteNoOption: {
|
|
230
|
+
el:"Όχι, δεν θέλω να διαγράψω την καταχώρηση",
|
|
231
|
+
en:"No, I don't want to delete this entry",
|
|
232
|
+
tr:"No, I don't want to delete this entry"
|
|
223
233
|
}
|
|
224
234
|
},
|
|
225
235
|
//remderer sections
|
|
@@ -631,9 +641,9 @@ export function getMultipleThingsLink(linkType, siteId, pageUrl, lang , entryKey
|
|
|
631
641
|
element: "htmlElement",
|
|
632
642
|
params: {
|
|
633
643
|
text: {
|
|
634
|
-
en: `<p><a${(count !== null && linkType === "add" ? ` id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`,
|
|
635
|
-
el: `<p><a${(count !== null && linkType === "add" ? ` id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`,
|
|
636
|
-
tr: `<p><a${(count !== null && linkType === "add" ? ` id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`
|
|
644
|
+
en: `<p><a${(count !== null && linkType === "add" ? ` class="govcy-add-new-item" id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`,
|
|
645
|
+
el: `<p><a${(count !== null && linkType === "add" ? ` class="govcy-add-new-item" id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`,
|
|
646
|
+
tr: `<p><a${(count !== null && linkType === "add" ? ` class="govcy-add-new-item" id="addNewItem${count}"` : "")} href="${fullPath}">${linkTextString}</a></p>`
|
|
637
647
|
}
|
|
638
648
|
}
|
|
639
649
|
};
|
|
@@ -104,7 +104,13 @@ export function populateFormData(
|
|
|
104
104
|
|
|
105
105
|
// 2) If not found, fall back to dataLayer (normal page behaviour)
|
|
106
106
|
if (!fileData) {
|
|
107
|
-
|
|
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
|
+
}
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
if (fileData) {
|
|
@@ -100,6 +100,16 @@ export async function handleFileUpload({
|
|
|
100
100
|
|
|
101
101
|
// deep copy the page template to avoid modifying the original
|
|
102
102
|
const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
|
|
103
|
+
|
|
104
|
+
// If mode is `single` make sure it has no multipleThings
|
|
105
|
+
if (mode === "single" && page?.multipleThings) {
|
|
106
|
+
return {
|
|
107
|
+
status: 400,
|
|
108
|
+
dataStatus: 413,
|
|
109
|
+
errorMessage: 'Single mode upload not allowed on multipleThings pages'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
// Validate the field: Only allow upload if the page contains a fileInput with the given name
|
|
104
114
|
const isAllowed = pageContainsFileInput(pageTemplateCopy, elementName);
|
|
105
115
|
if (!isAllowed) {
|
|
@@ -96,13 +96,39 @@ function validateValue(value, rules) {
|
|
|
96
96
|
}
|
|
97
97
|
return normalizedVal <= max;
|
|
98
98
|
},
|
|
99
|
-
// ✅
|
|
99
|
+
// ✅ Year based current rules
|
|
100
100
|
maxCurrentYear: (val) => {
|
|
101
101
|
const normalizedVal = normalizeNumber(val);
|
|
102
102
|
if (isNaN(normalizedVal)) return false;
|
|
103
103
|
const currentYear = new Date().getFullYear();
|
|
104
104
|
return normalizedVal <= currentYear;
|
|
105
105
|
},
|
|
106
|
+
minCurrentYear: (val) => {
|
|
107
|
+
const normalizedVal = normalizeNumber(val);
|
|
108
|
+
if (isNaN(normalizedVal)) return false;
|
|
109
|
+
const currentYear = new Date().getFullYear();
|
|
110
|
+
return normalizedVal >= currentYear;
|
|
111
|
+
},
|
|
112
|
+
// ✅ Date-based current rules
|
|
113
|
+
minCurrentDate: (val) => {
|
|
114
|
+
const valueDate = parseDate(val);
|
|
115
|
+
const today = new Date();
|
|
116
|
+
if (isNaN(valueDate)) return false;
|
|
117
|
+
// strip time components from both
|
|
118
|
+
const valueOnly = new Date(valueDate.getFullYear(), valueDate.getMonth(), valueDate.getDate());
|
|
119
|
+
const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
120
|
+
return valueOnly >= todayOnly;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
maxCurrentDate: (val) => {
|
|
124
|
+
const valueDate = parseDate(val);
|
|
125
|
+
const today = new Date();
|
|
126
|
+
if (isNaN(valueDate)) return false;
|
|
127
|
+
const valueOnly = new Date(valueDate.getFullYear(), valueDate.getMonth(), valueDate.getDate());
|
|
128
|
+
const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
129
|
+
return valueOnly <= todayOnly;
|
|
130
|
+
},
|
|
131
|
+
|
|
106
132
|
minValueDate: (val, minDate) => {
|
|
107
133
|
const valueDate = parseDate(val); // Parse the input date
|
|
108
134
|
const min = parseDate(minDate); // Parse the minimum date
|
|
@@ -139,7 +165,7 @@ function validateValue(value, rules) {
|
|
|
139
165
|
// Skip validation if the value is empty
|
|
140
166
|
if (value === null || value === undefined || (typeof value === 'string' && value.trim() === "")) {
|
|
141
167
|
continue; // let "required" handle emptiness
|
|
142
|
-
}
|
|
168
|
+
}
|
|
143
169
|
// Check for "valid" rules (e.g., numeric, telCY, etc.)
|
|
144
170
|
if (check === "valid" && validationRules[checkValue]) {
|
|
145
171
|
const isValid = validationRules[checkValue](value);
|
|
@@ -168,12 +194,12 @@ function validateValue(value, rules) {
|
|
|
168
194
|
if (check === 'minValue' && !validationRules.minValue(value, checkValue)) {
|
|
169
195
|
return message;
|
|
170
196
|
}
|
|
171
|
-
|
|
197
|
+
|
|
172
198
|
// Check for "maxValue"
|
|
173
199
|
if (check === 'maxValue' && !validationRules.maxValue(value, checkValue)) {
|
|
174
200
|
return message;
|
|
175
201
|
}
|
|
176
|
-
|
|
202
|
+
|
|
177
203
|
// Check for "minValueDate"
|
|
178
204
|
if (check === 'minValueDate' && !validationRules.minValueDate(value, checkValue)) {
|
|
179
205
|
return message;
|