611 Commits
Author SHA1 Message Date
robbond d36a56c4c9 refactor(representations): decompose representation flow shell into form stage and timetable panel (Slice R4, behaviour-preserving) 2026-04-20 13:27:53 +01:00
robbond ebed61799e docs(representations): finalise refactor tracker and closeout state 2026-04-20 13:23:08 +01:00
Robert Bond 670e98a0a1 Merged PR 2258: refactor(summary-types): standardise case summary markup with SummaryCard/SummaryRow wrappers
Related work items: #22441
2026-04-20 12:15:43 +00:00
Robert Bond b6ca73d76a Merged PR 2257: refactor(representations): isolate finalisation boundary helpers (slice R8)
refactor(representations): isolate finalisation boundary helpers (slice R8)

Related work items: #22441
2026-04-17 13:23:55 +00:00
Robert Bond 7713fc3724 Merged PR 2256: refactor(representations): preserve Welsh conditional validation and summary...
refactor(representations): preserve Welsh conditional validation and summary parity

Related work items: #22441
2026-04-17 12:44:23 +00:00
Robert Bond 33f31ad3c8 Merged PR 2255: refactor(representations): extract deterministic data-resolution helpers for...
refactor(representations): extract deterministic data-resolution helpers for representation flow (Slice R6, behaviour-preserving)

Related work items: #22441
2026-04-17 10:07:52 +00:00
Robert Bond 8e21ec1dd7 Merged PR 2254: refactor(representations): normalise shared type-selector and action-row frag...
refactor(representations): normalise shared type-selector and action-row fragments in main representation variants (Slice R5, behaviour-preserving)

Related work items: #22441
2026-04-17 09:22:36 +00:00
Robert Bond d05e54c020 Merged PR 2252: refactor(representations): extract journey step-resolution helpers (Slice R3,...
refactor(representations): extract journey step-resolution helpers (Slice R3, behaviour-preserving)

Related work items: #22441
2026-04-17 06:45:21 +00:00
Robert Bond a43eb76e3a Merged PR 2251: refactor(representations): separate representation page SSR loaders into lib/...
refactor(representations): separate representation page SSR loaders into lib/representation/pageLoaders (Slice R2, behaviour-preserving)

Related work items: #22441
2026-04-17 05:30:38 +00:00
robbond 011be977a2 Merge branch 'SIPS-Development' of https://welshgovernment.visualstudio.com/PEDW%20-%20Planning%20Casework/_git/FrontEnd into refactor 2026-04-16 12:35:44 +01:00
Robert Bond c8b08a04a6 Merged PR 2250: fixed typo
Related work items: #22632
2026-04-16 10:47:54 +00:00
Robert Bond bb4c3ee054 Merged PR 2247: added health endpoint to check pre prod container startup issues
added health endpoint to check pre prod container startup issues

Related work items: #22624
2026-04-15 09:52:15 +00:00
robbond e3614dba7b Merge branch 'refactor' of https://welshgovernment.visualstudio.com/PEDW%20-%20Planning%20Casework/_git/FrontEnd into refactor 2026-04-15 10:31:59 +01:00
Robert Bond ecd6075c3a Merged PR 2246: refactor reps first slice
Related work items: #22570, #22576, #22577, #22583, #22586, #22587, #22588, #22590, #22591
2026-04-15 09:28:50 +00:00
Robert Bond 15c35d201e Merged PR 2244: added condition to fix log in journey in welsh if no account in crm
added condition to fix log in journey in welsh if no account in crm

Related work items: #22081, #22303
2026-04-13 15:54:51 +00:00
Robert Bond 0de9268255 Merged PR 2242: Refactor: New Appeal flow structural cleanup (Slices 1–8, no behaviour change)
## Summary

This PR merges the completed new-appeal refactor stream (Slices 1–8) into SIPS-Development.

This refactor was executed in controlled slices with regression validation at each step.

## Scope

Structural and readability improvements only:
- XML/form derivation extraction
- payload and file helper extraction
- side-effect facade introduction
- BuildSection and BuildCheckSection decomposition
- BuildCheckRow formatter map refactor
- nested prop boundary cleanup
- start-flow cleanup (CreateCase / AboutYou)

## Behaviour

No intended behavioural changes.

The following were explicitly preserved:
- S78 journey behaviour
- payload shapes and field names/ids
- HASCAS mapping logic
- appellant/agent branching
- validation rules and messages
- redirect/query parameters (`lpa`, `apt`, `id`)
- navigation and side-effect sequencing
- EN/CY output parity

## Validation

Full regression has been performed on:
- start flow (CreateCase / AboutYou)
- save and resume flows
- file upload handling
- check answers rendering
- submit/finalisation sequence
- confirmation flow
- CRM insertion path
- EN/CY parity

Additional checks:
- docsOffline branch behaviour
- completion and partial-save email paths
- negative-path validation scenarios
- lint (warnings baseline unchanged)

## Risk

Low:
- changes are structural only
- no business logic changes
- no contract changes

## Rollback

Safe rollback via reverting this merge commit.

## Notes

This refactor reduces coupling and prepares the new-appeal flow for future appeal-type expansion.

Related work items: #22570, #22576, #22577, #22583, #22586, #22587, #22588, #22590
2026-04-13 13:09:12 +00:00
Robert Bond f3a05db323 Merged PR 2241: refactor(newappeal): simplify start flow readability (Slice 8)
refactor(newappeal): simplify start flow readability (Slice 8)

Related work items: #22590
2026-04-13 12:55:58 +00:00
Robert Bond ffcccf8981 Merged PR 2240: refactor(newappeal): simplify nested prop boundaries (Slice 7)
refactor(newappeal): simplify nested prop boundaries (Slice 7)

Related work items: #22588
2026-04-13 12:25:45 +00:00
Robert Bond 783786b9eb Merged PR 2239: refactor(newappeal): replace BuildCheckRow formatter chain with map (Slice 6)
refactor(newappeal): replace BuildCheckRow formatter chain with map (Slice 6)

Related work items: #22587
2026-04-13 11:53:45 +00:00
Robert Bond 20eea5b128 Merged PR 2238: refactor(newappeal): extract BuildCheckSection presentation components (Slice 5)
refactor(newappeal): extract BuildCheckSection presentation components (Slice 5)

Related work items: #22586
2026-04-13 10:59:27 +00:00
Robert Bond 869264b7fa Merged PR 2237: refactor(newappeal): extract BuildSection presentation components (Slice 4)
refactor(newappeal): extract BuildSection presentation components (Slice 4)

Related work items: #22583
2026-04-13 10:15:56 +00:00
Robert Bond d0f667ffbb Merged PR 2236: refactor(newappeal): add side-effect facade wrappers (Slice 3)
refactor(newappeal): add side-effect facade wrappers (Slice 3)

Related work items: #22583
2026-04-13 09:31:14 +00:00
robbond f8b7f4fffd refactor(newappeal): add side-effect facade wrappers (Slice 3) 2026-04-13 10:26:35 +01:00
Robert Bond 21f511c34b Merged PR 2235: refactor(newappeal): extract file merge and dedupe helpers (Slice 2b)
refactor(newappeal): extract file merge and dedupe helpers (Slice 2b)

Related work items: #22577
2026-04-13 09:11:49 +00:00
robbond 7c4ce131bf refactor(newappeal): extract file merge and dedupe helpers (Slice 2b) 2026-04-13 10:07:58 +01:00
Robert Bond ddbf2116f5 Merged PR 2234: refactor(newappeal): extract payload cleanup helpers (Slice 2a)
refactor(newappeal): extract payload cleanup helpers (Slice 2a)

- extracted boolean normalization

- extracted null key removal

- extracted internal key stripping

- no behaviour change

Related work items: #22576
2026-04-13 08:31:23 +00:00
robbond 5233f62d59 refactor(newappeal): extract payload cleanup helpers (Slice 2a)
- extracted boolean normalization

- extracted null key removal

- extracted internal key stripping

- no behaviour change
2026-04-10 16:37:42 +01:00
Robert Bond 5f78a881c5 Merged PR 2232: xml form derivation
Related work items: #22570
2026-04-10 14:37:02 +00:00
robbond 7f7d4daa8f docs(refactor): update refactor tracker status 2026-04-10 15:34:06 +01:00
robbond e9d4b65a3d chore(refactor): commit current refactor branch updates and slice1 xml/form derivation extraction 2026-04-10 09:39:28 +01:00
Robert Bond bd3d311cfb Merged PR 2231: refactor breadcrumb
Related work items: #22541
2026-04-09 15:36:06 +00:00
robbond ee2751a642 catch fix in view all 2026-04-09 16:34:41 +01:00
robbond ac74782030 fix(breadcrumbs): restore static and viewall label crumb resolution 2026-04-09 16:26:44 +01:00
robbond 7d195ad906 refactor(breadcrumbs): remove stale /newappeal inline no-op branch 2026-04-09 15:22:36 +01:00
robbond 61254e3942 test(breadcrumbs): cover missing representation route map fallback 2026-04-09 15:19:40 +01:00
robbond 97cde880c0 docs(breadcrumbs): record map precedence and callback invariants 2026-04-09 15:17:44 +01:00
robbond d3f4dda8af refactor(breadcrumbs): extract representation route-state renderer group 2026-04-09 15:16:01 +01:00
robbond 7a60e64a84 test(breadcrumbs): assert strict null on non-exact route map lookups 2026-04-09 15:06:41 +01:00
robbond cebd47bf1c refactor(breadcrumbs): map step-back route branches into renderer group 2026-04-09 15:03:19 +01:00
robbond 0b905f9374 refactor(breadcrumbs): extract callback route renderers into mapped groups 2026-04-09 14:57:47 +01:00
robbond 0cd78e6f46 22541 harden breadcrumb mapped route resolver against invalid entries 2026-04-09 14:45:12 +01:00
robbond 2a84056a76 22541 extract new appeal breadcrumb branch into mapped renderer group 2026-04-09 14:39:30 +01:00
robbond 17795f20c7 22541 extract deterministic case breadcrumb branches into factory maps 2026-04-09 14:33:54 +01:00
robbond 8edd23d621 22541 compose mapped breadcrumb groups in factory and add null fallback guard 2026-04-09 14:30:03 +01:00
robbond 96946ed205 Extract additional routes in to shared renderer factory 2026-04-09 13:56:17 +01:00
robbond c264830934 Extract case detail breadcrumb routes into shared renderer factory 2026-04-09 13:16:56 +01:00
robbond 062cd23463 Refactor breadcrumbs to use factory-based route renderers 2026-04-09 12:08:36 +01:00
robbond 5f6dde782a 22541 extract breadcrumb route-map helpers and add precedence guard tests 2026-04-09 10:25:47 +01:00
robbond 78d62c541f 22541 consolidate breadcrumb mapped route maps into grouped constants 2026-04-09 10:21:09 +01:00
robbond ee3f3eae74 22541 map myportal results pair routes through unified breadcrumb resolver 2026-04-09 10:16:45 +01:00
robbond 786f906ab8 22541 unify breadcrumb mapped-route resolver and widen route-map coverage 2026-04-09 10:02:02 +01:00
robbond 04dc8fd2b9 22541 extract simple link-text breadcrumb route map and add structure tests 2026-04-09 09:58:22 +01:00
robbond c464fbf13b 22541 introduce simple route renderer map for static breadcrumb branches 2026-04-09 09:54:49 +01:00
robbond a8ef55c65a 22541 extract grouped breadcrumb pair helpers for myportal and dns routes 2026-04-09 09:52:42 +01:00
robbond e24ab89e83 22541 consolidate static text breadcrumb route branches 2026-04-09 09:50:41 +01:00
robbond dd2ef694f8 22541 add semantic back crumb helper and consolidate edge breadcrumb branches 2026-04-09 09:47:53 +01:00
robbond e3e893ed58 22541 extract shared anchor/back breadcrumb helper patterns 2026-04-09 09:45:00 +01:00
robbond 30bc729c70 22541 consolidate remaining inline Link breadcrumb patterns 2026-04-09 09:41:33 +01:00
robbond d576987fcf 22541 extract shared text and link breadcrumb render helpers 2026-04-09 09:38:14 +01:00
robbond d75675f059 22541 extract shared case-reference breadcrumb list item helper 2026-04-09 09:34:24 +01:00
robbond 035f0e9adf 22541 extract shared my-portal breadcrumb list item helper 2026-04-09 09:32:21 +01:00
robbond f447aa19ea 22541 add bundled case breadcrumb state resolver 2026-04-09 09:28:54 +01:00
robbond 59d3ed287c 22541 expand breadcrumb route-state helpers for label and dns path reuse 2026-04-09 09:26:19 +01:00
robbond 1c06ef1639 22541 extract breadcrumb route-state helper and align phase22 auth locale test 2026-04-09 09:23:22 +01:00
Robert Bond 3cea83b7e1 Merged PR 2230: pass in locale to questionnaire for welsh version
pass in locale to questionnaire for welsh version

Related work items: #22534
2026-04-08 15:51:37 +00:00
robbond 1f5be7c9a2 pass in locale to questionnaire for welsh version 2026-04-08 16:50:47 +01:00
Robert Bond 87af9a2c4c Merged PR 2228: refactoring fix and fix for language dependant login and download questionnaire
Related work items: #22303, #22344, #22500
2026-04-08 11:20:59 +00:00
robbond 1b2f8e29c6 defect fix for language and login - crm precedence then current session 2026-04-08 12:17:28 +01:00
robbond 56e854ca2b defect fix for download local questionnaire 2026-04-08 11:18:41 +01:00
robbond 5d40fab5c4 Extract Radio, Numeric and Decimal wrappers 2026-04-08 10:00:01 +01:00
robbond dbebd03cac Extract CheckBox, DateFieldPicker and YesNo wrappers 2026-04-08 08:33:06 +01:00
robbond 8350e42d02 Remove dead and debug-only code from elements index 2026-04-08 08:23:51 +01:00
robbond 6c387fa6fb Extract PickList wrapper from elements index 2026-04-08 08:12:40 +01:00
robbond 30bd8c49da Extract visibility and validation helpers in elements index 2026-04-07 15:44:02 +01:00
robbond eccb87e4a1 Export FieldArrayForm from elements index barrel 2026-04-07 15:36:34 +01:00
robbond a3b848cfc1 Re-export RenderSubFields from elements index 2026-04-07 15:35:19 +01:00
robbond 497bb4c799 Extract RenderFileUpload and FieldArrayForm 2026-04-07 15:32:45 +01:00
robbond 5e992507de Clean dead code in FieldArrayForm slice 2026-04-07 14:27:31 +01:00
robbond dcdec08f6d Fix render-phase dispatch in FieldArrayForm 2026-04-07 12:37:29 +01:00
robbond 503a0b5b9a Fix render-phase update in RenderSubFields 2026-04-07 12:34:36 +01:00
robbond 52071058d6 Fix MultiLinefield hook order warning 2026-04-07 12:30:35 +01:00
robbond f886fb8f25 Remove unused renderField from elements index 2026-04-07 12:27:36 +01:00
robbond f8665ee6b7 Refactor field array subfields renderer into dedicated component 2026-04-07 12:17:15 +01:00
robbond 7ae5f80f26 Refactor checkbox, picklist, and decimal renderers into field components 2026-04-07 11:42:11 +01:00
robbond b022c0a0d9 Refactor elements renderers into dedicated field components 2026-04-07 11:25:48 +01:00
robbond 5b5a91b33b Make uploadsinglefile batch size env-configurable with fallback of 5 2026-04-07 11:24:17 +01:00
robbond aa4d1fad1e 22500 Phase 2: extract text and multiline leaf renderers 2026-04-07 09:45:19 +01:00
robbond 5b9498b3ef 22500 Phase 2: extract rich multiline leaf renderer 2026-04-07 09:40:25 +01:00
robbond 06b7855826 22500 Phase 1: extract pure helpers from elements index 2026-04-07 09:34:50 +01:00
Robert Bond 02512ec403 Merged PR 2227: batch uploads on frontend
batch uploads on frontend

Related work items: #22422
2026-04-02 15:11:05 +00:00
robbond c754e04034 batch uploads on frontend 2026-04-02 16:09:56 +01:00
Robert Bond da7e694120 Merged PR 2225: added fallback of 5 if no env var declared
added fallback of 5 if no env var declared

Related work items: #22422
2026-04-02 14:29:51 +00:00
robbond 786ed43fb2 added fallback of 5 if no env var declared 2026-04-02 15:29:15 +01:00
Robert Bond 638b51e55d Merged PR 2224: batch count from env var
batch count from env var

Related work items: #22422
2026-04-02 14:26:01 +00:00
robbond bddcd1e964 batch count from env var 2026-04-02 15:25:07 +01:00
Robert Bond bd13886222 Merged PR 2222: batch uploaded files in blocks of 10 at a time
batch uploaded files in blocks of 10 at a time

Related work items: #22422
2026-04-02 14:07:20 +00:00
robbond 69cf5d32b4 batch uploaded files in blocks of 10 at a time 2026-04-02 15:06:34 +01:00
Robert Bond fd76dfe9bc Merged PR 2215: update ldp query
update ldp query

Related work items: #21992
2026-04-02 09:03:21 +00:00
robbond 2add85b519 update ldp query 2026-04-02 10:02:12 +01:00
Robert Bond 82b99ae44f Merged PR 2210: Rep raising and pdf for welsh
Related work items: #22095, #22305
2026-03-30 11:46:22 +00:00
robbond 3bc3759a48 welsh downloadable new appeal form 2026-03-30 12:35:41 +01:00
robbond 13cd2514c7 ip app lpa ip can raise final comments 2026-03-30 12:34:48 +01:00
Robert Bond 461ca7dc02 Merged PR 2209: sprint 16 bug fixes
Related work items: #22079, #22151, #22304
2026-03-27 13:10:49 +00:00
robbond 536ca11f6b 22304 language switch on dnsapplications 2026-03-27 13:04:26 +00:00
robbond 2c4d645d56 22151 multiple files issue 2026-03-27 11:25:41 +00:00
robbond 1b145dd90f 22079 update text 2026-03-27 10:57:54 +00:00
Robert Bond 6f8bbd0131 Merged PR 2208: update and clean azurestorage.js
Related work items: #22326
2026-03-26 11:42:42 +00:00
robbond 830e36d0c4 TASK22269: reuse contentLength locals in touched azurestorage blob loops 2026-03-26 11:36:47 +00:00
robbond 48878e9ebd TASK22269: extract touched azurestorage case object path helper 2026-03-26 11:20:37 +00:00
robbond 5c62f3088e TASK22269: reuse azurestorage path builders in touched blob object fields 2026-03-26 11:18:50 +00:00
robbond 361328c14a TASK22269: prune touched azurestorage unused locals 2026-03-26 11:17:09 +00:00
robbond 427e1c4673 TASK22269: normalize touched azurestorage logging calls 2026-03-26 11:15:33 +00:00
robbond 9791270670 TASK22269: tidy azurestorage blob path split locals 2026-03-26 11:13:48 +00:00
robbond 58e1eef1f5 TASK22269: add phase22 azurestorage helper contract tests 2026-03-26 11:11:24 +00:00
robbond 8744d3dafc TASK22269: consolidate azurestorage hash metadata mapping 2026-03-25 14:27:52 +00:00
robbond fcce6a3978 TASK22269: normalize azurestorage hash query path builders 2026-03-25 14:21:09 +00:00
robbond 1b7e6009aa TASK22269: normalize document hash-suffix route composition 2026-03-25 14:13:53 +00:00
robbond 4e964ad1ad TASK22269: group migrate remaining account/portal signed GET flows 2026-03-25 14:06:18 +00:00
robbond 85cc7d165e TASK22269: delegate fileClient signed get/post to signedRequestClient 2026-03-25 13:47:46 +00:00
robbond 7470975d0e TASK22269: migrate portal deleteMyRepresentations to signed delete helper 2026-03-25 13:43:08 +00:00
robbond 811655c8d2 TASK22269: add signed request client and migrate portal deleteWatchedCases pilot 2026-03-25 13:37:49 +00:00
Robert Bond 35ff16b2b2 Merged PR 2207: updated refactoring docs
docs: add current-state context pack and actionable sequence-b prompts

Related work items: #22242
2026-03-25 13:21:02 +00:00
robbond 30fad8e954 docs: add current-state context pack and actionable sequence-b prompts 2026-03-25 13:08:05 +00:00
Robert Bond e238ec079f Merged PR 2206: split out actions to services and clients
Related work items: #22260
2026-03-25 12:16:40 +00:00
robbond 70d2ee175d test(phase22): add auth redirect safety and i18n route parity checks 2026-03-25 12:15:06 +00:00
robbond 10f221af38 test(portal): add phase22 behavioural closure and route harmonization 2026-03-25 12:04:11 +00:00
robbond 6b66ad6d8f test(phase22): add case service behavioural coverage 2026-03-25 11:58:58 +00:00
robbond 05f26ff12c refactor(case): normalize route composition with fileRouteBuilder 2026-03-25 11:49:19 +00:00
robbond ae64ee6c2b refactor(portal): normalize route composition with fileRouteBuilder 2026-03-25 11:36:59 +00:00
robbond 65003bb08c refactor(document): extract file route builder and normalize query composition 2026-03-25 11:31:39 +00:00
robbond ad855fec88 refactor(document): complete signed post flows via fileClient 2026-03-25 11:14:57 +00:00
robbond 391040b5ac test(phase7): lock downloadBlob file-client delegation 2026-03-25 11:08:52 +00:00
robbond e4bd8ab9b6 test(harness): add fileClient default helper injections 2026-03-25 11:04:57 +00:00
robbond a66d55af80 refactor(actions): extract fileClient with phase22 coverage 2026-03-25 10:59:20 +00:00
robbond 93eb19d028 test(phase22): add client utility behaviour coverage 2026-03-25 10:49:37 +00:00
robbond 64201a6591 test(phase22): add aggregate phase runner entrypoint 2026-03-25 10:43:38 +00:00
robbond 7553f992b7 test(phase22): align runner export/guard parity for core token suite 2026-03-25 10:34:34 +00:00
robbond d7874686a1 test(core): add bounded regression coverage for token request client 2026-03-25 10:30:39 +00:00
robbond 342c7ed457 refactor(core): continue bounded risk-reduction for token client extraction 2026-03-25 10:24:42 +00:00
robbond a715e5fac7 test(harness): continue bounded risk-reduction with shared service harness 2026-03-25 10:13:01 +00:00
robbond 7d2f8a2c27 test(phase6): align behavioural harness with client wrappers 2026-03-25 10:07:25 +00:00
robbond 480897bdc0 test(phase7): align service behaviour harness with client wrappers 2026-03-25 10:04:16 +00:00
robbond 5309ffb8cf refactor(actions): fix document download helper contract 2026-03-25 09:57:26 +00:00
robbond 4a49bbca09 refactor(actions): reduce remaining axios usage in document service 2026-03-25 09:52:27 +00:00
robbond ba7d94d83d refactor(actions): include residual axios-risk slices in branch 2026-03-25 09:43:40 +00:00
robbond 94e2ed5831 refactor(actions): adopt endpoint client in portal config helpers 2026-03-25 08:51:39 +00:00
robbond 2f95636c4a refactor(actions): remove remaining axios usage in case service 2026-03-25 08:03:02 +00:00
robbond 9d67079fb0 refactor(actions): adopt endpoint client in case service posts 2026-03-25 08:00:14 +00:00
robbond ad3b18ac27 refactor(actions): complete case read migration to endpoint client 2026-03-25 07:52:51 +00:00
robbond 353378ee45 refactor(actions): expand endpoint client adoption in case reads 2026-03-25 07:45:51 +00:00
robbond c1f6cc9832 refactor(actions): adopt endpoint client in case service reads 2026-03-25 07:36:53 +00:00
robbond f4471c5702 refactor(actions): adopt endpoint client in remaining document reads 2026-03-25 06:55:30 +00:00
robbond 13dbb8b2d0 refactor(actions): adopt endpoint client in document service posts 2026-03-25 06:48:54 +00:00
robbond 17be69323e refactor(actions): adopt endpoint client in document service reads 2026-03-25 06:43:51 +00:00
robbond 6e6a104dd9 refactor(actions): adopt endpoint client in portal involvement helpers 2026-03-25 06:30:35 +00:00
robbond f596ad4e98 refactor(actions): adopt endpoint client in portal service reads 2026-03-25 06:26:45 +00:00
robbond a74f59394e refactor(actions): adopt endpoint client in account direct service 2026-03-25 06:23:12 +00:00
robbond eb3b6013b3 refactor(actions): adopt endpoint client in reference data service 2026-03-25 06:19:37 +00:00
robbond 6fa7cf9375 refactor(actions): extract shared relay client for direct services 2026-03-25 06:14:23 +00:00
Robert Bond 5e51c2eb97 Merged PR 2205: addin timeouts and retries for stability
Related work items: #22242
2026-03-24 18:19:01 +00:00
robbond fcfb7cf75e refactor(relay): extract shared relay policy presets across endpoints 2026-03-24 18:07:04 +00:00
robbond 318f913397 feat(relay): apply policy overrides for my-portal case and representation endpoints 2026-03-24 15:52:29 +00:00
robbond 0a5819803e feat(relay): add portal endpoint relayPolicy overrides and contract checks 2026-03-24 15:44:09 +00:00
robbond e0510f9e8f feat(relay): apply endpoint-level relayPolicy overrides across account and search flows 2026-03-24 15:36:21 +00:00
robbond bb190946ae feat(relay): add policy overrides and idempotency-aware retry gating 2026-03-24 13:03:49 +00:00
robbond 1ba0cbe8a9 feat(relay): enrich request lifecycle telemetry for retries and outcomes 2026-03-24 12:54:07 +00:00
Robert Bond 0faf79945f Merged PR 2204: update hardening docs
Related work items: #22239
2026-03-24 12:45:53 +00:00
robbond 7903944223 docs(runbook): add relay PR evidence template and summary draft 2026-03-24 12:41:30 +00:00
robbond ad0d2bf285 docs(governance): track context docs and add relay rollout controls 2026-03-24 12:26:32 +00:00
Robert Bond 2ac2f51fc3 Merged PR 2203: Api hardening
Related work items: #22236
2026-03-24 12:06:02 +00:00
robbond f12a719a1e docs(memory-bank): record relay hardening policy baseline and operating knobs 2026-03-24 11:55:24 +00:00
robbond 4e390491c0 refactor(api): tighten relay retry policy and config safety bounds 2026-03-24 11:54:42 +00:00
robbond fad99c93bc refactor(api): harden relay forwarding with timeout, retries and redacted logs 2026-03-24 11:43:40 +00:00
Robert Bond 1b7d9da32c Merged PR 2202: relay GET completion + notify logging fix
Related work items: #22229
2026-03-24 11:32:13 +00:00
robbond 51e255f70b fix(email): log notify send attempts without error framing 2026-03-24 10:35:09 +00:00
robbond da29e789c0 refactor(api): migrate batch 12 remaining relay-get candidates 2026-03-24 10:27:27 +00:00
robbond bf1ba9bc1e refactor(api): migrate batch 11 advancedsearchpaged to relayGet 2026-03-24 10:15:25 +00:00
robbond 0f4b8005ba refactor(api): migrate batch 10 endpoints to relayGet 2026-03-24 10:08:51 +00:00
robbond e8d4e8563e refactor(api): migrate batch 9 endpoints to relayGet 2026-03-24 09:59:59 +00:00
robbond 13be069b8b refactor(api): migrate batch 8 endpoints to relayGet 2026-03-24 09:51:13 +00:00
robbond bd15fd18e1 refactor(api): migrate batch 7 endpoints to relayGet 2026-03-24 09:43:17 +00:00
robbond 6da6fdb4d2 refactor(api): migrate batch 6 endpoints to relayGet 2026-03-24 09:28:01 +00:00
robbond 5797443edc refactor(api): migrate batch 5 endpoints to relayGet 2026-03-24 09:27:06 +00:00
robbond ac0253daeb refactor(api): migrate batch 4 endpoints to relayGet 2026-03-24 09:26:37 +00:00
robbond 44b32a618a TASK22229: P2-S2 batch 3 migrate case/event relay GET endpoints 2026-03-24 07:49:28 +00:00
robbond fdf95dd0b1 TASK22229: P2-S2 batch 2 migrate portal/representation relay GET endpoints 2026-03-24 07:45:59 +00:00
robbond 6edbc431f8 TASK22229: P2-S2 batch 1 migrate account/login relay GET endpoints 2026-03-24 06:58:50 +00:00
robbond 312f7415f5 TASK22229: pilot shared relay forwarding utility 2026-03-24 06:40:32 +00:00
Robert Bond dea127912f Merged PR 2201: update file endpoints
Related work items: #22224
2026-03-24 06:30:29 +00:00
robbond 7c0dc30b1a TASK22224: nextauth notify try-catch micro-refactor 2026-03-24 06:18:35 +00:00
robbond fe394f3fa8 TASK22224: complete documents + endpoint hygiene slices 2026-03-24 06:12:04 +00:00
robbond dfdfd3653c TASK22224: prune dead code in pdf handlers 2026-03-24 05:58:31 +00:00
robbond c6205bbb39 TASK22224: tidy pdf render compatibility 2026-03-24 05:52:38 +00:00
robbond 9b0365ffbd TASK22224: harden createappealcompletemessage parity 2026-03-24 05:49:58 +00:00
robbond cc4fb36439 TASK22224: modernize generatepdf handlers 2026-03-23 18:39:44 +00:00
robbond 411190e403 TASK22224: aggressive non-file parity bundle 2026-03-23 18:32:49 +00:00
robbond ba9beda65e TASK22224: modernize involvement and updatecase handlers 2026-03-23 18:23:54 +00:00
robbond 3f2b945172 TASK22224: harden proxy file routes and expand contracts 2026-03-23 18:14:22 +00:00
robbond 91a0ab82ea TASK22224: big parity bundle for completion message routes 2026-03-23 18:09:36 +00:00
robbond 9f5ec6f50a TASK22224: larger parity bundle for reps and upload handlers 2026-03-23 18:03:59 +00:00
robbond 3f5006a744 TASK22224: harden setupcontainer hash compatibility parity 2026-03-23 17:59:31 +00:00
robbond 6e02ffe26d TASK22224: harden deleteawaitingsubmission route parity 2026-03-23 17:56:14 +00:00
robbond 5c29a82842 fix ui issue on awaiting submission card 2026-03-23 17:53:19 +00:00
robbond ab50430493 TASK22224: harden getbloblist and getprogressobjblob parity 2026-03-23 17:45:47 +00:00
robbond c84287be3c TASK22224: harden awaiting-submission blob retrieval contract 2026-03-23 17:42:25 +00:00
robbond f484f87694 TASK22224: harden getrepsblob against stale tag entries 2026-03-23 17:37:03 +00:00
robbond 8e0131d1ce TASK22224: align delete file route hash guards and contracts 2026-03-23 17:31:25 +00:00
robbond bc483c5097 TASK22224: document downloadblob hotfix closure and next file-guard slice 2026-03-23 17:23:55 +00:00
robbond bd3bf6834b TASK22224: accept encoded hash variants for downloadblob 2026-03-23 17:04:30 +00:00
robbond f09f3b75cc TASK22224: fix downloadblob path normalization for prefixed blob names 2026-03-23 17:01:03 +00:00
robbond f8fba6a561 TASK22224: harden file/static endpoint contracts and phase21 coverage 2026-03-23 16:40:39 +00:00
Robert Bond 78cac2cbb9 Merged PR 2198: update apis
Related work items: #22211
2026-03-23 12:38:30 +00:00
robbond 51eb9b5a4f TASK22211: version memory-bank and backfill change-log stream 2026-03-23 12:32:44 +00:00
robbond eec59e8e94 TASK22211: normalize token endpoint contract handling 2026-03-23 12:24:36 +00:00
robbond 8b6ed73508 TASK22211: normalize new-appeal appeal types endpoint contract 2026-03-23 12:19:43 +00:00
robbond 134f99c04c TASK22211: normalize address search endpoint contract 2026-03-23 12:16:31 +00:00
robbond 722ef9846a TASK22211: normalize hash and metadata endpoint contracts 2026-03-23 12:14:17 +00:00
robbond cb69bbe732 TASK22211: normalize case update and crm task endpoint contracts 2026-03-23 12:11:16 +00:00
robbond 88e4586541 TASK22211: normalize advanced search paged endpoint contract 2026-03-23 12:07:40 +00:00
robbond 98e159d523 TASK22211: normalize case creation and media endpoint contracts 2026-03-23 11:55:23 +00:00
robbond e0e91c843b TASK22211: normalize dns and representation endpoint contracts 2026-03-23 11:52:14 +00:00
robbond bcf03a65f9 TASK22211: normalize form and publication endpoint contracts 2026-03-23 11:47:18 +00:00
robbond b59f13a45d TASK22211: normalize metadata and linked-case endpoint contracts 2026-03-23 11:42:03 +00:00
robbond 2959c7d7a4 TASK22211: normalize delete and watched-case endpoint contracts 2026-03-23 11:38:36 +00:00
robbond 4601d7c15f TASK22211: normalize case detail endpoint contracts 2026-03-23 11:34:01 +00:00
robbond b5a3a62c4b TASK22211: normalize portal module and lpa case endpoint contracts 2026-03-23 11:30:14 +00:00
robbond a106dea6c9 TASK22211: normalize dns basic search endpoint contracts 2026-03-23 11:26:56 +00:00
robbond b880364e51 TASK22211: normalize basic search endpoint contracts 2026-03-23 11:23:07 +00:00
robbond 9af541ac6b TASK22211: normalize myportal retrieval endpoint contracts 2026-03-23 11:12:19 +00:00
robbond b57f3de06f TASK22211: standardize search-document endpoint contracts and phase21 coverage 2026-03-23 11:07:52 +00:00
Robert Bond 2caae7b458 Merged PR 2197: update endpoint api slice
Related work items: #22168
2026-03-23 10:45:31 +00:00
robbond a67f84a99f update error and response handling 2026-03-23 10:28:19 +00:00
robbond 26b889d182 TASK22168 bundle C endpoint login/account consistency extension 2026-03-19 12:27:17 +00:00
robbond f9c9687cfe TASK22168 endpoint account contract consistency slice 2026-03-19 12:11:52 +00:00
Robert Bond 54820324f5 Merged PR 2188: mocku pand welsh login flow
Related work items: #22079, #22081
2026-03-18 14:12:43 +00:00
robbond 20b07ec3d5 22079 mockup 2026-03-18 14:11:37 +00:00
robbond e3bb321eae 22081 welsh signin flow 2026-03-18 14:10:54 +00:00
Robert Bond 860291e1e6 Merged PR 2185: address parts now do and and not fail if more than one is added
address parts now do and and not fail if more than one is added

Related work items: #22093
2026-03-18 10:41:36 +00:00
robbond 0d23e47bea address parts now do and and not fail if more than one is added 2026-03-18 10:41:02 +00:00
Robert Bond 22b70f9081 Merged PR 2184: update apis with helpers
Related work items: #22109
2026-03-17 16:39:31 +00:00
robbond e9cf1cf634 TASK22109: email mini-slice tidy and notify negative-path tests 2026-03-17 16:04:50 +00:00
robbond d17f3a11d2 TASK22109: expand email contract coverage and logging hygiene 2026-03-17 14:28:22 +00:00
robbond 6610e5c7f3 TASK22109: email api contract cleanup and phase21 coverage 2026-03-17 14:23:42 +00:00
Robert Bond 864da9fe23 Merged PR 2183: stanadardised response contract test
Related work items: #22104
2026-03-17 14:11:54 +00:00
robbond 16a5493109 TASK22104: slice4 split phase21 helper and handler contract tests 2026-03-17 14:06:57 +00:00
robbond 0e99d9548b TASK22104: slice3 add failure-path api contract tests 2026-03-17 14:00:09 +00:00
robbond 991cbf73ba TASK22104: slice2 extend api contract success and download tests 2026-03-17 13:57:48 +00:00
robbond 25ba88ac34 TASK22104: slice1 add api response contract tests 2026-03-17 13:49:05 +00:00
Robert Bond f6c5e7183c Merged PR 2182: update apis with standardised response helpers
Related work items: #22102
2026-03-17 13:38:06 +00:00
robbond 91890f4d73 TASK22102: slice7 complex file handler contract closeout 2026-03-17 13:25:51 +00:00
robbond 98184b65df TASK22102: slice6 blob crud and retrieval contract cleanup 2026-03-17 13:23:01 +00:00
robbond 3048df3cd9 TASK22102: slice5 larger file handler contract cleanup 2026-03-17 13:16:48 +00:00
robbond e68319f16c TASK22102: slice4 larger file set + production detail suppression 2026-03-17 13:06:28 +00:00
robbond 3cbd876b77 TASK22102: slice3 migrate file proxy handlers 2026-03-17 12:58:20 +00:00
robbond f16161bcee TASK22102: slice2 migrate remaining email handlers 2026-03-17 12:55:05 +00:00
robbond 9ae8ce7136 TASK22102: slice1 api contract helper + email/admin pilot 2026-03-17 12:50:54 +00:00
Robert Bond 7d06bc7bfa Merged PR 2181: updated query and display logic for post determination fields
updated query and display logic for post determination fields

Related work items: #21710
2026-03-17 12:01:30 +00:00
robbond 8ff1af00b7 updated query and display logic for post determination fields 2026-03-17 12:00:30 +00:00
Robert Bond bda0af9e5c Merged PR 2178: removed app if not an app in capacity
removed app if not an app in capacity

Related work items: #22095
2026-03-17 11:16:42 +00:00
robbond 8ecde91bd9 removed app if not an app in capacity 2026-03-17 11:15:54 +00:00
Robert Bond 51f89320de Merged PR 2177: languge check for relevant template language
languge check for relevant template language

Related work items: #21979
2026-03-17 10:29:58 +00:00
robbond c64c47ca1f languge check for relevant template language 2026-03-17 10:29:00 +00:00
Robert Bond fcd51b41bd Merged PR 2176: update formatting
update formatting

Related work items: #22012
2026-03-17 09:24:41 +00:00
robbond 605932a640 removed commented out call for testing 2026-03-17 09:24:20 +00:00
robbond f1775c2a96 update formatting 2026-03-17 09:22:31 +00:00
Robert Bond 1e690608fb Merged PR 2172: applied downloadable checkbox on submit of new appeal
applied downloadable checkbox on submit of new appeal

Related work items: #22062
2026-03-16 13:58:54 +00:00
robbond 7c84344d5d applied downloadable checkbox on submit of new appeal 2026-03-16 13:58:02 +00:00
Robert Bond 72eafd0a8e Merged PR 2170: updated copy on representation complete submit
updated copy on representation complete submit

Related work items: #21095, #21979
2026-03-16 12:08:49 +00:00
robbond 071fcc0dab updated copy on representation complete submit 2026-03-16 12:07:39 +00:00
Robert Bond e9b2753fd9 Merged PR 2169: Api hardening
Related work items: #22057
2026-03-16 11:17:53 +00:00
robbond 18dfc60754 moved hashapipath to reusable single helper 2026-03-16 11:13:41 +00:00
robbond 023a8e9e6d api hardening 2026-03-16 10:46:05 +00:00
robbond 598e4a6077 TASK22057: phase19 endpoint consistency hardening slice 2026-03-14 07:06:57 +00:00
robbond fffe1d19dd TASK22057: phase18 additional consolidation slices 2026-03-14 06:52:17 +00:00
robbond daa38d6459 TASK22057: phase17 closeout hash/input consistency hardening 2026-03-13 18:43:42 +00:00
Robert Bond 36d35f9549 Merged PR 2168: update client side api calls with hardened hash
Related work items: #22028
2026-03-13 17:59:32 +00:00
robbond c2e14cab31 TASK22028: update memory-bank for myportal and i18n follow-up 2026-03-13 17:57:25 +00:00
robbond d32d7d016e TASK22028: fix myportal deleteRepItem caseRef and i18n namespace 2026-03-13 17:44:27 +00:00
robbond 6283786aeb TASK22028: expand signer coverage for delete flows 2026-03-13 16:40:51 +00:00
robbond e404a6d6db TASK22028: document signer allow-list follow-up 2026-03-13 16:31:21 +00:00
robbond 49dd99016a TASK22028: allow signer hash for deleteblobcase browser flow 2026-03-13 16:30:23 +00:00
robbond 6a9a257ae8 TASK22028: phase 16 memory-bank updates 2026-03-13 16:24:55 +00:00
robbond c196edb457 TASK22028: phase 16 file-proxy guard consistency slice 2026-03-13 16:23:34 +00:00
robbond 1a95cf1098 TASK22028: phase 15 hash guard consistency hardening slice 2026-03-13 16:04:31 +00:00
Robert Bond 0ca6cd94cc Merged PR 2167: APi hardening
Related work items: #22019
2026-03-13 14:24:39 +00:00
robbond 43204dfb54 Update memory-bank with phase 14 follow-up hardening closeout 2026-03-13 14:21:12 +00:00
robbond b8fa514e3b Align getrepsblob guard with route contract and fix quill utils import 2026-03-13 14:12:36 +00:00
robbond 6094874851 Route portal login hashing through authenticated signer 2026-03-13 14:01:18 +00:00
robbond 82f891ada8 Fix client hash signing via authenticated endpoint 2026-03-13 13:49:28 +00:00
robbond 846cba1645 TASK22019: phase 13 harden delete and involvement guards 2026-03-13 13:02:49 +00:00
robbond 0ca63d0228 TASK22019: phase 12 harden download and blob proxy guards 2026-03-13 12:49:54 +00:00
robbond 36260bb405 TASK22019: phase 11 harden blob/delete hash guards and negative paths 2026-03-13 12:32:37 +00:00
robbond 368ef86b74 TASK22019: phase 10 harden file retrieval hash guards and negative paths 2026-03-13 12:26:06 +00:00
robbond c73313b0fa TASK22019: phase 9 harden file handlers hash guards and negative paths 2026-03-13 12:17:51 +00:00
Robert Bond 51269db849 Merged PR 2166: hardening for sensitive hash-guarded APIs
TASK22017: phase 8 hardening for sensitive hash-guarded APIs

Related work items: #22017
2026-03-13 11:43:17 +00:00
robbond 52202f4e1c TASK22017: phase 8 hardening for sensitive hash-guarded APIs 2026-03-13 11:38:14 +00:00
Robert Bond f28b25cc24 Merged PR 2165: TASK21988a: Phase 7 search SSR fix and service behaviour expansion
TASK21988a: Phase 7 search SSR fix and service behaviour expansion

Related work items: #21998
2026-03-13 11:01:10 +00:00
robbond e39c9203e3 TASK21988a: Phase 7 search SSR fix and service behaviour expansion 2026-03-13 10:58:26 +00:00
Robert Bond 3abebc8093 Merged PR 2164: test(actions): phase 6 add parity and behavioural service hardening checks
test(actions): phase 6 add parity and behavioural service hardening checks

Related work items: #21998
2026-03-13 10:16:46 +00:00
robbond b5ae6c271f test(actions): phase 6 add parity and behavioural service hardening checks 2026-03-13 10:11:33 +00:00
Robert Bond a35f2990c6 Merged PR 2163: split actions to individual services
Related work items: #21997
2026-03-13 07:42:22 +00:00
robbond 0c4a50a9e1 refactor(actions): remove legacy actions service after phase 5 split 2026-03-13 06:47:00 +00:00
robbond 13e7e0c1a4 refactor(actions): phase 5 extract remaining direct service modules 2026-03-13 06:44:10 +00:00
robbond cd13453121 refactor(actions): phase 5 extract account and case direct services 2026-03-13 06:41:47 +00:00
robbond 9a24ba1774 refactor(actions): phase 5 extract portal direct service 2026-03-13 06:37:24 +00:00
robbond 906be69648 refactor(actions): phase 5 extract document direct service 2026-03-13 06:32:29 +00:00
robbond 344b6efed1 chore(lint): unblock next lint and fix current eslint errors 2026-03-13 06:24:38 +00:00
robbond 903118e47d refactor(actions): phase 5 extract search and reference direct services 2026-03-13 06:15:32 +00:00
Robert Bond 1d1baec43b Merged PR 2162: refactor actions
Related work items: #21996
2026-03-13 05:51:45 +00:00
robbond bef8f2590b chore(refactor): remove stale legacy actions import comments 2026-03-12 17:41:36 +00:00
robbond fa985351ff refactor(actions): migrate endpoint and file proxies to focused imports 2026-03-12 17:39:50 +00:00
robbond f0202bbd02 refactor(actions): migrate viewall/search/unsubscribe to focused imports 2026-03-12 17:34:03 +00:00
robbond c4ad9ab7a2 refactor(actions): complete priority-1 consumer import migration pass 2026-03-12 13:26:48 +00:00
robbond a0a633a9ea refactor phase 3a components chunk 1 targeted actions imports 2026-03-12 12:13:29 +00:00
robbond be59237ec9 refactor phase 3a api-file chunk 3 targeted actions imports 2026-03-12 11:46:06 +00:00
robbond 61913f9f80 refactor phase 3a api-file chunk 2 targeted actions imports 2026-03-12 11:41:40 +00:00
robbond 5822930f0a refactor phase 3a api-file chunk 1 targeted actions imports 2026-03-12 11:38:18 +00:00
robbond 2a3c7eac34 refactor phase 3a api-endpoint chunk 5a targeted actions imports 2026-03-12 11:31:38 +00:00
robbond 86f41f81e9 refactor phase 3a api-endpoint chunk 5 targeted actions imports 2026-03-12 11:21:40 +00:00
robbond 79141ac6a9 refactor phase 3a api-endpoint chunk 4b targeted actions imports 2026-03-12 11:09:52 +00:00
robbond 36e41e064d refactor phase 3a api-endpoint chunk 4a targeted actions imports 2026-03-12 11:07:25 +00:00
robbond d91ceda196 refactor phase 3a api-endpoint chunk 4 targeted actions imports 2026-03-12 10:50:16 +00:00
robbond 8b8742753e refactor phase 3a api-endpoint chunk 3 targeted actions imports 2026-03-12 10:31:28 +00:00
robbond 8141cb2394 refactor phase 3a api-endpoint chunk 2 targeted actions imports 2026-03-12 10:15:29 +00:00
robbond e7882c9f78 refactor phase 3a api-endpoint chunk 1 targeted actions imports 2026-03-12 10:08:13 +00:00
robbond 7bd5c7d833 refactor phase 3a api admin auth documents email targeted actions imports 2026-03-12 09:58:39 +00:00
robbond cf99d8e466 refactor phase 3a pages-dns targeted actions imports 2026-03-12 09:53:17 +00:00
robbond 1a8a28fe3a refactor phase 3a top-level pages dbcheck targeted actions import 2026-03-12 09:51:13 +00:00
robbond c33cdf1b28 refactor phase 3a top-level pages targeted actions imports 2026-03-12 09:49:40 +00:00
robbond eebcfffd5e refactor phase 3a pages-newappeal-and-case targeted actions imports 2026-03-12 09:43:47 +00:00
robbond 0da4a4b3b4 refactor phase 3a pages-admin targeted actions imports 2026-03-12 09:35:25 +00:00
robbond 570bbb2fa1 refactor phase 3a pages-auth targeted actions imports 2026-03-12 09:34:50 +00:00
robbond 3550faf051 refactor phase 3a pages-account targeted actions imports 2026-03-12 09:34:16 +00:00
robbond 72e4655188 refactor phase 3a myportal representation and case imports 2026-03-12 09:26:34 +00:00
robbond dacf2511f5 refactor phase 3a myportal index targeted actions imports 2026-03-12 09:25:56 +00:00
robbond 5c54220bf8 refactor phase 3a myportal case-by-incident imports 2026-03-12 09:24:10 +00:00
robbond 548c85e339 refactor phase 3a myportal dns imports 2026-03-12 09:22:17 +00:00
robbond 562cffdc59 refactor phase 3a myportal advanced and address search imports 2026-03-12 09:20:33 +00:00
robbond cbcb9549be refactor phase 3a myportal search results imports 2026-03-12 09:20:01 +00:00
robbond 694eab8297 refactor phase 3a myportal contactus targeted actions imports 2026-03-12 09:18:28 +00:00
robbond a0ae6fcba3 refactor phase 4 hardening helpers and redacted logging 2026-03-12 09:03:24 +00:00
robbond aeec5e0f59 refactor phase 3 targeted actions imports 2026-03-12 07:39:06 +00:00
robbond 2950ce788a refactor actions into core modules and grouped services 2026-03-12 07:30:40 +00:00
Robert Bond 0bd6e12877 Merged PR 2159: adjusted logic to cater for viewl all path and dnsapplications
adjusted logic to cater for viewl all path and dnsapplications

Related work items: #21942, #21943, #21971
2026-03-11 10:42:44 +00:00
robbond f947821afa adjusted logic to cater for viewl all path and dnsapplications 2026-03-11 10:40:48 +00:00
Robert Bond 9c483ec655 Merged PR 2158: fixed breacrumb and label
fixed breacrumb and label

Related work items: #18688, #21942, #21943
2026-03-10 18:06:46 +00:00
robbond df311e131a fixed breacrumb and label 2026-03-10 18:02:37 +00:00
Robert Bond 93d02697a2 Merged PR 2157: correctly go back to the previous page search results
correctly go back to the previous page search results

Related work items: #18688, #21942, #21943
2026-03-10 10:33:13 +00:00
robbond 3e48f8cd28 correctly go back to the previous page search results 2026-03-10 10:31:16 +00:00
Robert Bond e6c25d7ef5 Merged PR 2154: notify check for new case ref tempate and pref lang
notify check for new case ref tempate and pref lang

Related work items: #21722, #21726
2026-03-09 14:11:29 +00:00
robbond 6e3f7eeec1 notify check for new case ref tempate and pref lang 2026-03-09 14:10:30 +00:00
Robert Bond de5ee86cde Merged PR 2153: fix for pdf formatting and hyperlinks
fix for pdf formatting and hyperlinks

Related work items: #21897, #21898
2026-03-09 13:13:04 +00:00
robbond 96e180040d fix for pdf formatting and hyperlinks 2026-03-09 13:11:28 +00:00
Robert Bond 95c1492fe0 Merged PR 2152: check box to download questionnaire
chekc box to download questionnaire

Related work items: #17556
2026-03-09 12:14:14 +00:00
robbond 2c6d110d9b chekc box to download questionnaire 2026-03-09 12:13:24 +00:00
Robert Bond e2c79d6b0f Merged PR 2134: regex update to include apostrophe
regex update to include apostrophe

Related work items: #21099
2026-02-27 09:42:32 +00:00
robbond 0e9a4da4be regex update to include apostrophe 2026-02-27 09:41:40 +00:00
Robert Bond db184984ca Merged PR 2133: removed the help section on registration
removed the help section on registration

Related work items: #21093
2026-02-27 09:04:54 +00:00
robbond 610056849d removed the help section on registration 2026-02-27 09:04:01 +00:00
Robert Bond 92eb0c5a15 Merged PR 2132: included emdash in doc upload validation
included emdash in doc upload validation

Related work items: #21099
2026-02-26 13:05:10 +00:00
robbond cfe732f299 included emdash in doc upload validation 2026-02-26 13:04:28 +00:00
Robert Bond b39ed0fd8c Merged PR 2123: updated to allow new appeals to work post ph merge
updated to allow new appeals to work post ph merge

Related work items: #17555
2026-02-23 12:47:20 +00:00
robbond 21eceff8df updated to allow new appeals to work post ph merge 2026-02-23 12:46:15 +00:00
Robert Bond 43c692a959 Merged PR 2121: missing case ref failing sip reps pdf
missing case ref failing sip reps pdf

Related work items: #21254
2026-02-20 16:17:22 +00:00
robbond ab847a62b3 missing case ref failing sip reps pdf 2026-02-20 16:16:34 +00:00
Robert Bond 292a280f50 Merged PR 2120: update to cater for tickenumber not title
had been done on ph2 needed updating for sips

Related work items: #21254
2026-02-20 12:00:14 +00:00
robbond 7da20386dd config 2026-02-20 11:59:09 +00:00
robbond 5e46495e79 reference ticket number not title, caters for long titles 2026-02-20 11:58:35 +00:00
Robert Bond c438a62553 Merged PR 2115: reinstate sips rep button logic
reinstate sips rep button logic

Related work items: #21254
2026-02-20 07:44:14 +00:00
robbond 5013b967f3 reinstate sips rep button logic 2026-02-20 07:40:27 +00:00
Robert Bond 85b0a7e4c4 Merged PR 2112: pass mediaobj to case pages
Related work items: #21254
2026-02-19 14:14:42 +00:00
robbond 5cd3744e52 passing mediaobj to dns case and myportal dns case 2026-02-19 14:12:55 +00:00
robbond 9d355f6f77 passing media obj in myportal search 2026-02-19 13:58:58 +00:00
Robert Bond 38a948e9a0 Merged PR 2070: media tab and player
Related work items: #21254, #21255, #21256
2026-02-04 11:23:21 +00:00
robbond 53540911cc 21254 youtube media player 2026-02-04 11:22:11 +00:00
robbond ec067e62a5 21254 media tab 2026-02-04 11:21:05 +00:00
robbond 3bd1e7e7fe 21256 mocked api response 2026-02-04 11:20:29 +00:00
robbond dc8958f7a1 updated service status down functionality 2026-02-04 11:19:35 +00:00
Robert Bond 5c6ee2fedd Merged PR 2064: update language delimeter to >>>
Related work items: #21187
2026-01-27 12:01:12 +00:00
robbond 1b586da1ae 21187 implement helper function on summary 2026-01-27 11:58:40 +00:00
robbond 49374640e5 21187 add bilingual helper function 2026-01-27 11:57:44 +00:00
Robert Bond 3569bce8d1 Merged PR 2060: added in link and label in english and welsh
added in link and label in english and welsh

Related work items: #21091
2026-01-22 12:19:28 +00:00
robbond 6b7403f6c8 added in link and label in english and welsh 2026-01-22 12:18:03 +00:00
Robert Bond a65ed04ad7 Merged PR 2055: 21099 api serverside validation check
21099 api serverside validation check

Related work items: #21099
2026-01-21 13:18:35 +00:00
robbond dd12dc99da 21099 api serverside validation check 2026-01-21 13:17:03 +00:00
Robert Bond e0392b108c Merged PR 2051: Update filename validation
Related work items: #21099, #21154
2026-01-20 15:22:40 +00:00
robbond e6b0b0763a 21099 update filename validation with # 2026-01-20 15:20:54 +00:00
robbond 737b74539d 21154 updated language labels 2026-01-20 15:20:01 +00:00
Robert Bond b9434e07a3 Merged PR 2038: 18791 remove further details
Related work items: #21097, #21098
2026-01-19 13:17:43 +00:00
robbond cfe6070d57 21098 update rep pdfs remove further details 2026-01-19 13:16:20 +00:00
robbond 4710d3e420 21097 remove ui for further details 2026-01-19 13:15:50 +00:00
Robert Bond 088c93e2cf Merged PR 2034: updates to new appeal
restricts completion of an appeal or rep if no docapi....stop it failing if already started.

Related work items: #17559
2026-01-15 13:23:55 +00:00
robbond 8a0c82f4e0 update env.local with d365 2026-01-15 13:21:26 +00:00
robbond 83dc4e12e3 refactor appeal for new and resume 2026-01-15 13:21:11 +00:00
robbond 9241fe0b84 updated restriction to disallow partial reps and appeals from being submitted when not docapi 2026-01-15 13:20:07 +00:00
Robert Bond 94861846aa Merged PR 2026: added searching by LPA application ref and criteria highlighting
added searching by LPA application ref and criteria highlighting

Related work items: #18544
2026-01-09 13:38:36 +00:00
robbond 9b0f9acff9 added searching by LPA application ref and criteria highlighting 2026-01-09 13:37:40 +00:00
Robert Bond 062f64d157 Merged PR 2024: Update validation for decision date
Related work items: #17559
2026-01-08 12:52:08 +00:00
robbond dfaa54a6cf updated start date validation to be dependant on value of dateofapplication 2026-01-06 12:47:56 +00:00
Robert Bond 6978114ce0 Merged PR 2017: merge latest ph2 to sips
Related work items: #20553
2025-12-02 14:17:22 +00:00
robbond 3a5497fe76 merged 2025-12-02 14:12:55 +00:00
robbond 5889c4093b Merge branch 'Development' into SIPS-Development 2025-12-02 14:03:48 +00:00
Robert Bond 140d615310 Merged PR 2016: part 2 ph2 update
part 2 ph2 update

Related work items: #20553
2025-11-28 13:30:31 +00:00
robbond 1f933b6d17 part 2 ph2 update 2025-11-28 13:27:52 +00:00
Robert Bond ab1df81745 Merged PR 2009: partial synch with ph2 updates
partial synch with ph2 updates

Related work items: #20553
2025-11-28 08:50:01 +00:00
robbond 3f4bd8d52e partial synch with ph2 updates 2025-11-28 08:45:13 +00:00
Robert Bond dc14e6c9af Merged PR 1869: ph 2 merge to sips
Related work items: #18559
2025-09-10 09:22:28 +00:00
robbond b5b114f455 Merge branch 'Development' into SIPS-Development 2025-09-10 10:18:07 +01:00
Robert Bond bf2679045e Merged PR 1835: merge in PH2 work
Related work items: #17344, #17346, #17392, #17466, #17552, #17592, #17728, #18356
2025-08-20 11:14:26 +00:00
robbond 92ceacb57b Merge branch 'Development' into SIPS-Development 2025-08-20 12:11:55 +01:00
Robert Bond 8f1aede2e3 Merged PR 1787: 17346 updated sips in line
17346 updated sips in line

Related work items: #17346
2025-07-29 11:31:24 +00:00
robbond d0b2fd0996 17346 updated sips in line 2025-07-29 12:30:20 +01:00
Robert Bond 4562e5f092 Merged PR 1772: show reps on results for dns search
show reps on results for dns search

Related work items: #13922
2025-07-24 08:24:20 +00:00
robbond 3ccb96abc5 show reps on results for dns search 2025-07-24 09:21:26 +01:00
Robert Bond ab04761677 Merged PR 1730: scroll top for anorid
Related work items: #12956, #17344
2025-07-08 08:09:51 +00:00
robbond cb2ade03b4 update git ignore 2025-07-08 09:08:35 +01:00
robbond 103c548e32 added scrolltop 2025-07-08 09:08:13 +01:00
robbond 28fb2ecbe7 Merge branch 'SIPS-Development' of https://git.rdbdata.com/robbond/pedwFrontend into SIPS-Development 2025-07-08 08:59:21 +01:00
robbond 8713317404 Merged PR 1717: merge dev to sips
Related work items: #12956
2025-07-08 08:58:41 +01:00
robbond 7c1262e4dc Update Jenkinsfile 2025-07-07 15:06:28 +00:00
robbond 777d37f8b4 Add Jenkinsfile 2025-07-07 15:02:39 +00:00
robbond a24d68b8c4 Add Dockerfile 2025-07-07 12:02:02 +00:00
Robert Bond dcb67e0de3 Merged PR 1717: merge dev to sips
Related work items: #12956
2025-07-03 10:21:50 +00:00
robbond 39dc5a349a Merge branch 'Development' into SIPS-Development 2025-07-03 11:20:38 +01:00
Robert Bond a5dbd52d73 Merged PR 1699: past sprint defect fixes
Related work items: #16503, #17110
2025-06-26 05:23:16 +00:00
robbond 53f4b8140a 16503 email for consutlation response update 2025-06-26 06:21:30 +01:00
robbond 9e1d091116 removed extra heading of submission 2025-06-26 06:12:30 +01:00
Robert Bond 7e3c79830c Merged PR 1696: link on portal for pdf download
link on portal for pdf download

Related work items: #12925
2025-06-24 09:53:17 +00:00
robbond 4d310fd6c1 link on portal for pdf download 2025-06-24 10:52:06 +01:00
Robert Bond febfb4e532 Merged PR 1688: 17143 removed pref from myportal page rely on link for lang
17143 removed pref from myportal page rely on link for lang

Related work items: #17143
2025-06-18 12:06:32 +00:00
robbond a92b0af2db 17143 removed pref from myportal page rely on link for lang 2025-06-18 12:49:38 +01:00
Robert Bond 806952b98d Merged PR 1686: 17143 adjsut switcher
17143 adjsut switcher

Related work items: #17143
2025-06-18 09:18:21 +00:00
robbond 412f7d2e90 17143 adjsut switcher 2025-06-18 10:17:26 +01:00
Robert Bond 56ad0c4843 Merged PR 1685: 17143 pref lang abck in
17143 pref lang abck in

Related work items: #17143
2025-06-18 08:36:26 +00:00
robbond ffae7df890 17143 pref lang abck in 2025-06-18 09:29:43 +01:00
Robert Bond 18ada0f9b1 Merged PR 1684: 17169 update portal with document location
Related work items: #17169
2025-06-18 08:11:53 +00:00
robbond 6900d02f8c csp update 2025-06-18 09:09:45 +01:00
robbond 4b92f40018 test language switcher 2025-06-18 09:09:06 +01:00
robbond 0df0ad5faa 17169 added consultation response location id 2025-06-18 09:01:41 +01:00
Robert Bond 7eda5ffd37 Merged PR 1681: 17143 switcher fix..
17143 switcher fix..

Related work items: #17143
2025-06-17 10:45:47 +00:00
robbond 65f6b65613 17143 switcher fix.. 2025-06-17 11:40:34 +01:00
Robert Bond 26130d12d4 Merged PR 1679: 17143 language switcher refactor push
17143 language switcher refactor push

Related work items: #17143
2025-06-17 05:46:01 +00:00
robbond e2df26c935 17143 language switcher refactor push 2025-06-17 06:44:53 +01:00
Robert Bond 3b65133ea0 Merged PR 1676: 17124 appeal pdf default ishare location added
17124 appeal pdf default ishare location added

Related work items: #17124
2025-06-13 14:34:01 +00:00
robbond d31002f0ed 17124 appeal pdf default ishare location added 2025-06-13 15:19:34 +01:00
Robert Bond ffd6943aa1 Merged PR 1674: Language switching issues
Related work items: #16185, #16630
2025-06-13 12:55:16 +00:00
robbond 17f516b797 16630 adjusted cookie 2025-06-13 13:43:07 +01:00
robbond 46876cdff6 16185 and 16630 language switching 2025-06-13 12:58:38 +01:00
robbond b4451dc7ee build warning fix 2025-06-13 12:53:51 +01:00
Robert Bond 4cea869256 Merged PR 1668: Contact details for owners and tenants
Related work items: #16580, #16583
2025-06-11 17:31:34 +00:00
robbond 89f1691c8f 16580 contact fields for owners and tenants 2025-06-11 18:26:14 +01:00
robbond 5fc3b3867c 16583 appeal pdf update 2025-06-11 18:24:03 +01:00
robbond a80fa87ea2 refactor 2025-06-11 13:05:18 +01:00
Robert Bond 19ae9b9f63 Merged PR 1665: 17105 no LIR for applicant and refactor
17105 no LIR for applicant and refactor

Related work items: #17105
2025-06-11 09:46:29 +00:00
robbond 40f7b92dc6 17105 no LIR for applicant and refactor 2025-06-11 10:44:18 +01:00
Robert Bond 9c42fc64f0 Merged PR 1664: 17025 proxy for delete cases
17025 proxy for delete cases

Related work items: #17025
2025-06-10 16:58:04 +00:00
robbond 1514eef343 17025 proxy for delete cases 2025-06-10 17:56:59 +01:00
Robert Bond 5561e9f9bf Merged PR 1663: 17025 viewall unsub fix
17025 viewall unsub fix

Related work items: #17025
2025-06-10 16:17:41 +00:00
robbond e920ea681e 17025 viewall unsub fix 2025-06-10 17:16:42 +01:00
Robert Bond e97f514c3f Merged PR 1662: 17025 unsub getwatchedcases proxy fix csp
17025 unsub getwatchedcases proxy fix csp

Related work items: #17025
2025-06-10 15:58:40 +00:00
robbond 941e030b57 17025 unsub getwatchedcases proxy fix csp 2025-06-10 16:57:01 +01:00
Robert Bond 28eb95018a Merged PR 1660: unsub issue and 14626
Related work items: #14626, #17025
2025-06-10 11:17:08 +00:00
robbond d0c56d1741 14626 reporting decision fields to summary 2025-06-10 12:08:01 +01:00
robbond 193da6af2f 17025 unsub issues 2025-06-10 11:25:32 +01:00
Robert Bond 5dee64dfec Merged PR 1659: 17025 unsub fixes
17025 unsub fixes

Related work items: #17025
2025-06-09 13:14:57 +00:00
robbond f11aa09412 17025 unsub fixes 2025-06-09 14:12:44 +01:00
Robert Bond 3d897e6b26 Merged PR 1657: 17025 email notiofications fixes
17025 email notiofications fixes

Related work items: #17025
2025-06-05 17:08:47 +00:00
robbond d6fd08a8ff 17025 email notiofications fixes 2025-06-05 18:07:52 +01:00
Robert Bond f40514b4ea Merged PR 1655: 16937 updated translation for contact us button
16937 updated translation for contact us button

Related work items: #16937
2025-06-04 09:04:09 +00:00
robbond 408dae0ca3 16937 updated translation for contact us button 2025-06-04 10:03:30 +01:00
Robert Bond c14b0d0a40 Merged PR 1654: welsh language contact us form and mailing list
Related work items: #16937, #16945
2025-06-03 18:05:40 +00:00
robbond cdf48a81a7 16937 welsh contact us form 2025-06-03 19:03:53 +01:00
robbond 954e58ee3f 16945 welsh template for mailing list 2025-06-03 18:38:56 +01:00
Robert Bond 6e764e9364 Merged PR 1653: right click link issues
Related work items: #16185, #16477, #16478, #16630
2025-06-03 16:18:03 +00:00
robbond dedd23dfb1 right click on language switcher 2025-06-03 17:15:11 +01:00
robbond 1220131cfd 16478 right click view all page titles 2025-06-03 16:11:51 +01:00
Robert Bond 3555688e39 Merged PR 1652: 16986 fixed unsubscribe all
16986 fixed unsubscribe all

Related work items: #16986
2025-06-03 04:46:17 +00:00
robbond c38da6c8d8 16986 fixed unsubscribe all 2025-06-03 05:45:23 +01:00
Robert Bond 297a5dfc77 Merged PR 1651: 16986 unsubscribe link
16986 unsubscribe link

Related work items: #16986
2025-06-02 16:43:22 +00:00
robbond 6c7eca581b 16986 unsubscribe link 2025-06-02 17:41:39 +01:00
Robert Bond 8b244ffb94 Merged PR 1648: 16578 link url updated
16578 link url updated

Related work items: #16578
2025-05-30 13:23:49 +00:00
robbond 089c225e4d 16578 link url updated 2025-05-30 14:23:13 +01:00
Robert Bond 0e33718c34 Merged PR 1647: Email notifications
Related work items: #16577, #16578, #16951
2025-05-30 09:20:25 +00:00
robbond 511713eede 16578 mailout api from mailing list 2025-05-30 10:18:50 +01:00
robbond ff85f525a5 update subscribe function 2025-05-30 10:18:14 +01:00
robbond 2cfb06db2a 16951 mail notification subscribe 2025-05-30 10:16:21 +01:00
Robert Bond eb020b014d Merged PR 1646: sprint 8 tasks and app error fix
Related work items: #14627, #16943, #16944
2025-05-28 13:16:02 +00:00
robbond 6354cabde0 14627 ui for email notifications 2025-05-28 14:14:46 +01:00
robbond f725c811b3 16944 logged contact us 2025-05-28 14:12:56 +01:00
robbond 8e6b88984f 16493 routing for PET team send email 2025-05-28 14:12:31 +01:00
robbond e04f87323f App error on selecting case 2025-05-28 13:02:35 +01:00
Robert Bond ea1d530155 Merged PR 1645: 14949 activity from contact us
Related work items: #16290, #16291, #16938
2025-05-27 17:16:21 +00:00
robbond b72df06349 16920 contatus ui 2025-05-27 18:08:41 +01:00
robbond 87812a6bf2 16291 crm activity 2025-05-27 18:05:54 +01:00
Robert Bond 7aa7e2819a Merged PR 1643: right click and language pref bugs
Related work items: #16185, #16478, #16630
2025-05-26 06:08:54 +00:00
robbond 26070426e8 right click bugs 2025-05-26 07:07:12 +01:00
robbond d85b59a25f updated to check language selector cookie and pref language 2025-05-26 06:54:50 +01:00
Robert Bond 0cf344ea85 Merged PR 1640: right click fixes
Related work items: #16478
2025-05-22 15:30:23 +00:00
robbond deb7b12313 right click to open dns applications 2025-05-22 16:28:40 +01:00
robbond 29abd0efd0 rioght mouse click on view all 2025-05-22 14:15:42 +01:00
robbond 3877323b9b removed s from SIPs - is singular not plural 2025-05-20 13:15:06 +01:00
Robert Bond b4bec11fa0 Merged PR 1631: updated the url in next config
updated the url in next config

Related work items: #16477
2025-05-16 13:44:59 +00:00
robbond afa81d8bd6 updated the url in next config 2025-05-16 14:19:17 +01:00
Robert Bond ae25ad4673 Merged PR 1630: added reps/cons details on viewall
added reps/cons details on viewall

Related work items: #16500
2025-05-15 13:05:14 +00:00
robbond ae007d18ad added reps/cons details on viewall 2025-05-15 14:04:27 +01:00
Robert Bond 2fb7895234 Merged PR 1627: added nrw domain as env variable
added nrw domain as env variable

Related work items: #16003
2025-05-15 09:16:20 +00:00
robbond c738d2bde9 added nrw domain as env variable 2025-05-15 10:15:41 +01:00
Robert Bond bfac0d1ff9 Merged PR 1617: package updates
Related work items: #16080
2025-05-12 18:42:10 +00:00
robbond f0059eb949 added ubservr 2025-05-12 19:41:02 +01:00
robbond 50c2feaeba and again 2025-05-12 19:01:28 +01:00
Robert Bond 72ef0f7f40 Merged PR 1616: updated start
updated start

Related work items: #16080
2025-05-12 17:17:29 +00:00
robbond dd0ed06732 updated start 2025-05-12 18:17:00 +01:00
Robert Bond 48f24a6330 Merged PR 1615: update package lock
update package lock

Related work items: #16080
2025-05-12 16:34:12 +00:00
robbond 4fc0917b78 update package lock 2025-05-12 17:33:23 +01:00
Robert Bond 38f7853723 Merged PR 1613: added in package lock to repo
added in package lock to repo

Related work items: #16080
2025-05-12 16:14:22 +00:00
robbond 9dc135ef66 added in package lock to repo 2025-05-12 17:13:14 +01:00
Robert Bond e961a1dff4 Merged PR 1612: added prisma
added prisma

Related work items: #16080
2025-05-12 12:33:18 +00:00
robbond 5ebffe74af added prisma 2025-05-12 13:30:12 +01:00
Robert Bond f498d1a09e Merged PR 1605: set documentlocationid
set documentlocationid

Related work items: #16004
2025-05-08 11:54:32 +00:00
robbond c0b23fff6c set documentlocationid 2025-05-08 12:53:25 +01:00
Robert Bond 7f5fd90a17 Merged PR 1591: Consultation reps to have text area for sips
Consultation reps to have text area for sips

Related work items: #16010
2025-04-30 05:43:26 +00:00
robbond 8f9fd50f36 Consultation reps to have text area for sips 2025-04-30 06:42:32 +01:00
Robert Bond 2e454290c7 Merged PR 1588: 16345 no submit empty rep
Related work items: #16345
2025-04-28 17:02:33 +00:00
robbond eae42c3622 16435 logic to disable continue button so cant submit empty rep 2025-04-28 17:14:59 +01:00
robbond 4bd1cf9744 removed signout link in header on reps page 2025-04-28 17:14:26 +01:00
Robert Bond 0346b57d19 Merged PR 1586: SIPS Sprint 6 280425
16003 Sips Documents, 16010 Role baes reps for sips

Related work items: #16003, #16010
2025-04-28 12:54:29 +00:00
robbond a33018d225 16003 Sips Documents, 16010 Role baes reps for sips 2025-04-28 13:53:25 +01:00
Robert Bond 0f7a9c36df Merged PR 1585: Env Permitting fix and 14624
Related work items: #14624
2025-04-27 13:16:03 +00:00
robbond 372e1bf7ce Fix for eiarequired named wrong on env permittins 2025-04-27 14:14:40 +01:00
robbond ad5c726b54 14624 rep button logic for sips 2025-04-27 14:14:18 +01:00
Robert Bond b6798fbfca Merged PR 1570: DEV merge
Related work items: #12956
2025-04-21 09:10:21 +00:00
robbond 43a194bf98 Merge branch 'development' into SIPS-Development 2025-04-21 10:08:58 +01:00
Robert Bond 0433304faf Merged PR 1566: merge of dev csp work
Related work items: #15520
2025-04-16 14:48:21 +00:00
robbond 0d91e75811 csp frame src set for map wales 2025-04-16 15:47:20 +01:00
robbond f586842b39 key in notice banner 2025-04-16 15:47:05 +01:00
robbond 81fe090ce0 Merge branch 'Development' into SIPS-Sprint-5-dev-merge-csp 2025-04-16 15:46:10 +01:00
robbond 2dc8a721ab Merge branch 'Development' into SIPS-Development 2025-04-16 08:27:06 +01:00
Robert Bond 0c8f166410 Merged PR 1560: CSP and language preference
Related work items: #16079, #16080
2025-04-15 12:28:39 +00:00
robbond c7c74b6250 language check update 2025-04-15 13:26:53 +01:00
robbond a26313c798 Merge branch 'Development' into SIPS-Development 2025-04-15 13:22:35 +01:00
Robert Bond e24fe83d0a Merged PR 1558: Language selection
Related work items: #12956
2025-04-15 08:48:19 +00:00
robbond 7c5582f6c2 Merge branch 'Development' into SIPS-Development 2025-04-15 09:29:23 +01:00
Robert Bond d6206ee2be Merged PR 1553: 15643 Document list showing document publish date
15643 Document list showing document publish date

Related work items: #15643
2025-04-10 12:18:27 +00:00
robbond cb8b7938f5 15643 Document list showing document publish date 2025-04-10 13:17:11 +01:00
Robert Bond 13e598c60c Merged PR 1548: Merge Welsh login url fix to SIPS
Related work items: #12956
2025-04-09 09:35:24 +00:00
robbond 2af5c712ab Merge branch 'Development' into SIPS-Development 2025-04-09 10:33:26 +01:00
Robert Bond ed077214bb Merged PR 1535: SIPS Sprint 4 020425
Related work items: #14625, #15419, #15518
2025-04-02 17:09:14 +00:00
robbond ac1c127548 14625 submission ui portal 2025-04-02 18:08:07 +01:00
robbond 9a44e36ceb 15518 Env Statement Location on Summary 2025-04-02 12:03:22 +01:00
robbond 8a24852961 15419 update eia required field 2025-04-02 09:13:09 +01:00
Robert Bond c14c86cdbd Merged PR 1531: 15439 removed double click on dropdowns
15439 removed double click on dropdowns

Related work items: #15439
2025-04-01 15:34:15 +00:00
robbond 46bd30cd76 15439 removed double click on dropdowns 2025-04-01 16:33:27 +01:00
Robert Bond 7d86ac0ca4 Merged PR 1529: 15439 sips advanced search
15439 sips advanced search

Related work items: #15439
2025-03-31 16:20:24 +00:00
robbond 479f30b0a4 15439 sips advanced search 2025-03-31 17:19:50 +01:00
Robert Bond e6498c813d Merged PR 1525: Update SIPS Dev branch
Related work items: #12956
2025-03-28 10:51:32 +00:00
robbond a5df22a486 Merge branch 'SIPS-Development' of https://welshgovernment.visualstudio.com/PEDW%20-%20Planning%20Casework/_git/FrontEnd into SIPS-Development 2025-03-28 10:48:52 +00:00
Robert Bond b912f9ecb8 Merged PR 1523: App error fix
Related work items: #15439
2025-03-28 09:00:01 +00:00
robbond 1a740bd78b 15439 fix for app error in advanced search 2025-03-28 06:38:06 +00:00
robbond cc0e830cf8 Merge branch 'Development' into SIPS-Sprint-3-270325 2025-03-27 13:25:10 +00:00
robbond 927cd70d75 Merge branch 'Development' into SIPS-Development 2025-03-27 13:18:46 +00:00
robbond 8cca0db6e9 mime type fix for ithc 2025-03-27 11:03:24 +00:00
robbond a09c458aed dmw preliminary work 2025-03-27 11:02:32 +00:00
robbond 93f385e0b0 Merge branch 'Development' into SIPS-Development 2025-03-25 13:14:10 +00:00
Robert Bond 5c829f1d26 Merged PR 1511: EIA requried fields updating
EIA requried fields updating

Related work items: #15404
2025-03-25 10:48:51 +00:00
robbond 1c65d49110 EIA requried fields updating 2025-03-25 10:47:22 +00:00
Robert Bond a6cdec43e1 Merged PR 1507: 14769 search by project type
14769 search by project type

Related work items: #14769
2025-03-21 17:40:49 +00:00
robbond b50f94074b 14769 search by project type 2025-03-21 17:40:11 +00:00
Robert Bond 4d4d1fe995 Merged PR 1504: SIPS Sprint 3 200325
Related work items: #14768, #15085
2025-03-20 11:26:27 +00:00
robbond 11c9df9483 update api for coords for DMW 2025-03-20 11:24:59 +00:00
robbond 19a377f49f 15085 EIA required and remove case officer 2025-03-20 11:24:09 +00:00
robbond cd750a9228 14768 Project type on Sips summary 2025-03-20 11:22:41 +00:00
Robert Bond 98251d5da9 Merged PR 1501: 14759 display logic and welsh translations
14759 display logic and welsh translations

Related work items: #14759
2025-03-18 10:49:19 +00:00
robbond d01d83ecc4 14759 display logic and welsh translations 2025-03-18 10:47:50 +00:00
Robert Bond 626b2720ab Merged PR 1499: SIPS Sprint 3 14760 tabs ui
Related work items: #14758, #15079
2025-03-18 06:07:21 +00:00
robbond 837d7c68f7 14758 tabs ui 2025-03-18 06:06:05 +00:00
robbond f7af4d4bcc 15079 show eia required label 2025-03-18 06:05:26 +00:00
Robert Bond 528c3284c6 Merged PR 1493: updated coords and address fix
updated coords and address fix

Related work items: #14865
2025-03-14 15:20:42 +00:00
robbond 68d03ed93d updated coords and address fix 2025-03-14 15:20:06 +00:00
Robert Bond 9eace1b626 Merged PR 1479: 14430 + 14355 remove fields
14430 + 14355 remove fields

Related work items: #14355, #14430
2025-03-11 12:12:50 +00:00
robbond 34ce7f73a8 14430 + 14355 remove fields 2025-03-11 12:11:54 +00:00
Robert Bond 07f3769d84 Merged PR 1472: SIPS Sprint 2 070325
Related work items: #14656, #14697
2025-03-10 09:25:27 +00:00
robbond f6fdc79ae3 14656 filtering adjust 2025-03-10 09:24:23 +00:00
robbond ebed61f75f 14697 Start and due date for notifications 2025-03-07 11:17:18 +00:00
Robert Bond d9a18f008d Merged PR 1466: pre submssion details on summary
pre submssion details on summary

Related work items: #14283
2025-03-05 15:33:55 +00:00
robbond c5f893f91c pre submssion details on summary 2025-03-05 15:32:54 +00:00
Robert Bond 373a6c0d27 Merged PR 1465: SIPs Sprint 2
Related work items: #14324, #14325, #14326, #14327, #14328, #14329, #14330, #14345
2025-03-05 11:03:43 +00:00
robbond 884c62ee46 banner notification 2025-03-05 11:00:19 +00:00
robbond 73ebd12cd2 Document filtering 2025-03-05 10:39:22 +00:00
Robert Bond 05b034c328 Merged PR 1449: updated labels and added welsh versions
updated labels and added welsh versions

Related work items: #14360
2025-02-26 09:10:06 +00:00
robbond a458a12c6a updated labels and added welsh versions 2025-02-26 09:09:30 +00:00
Robert Bond bbc1e3f0a1 Merged PR 1448: 13925 SIPs translations
13925 SIPs translations

Related work items: #13925
2025-02-25 16:52:32 +00:00
robbond 83fed387f3 13925 SIPs translations 2025-02-25 16:51:43 +00:00
Robert Bond 1a276be54d Merged PR 1442: 14276 Cancelled spelling
Related work items: #12956, #14276
2025-02-24 13:30:24 +00:00
robbond 422b3ce98c 14276 spelling of canceled 2025-02-24 10:13:14 +00:00
robbond 2c7823f911 Merge branch 'Development' into SIPS-Development 2025-02-24 09:48:05 +00:00
Robert Bond bef329453a Merged PR 1436: SIPs Sprint 1 200225
Related work items: #14086, #14244
2025-02-21 09:21:07 +00:00
robbond 8d3d356815 14244 GEL updates and rework 2025-02-21 09:19:31 +00:00
robbond baa1f7c4f3 14086 Amend copy for SIPs 2025-02-21 09:18:14 +00:00
Robert Bond 804f3a6f66 Merged PR 1431: Sprint 1 - 14100 & 14115
Related work items: #14100, #14115
2025-02-20 07:36:53 +00:00
robbond 49baec5814 14115 SIPs events under summary pages on myportal 2025-02-20 07:35:32 +00:00
robbond ae5c0b4e39 14115 SIPs events on summary page 2025-02-19 15:00:25 +00:00
robbond e1444887cb 14100 updated copy 2025-02-19 14:59:09 +00:00
Robert Bond 9d6f9c6c49 Merged PR 1426: Sprint 1 tasks
Related work items: #13923, #13924, #13925, #13927, #13928, #13929, #13930, #13931, #13933
2025-02-18 14:30:46 +00:00
robbond b699f1d359 13933 2025-02-18 14:25:39 +00:00
robbond dec96c0546 13933 ui for infrasturcure card 2025-02-18 14:25:13 +00:00
robbond 7fad19452b 13927 sips summary page 2025-02-18 14:21:20 +00:00
robbond 1a102ab6cd 13924 13925 13923 update collections for sips 2025-02-18 14:20:27 +00:00
robbond 202e1efd51 13929 Update advanced search 2025-02-18 14:18:40 +00:00
robbond 4d6ed2d38d 13930 Update address search for SIPS 2025-02-18 14:17:26 +00:00
robbond f9df42f361 13931 upodate DNS list inc sips 2025-02-18 14:16:09 +00:00
robbond 53515afc49 13928 update select query 2025-02-18 13:23:11 +00:00
474 changed files with 67016 additions and 26004 deletions
+176
View File
@@ -0,0 +1,176 @@
# Contributing with AI (Refactor Branch — PEDW FrontEnd)
## Purpose
This guide defines how engineers and AI agents should work safely and efficiently in this refactor branch.
This branch focuses on **safe, behaviour-preserving refactor of the new appeal flow**.
---
## Start Here (Required Order)
1. Read `.clinerules/refactor-branch-rules.md`
2. Read `GUARDRAILS.md`
3. Read core context:
- `context/refactor-branch-charter.md`
- `context/newappeal-refactor-guardrails.md`
- `context/architecture.md`
- `context/domain-flows.md`
Only read additional files when directly relevant.
Do **not** load all context, templates, or memory-bank files by default.
---
## Core Principle
This is a **refactor branch**, not a feature branch.
- Preserve current live S78 behaviour
- Do not introduce business-rule changes unless explicitly requested
- Prefer small, safe, reversible changes
- Focus on structure, not behaviour
---
## Standard AI-Assisted Workflow
### 1. Scope
- Define requirement
- Confirm non-goals
- Identify impacted flow areas
- Identify risk level
### 2. Plan
- Keep plan small and incremental
- Identify safe boundaries
- Avoid mixing refactor + feature work
### 3. Implement
- Make the smallest viable change
- Prefer extraction over rewrite
- Preserve interfaces and payload shapes
### 4. Validate
Minimum required:
- `npm run lint`
- targeted manual checks
- EN/CY parity checks (if user-facing)
- negative-path checks for sensitive flows
### 5. Document (only when needed)
- update `memory-bank/change-log.md` for non-trivial work
- record decisions or pitfalls if useful
---
## Templates (Use Only When Needed)
Templates are optional tools, not default context.
Use **only one** template if needed:
- feature implementation
- bug fix
- refactor
- debugging
- code review
Do not load all templates.
Do not treat templates as baseline context.
---
## Context Loading Rule (Critical)
Always use the **minimum required context**.
### Default context for this branch:
- `context/refactor-branch-charter.md`
- `context/newappeal-refactor-guardrails.md`
- `context/architecture.md`
- `context/domain-flows.md`
### Only load additional files when:
- working with integrations → `integration-map.md`
- reviewing test gaps → `test-coverage-map.md`
- performing release work → `runbook.md`
- working on specific planned sequence → `context/plans/*`
Never load:
- all context files
- all templates
- onboarding or overview docs
---
## Branching Protocol
This repository currently uses different branch rules depending on the work type.
### For BAU / normal delivery work
- create work branches from `SIPS-Development`
### For new appeal refactor work
- use the `refactor` branch as the base branch
- do refactor implementation either:
- directly on `refactor`, if that is the agreed working model, or
- on a short-lived feature branch created from `refactor`
Do not perform refactor implementation directly on `SIPS-Development`.
If unsure which branch model applies, stop and confirm before making changes.
---
## No-Break Rules (Always Enforce)
- Do not weaken auth/session behaviour
- Do not weaken security headers/CSP
- Keep EN/CY behaviour aligned
- Do not expose secrets or personal data
- Protect save/resume/upload/submit journeys
---
## PR Minimum (AI-Assisted)
Every change must include:
- Scope summary
- Files changed
- Risk notes (auth/data/i18n/a11y)
- Validation evidence
- Rollback plan
---
## Handling Unknowns
If unsure:
1. Choose the safer option
2. Do not change behaviour
3. call out assumptions clearly
4. log open questions if needed
---
## Summary Rule
If in doubt:
> Preserve behaviour, reduce risk, keep changes small.
+95
View File
@@ -0,0 +1,95 @@
# Default Rules (Reference Only)
## Important
This file is **not the active rule set for this refactor branch**.
Active working rules are defined in:
- `.clinerules/refactor-branch-rules.md`
- `context/refactor-branch-charter.md`
- `context/newappeal-refactor-guardrails.md`
Do not load this file by default.
---
## Purpose
This file provides general repository-wide guidance and may be referenced when needed for broader context.
---
## Repository Mission
Deliver safe, accessible, bilingual (EN/CY), and reliable public-service functionality without regressing core journeys.
---
## General Priorities
1. Safety and security of user data
2. Stability of public-facing journeys
3. Accessibility and bilingual parity
4. Maintainability of code
5. Controlled, safe delivery
---
## General Coding Guidance
- Follow existing repo patterns
- Keep page-level logic thin
- Move reusable logic into `components/`, `actions/`, or `lib/`
- Avoid broad refactors in feature or bugfix work
- Prefer small, focused changes
---
## Architecture Guardrails (General)
- Do not bypass auth/session handling
- Do not weaken security controls
- Preserve relay request integrity
- Maintain EN/CY route parity
- Keep Redux hydration/persistence stable
---
## Validation Expectations (General)
- linting must pass
- user-facing changes must be verified
- accessibility and EN/CY checks required
- sensitive flows require negative-path checks
---
## When To Use This File
Only reference this file when:
- broader repository rules are required
- behaviour conflicts are unclear
- no branch-specific guidance exists
---
## Summary
This file is background guidance only.
For this refactor branch:
- follow branch-specific rules first
- prioritise behaviour preservation
- keep context minimal and focused
## Branching Guidance (Reference)
Branching depends on work type:
- BAU work typically branches from `SIPS-Development`
- refactor-stream work for the new appeal flow uses `refactor` as the base branch
Follow branch-specific rules first.
+234
View File
@@ -0,0 +1,234 @@
# Refactor Branch Rules (Active)
## Purpose
This file defines the active working rules for the **refactor branch**.
This branch is focused on **safe, behaviour-preserving refactor of portal journeys**, including:
- New Appeal Flow (completed baseline)
- Representations Flow (active refactor stream)
---
## Core Rule (Highest Priority)
Do not change live behaviour unless explicitly instructed.
Refactor = improve structure, not behaviour.
---
## Primary Objectives
1. Make core portal journeys easier to understand and maintain
2. Reduce risk when making future changes
3. Improve separation of concerns (UI, workflow, data, integrations)
4. Prepare flows for future extensibility (e.g. multiple appeal types, additional journey types)
---
## Active Refactor Streams
### Completed
- New Appeal Flow (S78)
### Active
- Representations Flow
Each stream must follow **slice-based, behaviour-preserving refactor discipline**.
---
## Non-Negotiable Rules
- Preserve all current user journeys:
### Appeals
- start appeal
- save and exit
- resume appeal
- upload documents
- check answers
- submit appeal
- confirmation
### Representations
- start from case summary CTA
- select capacity
- select representation type
- enter content / upload files
- check answers
- submit representation
- completion
---
Do not:
- change payload structures
- change validation rules
- change business logic
- change route/query behaviour
- change navigation or side-effect sequencing
- introduce EN/CY drift
- mix refactor with feature work
---
## Refactor Approach
Always:
1. Understand current behaviour first
2. Identify smallest safe change
3. Prefer extraction over rewrite
4. Keep public interfaces stable
5. Make changes easy to review and revert
---
## Change Size Guidance
- Prefer small PRs (<400 LOC where possible)
- Avoid large multi-concern changes
- Split work into safe slices
- One slice = one branch = one commit
---
## Branch Model (Critical)
### Branch Roles
- `SIPS-Development`
- BAU branch
- ongoing feature and bugfix work
- must remain stable
- `refactor`
- integration branch for refactor work
- all refactor slices merge here first
- feature branches (short-lived)
- created from `refactor`
- one per slice
- merged back into `refactor`
---
### Flow
SIPS-Development
refactor
feature/slice-x
refactor
↓ (when stable & validated)
SIPS-Development
---
## Context Usage Rules (Critical for Efficiency)
### Default context (only load these):
- `context/refactor-branch-charter.md`
- `context/architecture.md`
- `context/domain-flows.md`
### Only load when needed:
- appeal work → `newappeal-refactor-guardrails.md`
- representation work → `representations-refactor-guardrails.md`
- plans → `context/plans/*`
### Never load by default:
- all context files
- onboarding docs
- all templates
---
## Templates Usage
- Use only ONE template when needed
- Do not load all templates
- Do not treat templates as baseline context
---
## Safety Rules
- Do not weaken authentication or session logic
- Do not weaken security headers or middleware
- Do not expose secrets or personal data
- Preserve EN/CY parity
- Maintain accessibility standards
---
## Regression Safety Rule (Critical)
This is a live system. All refactor work must prove behaviour is unchanged.
Before completing any slice:
### Appeals
- start appeal
- save and exit
- resume
- upload
- check answers
- submit
- confirmation
### Representations
- navigate from case summary → make representation
- complete full journey
- submit successfully
- confirmation shown
Also:
- verify EN/CY parity
- verify no navigation or state regressions
If regression cannot be ruled out:
→ do not proceed
---
## When Unsure
1. Choose the safest option
2. Do not change behaviour
3. Call out assumptions
4. Keep the change minimal
---
## Definition of Success
A successful change:
- preserves behaviour
- reduces complexity
- improves clarity
- is small and safe to merge
- introduces no regression risk
---
## One-Line Rule
> Keep behaviour the same, reduce risk, and make the smallest safe change.
+15
View File
@@ -0,0 +1,15 @@
# Bugfix Workflow
1. Clarify the bug symptoms and expected behavior.
2. Inspect the relevant files and execution path.
3. Identify the most likely root cause.
4. Implement the smallest safe fix.
5. Check for:
- side effects
- regression risk
- validation gaps
- missing error handling
6. Summarize:
- root cause
- fix
- preventive improvements worth noting
+17
View File
@@ -0,0 +1,17 @@
# Code Review Workflow
Review the relevant code or changes for:
- correctness
- edge cases
- data integrity
- security concerns
- performance concerns
- maintainability
- readability
- consistency with repo conventions
Return:
1. key findings
2. impact / severity where useful
3. suggested improvements
4. strengths worth preserving
@@ -0,0 +1,18 @@
# Database Migration Workflow
1. Understand the desired schema or data change.
2. Inspect current data model, queries, and dependent code.
3. Identify:
- forward migration steps
- backward compatibility concerns
- rollback considerations
4. Implement or propose the minimal safe migration path.
5. Review for:
- data integrity
- ordering issues
- application compatibility
- deployment timing risk
6. Summarize:
- schema/data changes
- impacted code paths
- rollout / rollback notes
+17
View File
@@ -0,0 +1,17 @@
# Debugging Workflow
1. Capture the symptom, error, or incorrect behavior.
2. Inspect the relevant path and recent changes.
3. Form the most likely hypotheses.
4. Narrow the cause using the smallest practical evidence checks.
5. Propose or implement the minimal safe fix.
6. Review for:
- hidden assumptions
- unhandled states
- logging or observability gaps
- regression risk
7. Summarize:
- symptom
- cause
- fix
- prevention ideas
@@ -0,0 +1,10 @@
# Documentation Update Workflow
1. Identify what changed in the code or workflow.
2. Inspect the affected docs and support files.
3. Update only the relevant sections.
4. Keep the docs concise, factual, and current.
5. Summarize:
- what changed
- which docs were updated
- anything still uncertain
@@ -0,0 +1,16 @@
# Feature Implementation Workflow
1. Understand the requirement and desired outcome.
2. Inspect the relevant files, patterns, and data flows.
3. Propose a short implementation plan for non-trivial work.
4. Implement the smallest maintainable change that satisfies the requirement.
5. Review for:
- correctness
- edge cases
- error handling
- compatibility with existing behavior
- maintainability
6. Summarize:
- what changed
- why
- any follow-up considerations
+13
View File
@@ -0,0 +1,13 @@
# Planning Workflow
1. Clarify the objective.
2. Inspect the relevant code areas and constraints.
3. Identify implementation options.
4. Compare trade-offs.
5. Recommend the most practical path.
6. Return:
- objective summary
- proposed plan
- risks
- dependencies
- suggested execution order
+16
View File
@@ -0,0 +1,16 @@
# PR Review Workflow
Review the pull request or change set for:
- correctness
- scope discipline
- regression risk
- compatibility impact
- tests or validation gaps
- security and performance concerns
- maintainability
Return:
1. summary of change intent
2. key risks
3. requested changes
4. optional improvements
+16
View File
@@ -0,0 +1,16 @@
# Refactor Workflow
1. Understand the current behavior that must be preserved.
2. Inspect the relevant modules and dependencies.
3. Identify safe refactor boundaries.
4. Propose a scoped plan.
5. Refactor incrementally.
6. Review for:
- preserved behavior
- reduced complexity
- improved readability
- regression risk
7. Summarize:
- what was simplified
- what was preserved
- remaining debt
@@ -0,0 +1,15 @@
# Release Readiness Workflow
1. Review the scope of changes.
2. Check:
- build readiness
- environment assumptions
- configuration changes
- database implications
- CI/CD impact
- rollback expectations
3. Identify release risks and missing checks.
4. Summarize:
- release blockers
- risks
- final recommended checks
+125 -27
View File
@@ -19,6 +19,7 @@ RELAY_ROOT = https://ar-ns-lz-pedw-dev-uks-01.servicebus.windows.net/ar-hc-lz-pe
RELAYURI = "ar-ns-lz-pedw-dev-uks-01.servicebus.windows.net"
RELAYPATH = "ar-hc-lz-pedw-dev-uks-01"
CRMURL = "devcrm2016.llcdt.gov.wales/DVPINS"
CRMURL_VERSION = "v8.2"
# //New Test
CLIENT_ID = d40b9373-96c2-4b04-8bd8-c2a2fe6444c1
@@ -26,18 +27,24 @@ CLIENT_SECRET = 6hT8Q~glL~jloWpGmvXw3UY37QFwjUE0r._jobEM
RELAY_ROOT = https://ar-ns-lz-pedw-test-uks-01.servicebus.windows.net/ar-hc-lz-pedw-test-uks-01/
RELAYURI = "ar-ns-lz-pedw-test-uks-01.servicebus.windows.net"
RELAYPATH = "ar-hc-lz-pedw-test-uks-01"
CRMURL = "tscrm2016.llcdt.gov.wales/UATPINS"
# CRMURL = "tscrm2016.llcdt.gov.wales/UATPINS"
# CRMURL_VERSION = "v8.2"
CRMURL = "ts-pedw.crm11.dynamics.com"
CRMURL_VERSION = "v9.2"
# # //New PreProd
# //New PreProd
# CLIENT_ID = ab6c4678-b31a-4eb3-b429-e039120e488a
# CLIENT_SECRET = V288Q~P1lFMvvX9Xnkx2pLixK~GOREH983Owqc1E
# CLIENT_SECRET = VWY8Q~9EtRzGXpd8Xs~5OS1fej2IyVJ8JgTMncw3
# RELAY_ROOT = https://ar-ns-lz-pedw-ppe-uks-01.servicebus.windows.net/ar-hc-lz-pedw-ppe-uks-01/
# RELAYURI = "ar-ns-lz-pedw-ppe-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-ppe-uks-01"
# # RELAY_ROOT = https://ar-ns-lz-pedw-ppe-ukw-01.servicebus.windows.net/ar-hc-lz-pedw-ppe-ukw-01/
# # RELAYURI = "ar-ns-lz-pedw-ppe-ukw-01.servicebus.windows.net"
# # RELAYPATH = "ar-hc-lz-pedw-ppe-ukw-01"
# CRMURL = "ppcrm2016.llcdt.gov.wales/PPPINSWales"
# # CRMURL = "ppcrm2016.llcdt.gov.wales/PPPINSWales"
# # CRMURL_VERSION = "v8.2"
# CRMURL = "wg-pp-pedw.crm11.dynamics.com"
# CRMURL_VERSION = "v9.2"
# //Prod Oauth WGO
@@ -49,28 +56,77 @@ CRMURL = "tscrm2016.llcdt.gov.wales/UATPINS"
# CRMURL = "crm2016.llc.gov.wales/PEDW"
# //New Prod
CLIENT_ID = 3e4141a3-1fbd-454e-98f0-7f3a4f14a3cc
CLIENT_SECRET = tFk8Q~E.R-HFjROTu1sRKqwb_ONzy05uPYoF6awR
RELAY_ROOT = https://ar-ns-lz-pedw-prod-uks-01.servicebus.windows.net/ar-hc-lz-pedw-prod-uks-01/
RELAYURI = "ar-ns-lz-pedw-prod-uks-01.servicebus.windows.net"
RELAYPATH = "ar-hc-lz-pedw-prod-uks-01"
# RELAY_ROOT = https://ar-ns-lz-pedw-ppe-ukw-01.servicebus.windows.net/ar-hc-lz-pedw-prod-ukw-01/
# RELAYURI = "ar-ns-lz-pedw-prod-ukw-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-prod-ukw-01"
CRMURL = "crm2016.llcdt.gov.wales/PEDW"
# CLIENT_ID = 3e4141a3-1fbd-454e-98f0-7f3a4f14a3cc
# CLIENT_SECRET = ULC8Q~fZiXm0.teVLlZ_7LjxBOj2vIsWSApu2bAq
# RELAY_ROOT = https://ar-ns-lz-pedw-prod-uks-01.servicebus.windows.net/ar-hc-lz-pedw-prod-uks-01/
# RELAYURI = "ar-ns-lz-pedw-prod-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-prod-uks-01"
# # RELAY_ROOT = https://ar-ns-lz-pedw-ppe-ukw-01.servicebus.windows.net/ar-hc-lz-pedw-prod-ukw-01/
# # RELAYURI = "ar-ns-lz-pedw-prod-ukw-01.servicebus.windows.net"
# # RELAYPATH = "ar-hc-lz-pedw-prod-ukw-01"
# CRMURL = "crm2016.llc.gov.wales/PEDW"
# CRMURL_VERSION = "v8.2"
# D365 cnr WGO
# CLIENT_ID = 3125ac03-91ac-46a7-a166-0086f0ed90b3
# CLIENT_SECRET = bFq8Q~9VX5Ra3lKnGGIXsB.G6-ZGMx7LqyqXjdr4
# RELAY_ROOT = https://ar-ns-lz-pedw-d365cnr-uks-01.servicebus.windows.net/ar-hc-lz-pedw-d365cnr-uks-01/
# RELAYURI = "ar-ns-lz-pedw-d365cnr-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-d365cnr-uks-01"
# CRMURL = "dv-pedw.crm11.dynamics.com"
# CRMURL_VERSION = "v9.2"
# D365 Dev WGO
# CLIENT_ID = 9b834097-03d7-409b-b038-ecfec31a394a
# CLIENT_SECRET = .Jf8Q~tKMObCJjhh_hy3J5AIctFrBalU4FS~tdxk
# RELAY_ROOT = https://ar-ns-lz-pedw-d365dev-uks-01.servicebus.windows.net/ar-hc-lz-pedw-d365dev-uks-01/
# RELAYURI = "ar-ns-lz-pedw-d365dev-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-d365dev-uks-01"
# CRMURL = "dv-pedw.crm11.dynamics.com"
# CRMURL_VERSION = "v9.2"
# D365 Test WGO
# CLIENT_ID = d40b9373-96c2-4b04-8bd8-c2a2fe6444c1
# CLIENT_SECRET = 6hT8Q~glL~jloWpGmvXw3UY37QFwjUE0r._jobEM
# RELAY_ROOT = https://ar-ns-lz-pedw-d365test-uks-01.servicebus.windows.net/ar-hc-lz-pedw-d365test-uks-01/
# RELAYURI = "ar-ns-lz-pedw-d365test-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-d365test-uks-01"
# CRMURL = "ts-pedw.crm11.dynamics.com"
# CRMURL_VERSION = "v9.2"
# D365 PP WGO
# CLIENT_ID = ab6c4678-b31a-4eb3-b429-e039120e488a
# CLIENT_SECRET = V288Q~P1lFMvvX9Xnkx2pLixK~GOREH983Owqc1E
# RELAY_ROOT = https://ar-ns-lz-pedw-d365ppe-uks-01.servicebus.windows.net/ar-hc-lz-pedw-d365ppe-uks-01/
# RELAYURI = "ar-ns-lz-pedw-d365ppe-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-d365ppe-uks-01"
# CRMURL = "wg-pp-pedw.crm11.dynamics.com"
# CRMURL_VERSION = "v9.2"
# D365 Prod WGO
# CLIENT_ID = 3e4141a3-1fbd-454e-98f0-7f3a4f14a3cc
# CLIENT_SECRET = ULC8Q~fZiXm0.teVLlZ_7LjxBOj2vIsWSApu2bAq
# RELAY_ROOT = https://ar-ns-lz-pedw-d365prod-uks-01.servicebus.windows.net/ar-hc-lz-pedw-d365prod-uks-01/
# RELAYURI = "ar-ns-lz-pedw-d365prod-uks-01.servicebus.windows.net"
# RELAYPATH = "ar-hc-lz-pedw-d365prod-uks-01"
# CRMURL = "wg-prod-pedw.crm11.dynamics.com"
# CRMURL_VERSION = "v9.2"
//GOOGLE_TAG_MANAGER = GTM-T78CBC3
GOOGLE_TAG_MANAGER = G-GTWW3JT03Z
SHOWLOGIN = true
SHOWREPRESENTATIONS = true
SHOWFILEUPLOAD = "false"
SHOWMAPS = false
SHOWMAPS = true
API_ROOT = "http://localhost:3000"
CY_API_ROOT = "http://cymru.local:3000"
I18N_DOMAIN = "cymru.local"
# DOCAPI_OFFLINE = "12:00:00,14:00:00"
DOCAPI_OFFLINE = "02:00:00,04:00:00"
# DOCAPI_OFFLINE = "07:00:00,16:00:00"
DOCAPI_OFFLINE = "01:00:00,04:00:00"
SHOWFILTERDOCS = true
@@ -80,16 +136,14 @@ NEXTAUTH_URL_INTERNAL = $API_ROOT
NEXTAUTH_SECRET = SuperSecret
// CNR sql server
DATABASE_URL = sqlserver://sql-srv-plt-pedw-cnr-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-cnr-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// dev test sql server
# DATABASE_URL = sqlserver://pedw-dev-test-sql-svr.database.windows.net:1433;database=PEDWDev;user=RobBondSQL;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// DATABASE_URL = sqlserver://sql-srv-plt-pedw-dev-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-dev-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// test sql server
DATABASE_URL = sqlserver://sql-srv-plt-pedw-test-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// Pre prod sql server
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-ppe-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustservercertificate=true;hostnameincertificate=*.database.windows.net;logintimeout=30;authentication=SqlPassword
@@ -97,7 +151,24 @@ DATABASE_URL = sqlserver://sql-srv-plt-pedw-cnr-uks-01.database.windows.net:1433
# DATABASE_URL = sqlserver://pp-pedw-sql.database.windows.net:1433;database=PEDWPreProd;user=RobBondSQL;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// Prod sql server
DATABASE_URL = sqlserver://sql-srv-plt-pedw-prod-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-prod-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// D365 CNR sql server
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-d365cnr-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// D365 Dev sql server
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-d365dev-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// D365 Test sql server
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-d365test-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// D365 PPE sql server
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-d365ppe-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
// D365 Prod sql server
# DATABASE_URL = sqlserver://sql-srv-plt-pedw-d365prod-uks-01.database.windows.net:1433;database=PEDW;user=PedwSqlAdmin;password=GhWKr62F5sQ69dZx;encrypt=true;trustServerCertificate=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;authentication=ActiveDirectoryPassword
SECRET = SuperSecret
@@ -120,6 +191,7 @@ AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwdevuks.blob.core.windows.net
// New test key
AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwtestuks"
# AZURE_STORAGE_ACCOUNT_KEY = 5NdDO9GGlbTFLHFOXZRw8H2C0Ow5bFTvHwUHEbStyzg2mbd1uwHSeBSvM/dkN+HfdwrFzqAkwL5P+AStdO6cig==
AZURE_STORAGE_ACCOUNT_KEY = bqJAz5pYQVGee8dnVhlsqW+xHwinNTjgY2AmojNyB+d4uAotMR20WcO4Op13yB+9dFaXAkkjtIA4+AStHT/IYg==
AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwtestuks.blob.core.windows.net
@@ -129,15 +201,40 @@ AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwtestuks.blob.core.windows.net
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwppeuks.blob.core.windows.net
// PROD key
# PROD key
# AZURE_STORAGE_ACCOUNT_NAME = "pedwprod"
# AZURE_STORAGE_ACCOUNT_KEY = koZ46STRqapSa2Dod8MLL6aju1eYLhhyJ6DXpCLMTnFLJvbDT6CHUv/jwr5U/Ta8a60MWzwToHQy+AStADO7sg==
# AZURE_PEDW_STORAGE_ENDPOINT = https://pedwprod.blob.core.windows.net
# // New PROD key
AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwproduks"
AZURE_STORAGE_ACCOUNT_KEY = OfV9MFqXcLLPam9GhEdMWadIOwIzhc5hd5TUqzIuQoswFdtV4ybaemjatSv+Kht0Ny1qQKjLplwY+AStUTZJrg==
AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwproduks.blob.core.windows.net
# New PROD key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwproduks"
# AZURE_STORAGE_ACCOUNT_KEY = OfV9MFqXcLLPam9GhEdMWadIOwIzhc5hd5TUqzIuQoswFdtV4ybaemjatSv+Kht0Ny1qQKjLplwY+AStUTZJrg==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwproduks.blob.core.windows.net
# D365 CNR key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwd365cnruks"
# AZURE_STORAGE_ACCOUNT_KEY = nEwCp+4FuVf/9a4DOyKuQO2yRRV4Ypigg20ch1YaiL09YmEhWMEO5pNLpYtI3WnQCkIqeu1CUqwM+AStCLW01g==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwd365cnruks.blob.core.windows.net
# D365 DEV key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwd365devuks"
# AZURE_STORAGE_ACCOUNT_KEY = XzwQxuZXSwYrVgemDUD2ymTpYTIfWqhypbmEALw1ZEEP0M4D3b3Gh28clSymxwFu7Gu9mhwkW4/V+AStZ6RF8Q==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwd365devuks.blob.core.windows.net
# D365 TEST key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwd365testuks"
# AZURE_STORAGE_ACCOUNT_KEY = 5NdDO9GGlbTFLHFOXZRw8H2C0Ow5bFTvHwUHEbStyzg2mbd1uwHSeBSvM/dkN+HfdwrFzqAkwL5P+AStdO6cig==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwd365testuks.blob.core.windows.net
# D365 PP key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwd365ppeuks"
# AZURE_STORAGE_ACCOUNT_KEY = xQz4FWDnQDE09uPOCj1t7axRiquvZG9vbxL88ywzVsJnTsAgewrHnuAGkxGPBCp2xGE53ffYOme6+AStSr8OoQ==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwd365ppeuks.blob.core.windows.net
# D365 Prod key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwd365produks"
# AZURE_STORAGE_ACCOUNT_KEY = DvKcWhDpFKMFdVXFF2SqDG0sYQ3AkNCmJUBG0cvgzi5NBHS4Ahd2hPAlfA3hGi2EbohUag3vCucK+ASt1IdBhA==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwd365produks.blob.core.windows.net
AZURE_PEDW_CONTAINER = "pedwapplications"
@@ -158,7 +255,7 @@ APPLICATIONINSIGHTS_CONNECTIONSTRING = InstrumentationKey=7a06e24a-793c-47bc-898
// Test Connection string
//NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=cc60e8e7-5b30-4c2b-a176-8b96b21b12df;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=2a4094be-6fd1-46ab-a67d-5f241692b129
NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=cc60e8e7-5b30-4c2b-a176-8b96b21b12df;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=2a4094be-6fd1-46ab-a67d-5f241692b129
// Preprod Connection string
//NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=7370f0f9-834d-4c7a-b3c4-9951fcad5ad2;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=04362cb7-6dfc-469e-8274-12243e8be851
@@ -174,3 +271,4 @@ HIDEERRORLOGS = false
SHOWSIPS = true
ALLOWED_IPS=::1,203.0.113.42,198.51.100.17
UPLOAD_BATCH_COUNT = 5
+8
View File
@@ -48,3 +48,11 @@ gitclean.sh
pages/admin/logcheck.js
pages/admin/logchecker.js
pages/baracuda.html
pages/holding-min.html
pages/holding.html
# Cline / AI-assistant governance artifacts (keep local-only)
ai-prompts/
pages/baracuda.min.html
+2 -1
View File
@@ -1,5 +1,6 @@
{
"tabWidth": 4,
"useTabs": false,
"quoteProps":"preserve"
"quoteProps": "preserve",
"trailingComma": "none"
}
+1 -1
View File
@@ -11,7 +11,7 @@
"command": "npm run dev"
},
{"name": "Launch Chrome against localhost",
"type": "pwa-chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
+46
View File
@@ -0,0 +1,46 @@
# AI Context — PEDW FrontEnd
## Overview
PEDW FrontEnd is a Next.js 14 (Pages Router) bilingual (EN/CY) public-service portal for planning case search, case detail/document viewing, and authenticated portal workflows (appeals, representations, watchlists, dashboard views).
## Architecture
- Monorepo-style Next.js app with UI routes in `pages/` and API handlers in `pages/api/**`.
- Redux (`next-redux-wrapper` + `redux-persist`) for shared client/server state.
- `next-auth` for auth/session with Prisma adapter.
- Integration-heavy BFF pattern: API routes proxy to relay/CRM and storage/notify services.
## Key components
- `components/breadcrumbs.js`: central breadcrumb/back-link behavior.
- `components/case/summary.js`: case-detail tabs, representation eligibility logic, watch/email actions.
- `actions/index.js`: shared API utilities, token/header/hash helpers, many side-effect wrappers.
- `pages/api/endpoint/**`: relay-backed business-data APIs.
- `pages/api/file/**`: blob/document/PDF/upload endpoints.
## Data model boundaries
- Prisma (`prisma/schema.prisma`) stores auth/session entities only (`User`, `Account`, `Session`, `VerificationToken`) on SQL Server.
- Business/case data is external (Dynamics/relay path), not persisted as Prisma domain models here.
## API/integration patterns
- Many endpoint handlers compute HMAC hash query params (`hash`) for relay-bound requests.
- Token acquisition + OData headers are reused across proxy handlers.
- File endpoints often validate request hash for sensitive blob actions.
- GOV.UK Notify used for email, including language-specific template selection in some flows.
## Infrastructure/tooling
- Scripts: `npm run dev`, `npm run build`, `npm start`, `npm run lint`.
- CI/CD artifacts present: `azure-pipelines.yml`, `Jenkinsfile`, `Dockerfile` (active production source-of-truth not explicit in repo).
- Security controls: `middleware.js` CSP/runtime headers + `next.config.js` security headers.
## Development constraints
- Preserve auth/session behavior in `pages/api/auth/[...nextauth].js`.
- Do not weaken CSP/security headers in `middleware.js` or `next.config.js`.
- Keep EN/CY parity across routes and locale resources (`i18n.js`, `locales/**`, rewrites).
- Treat relay hash behavior as security-sensitive; keep path/hash compatibility stable.
- Avoid logging secrets/personal data in auth/email/file/account flows.
+25
View File
@@ -0,0 +1,25 @@
# Use official Node.js 20 LTS image
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Install dependencies first (better caching)
COPY package*.json ./
RUN npm ci --production
# Copy source files (including .next build output if pre-built)
COPY . .
# If you want to build inside container, uncomment:
# RUN npm run build
# Use non-root user (optional, but recommended)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Expose port your app listens on
EXPOSE 3000
# Start your Next.js app (adjust if using custom server)
CMD ["npm", "start"]
+83
View File
@@ -0,0 +1,83 @@
# PEDW FrontEnd Guardrails
## Purpose
This document is a fast pre-flight checklist to reduce regressions in high-risk areas. Use it before coding, before opening a PR, and before merge.
## Non-Negotiable Guardrails
1. **Auth/session integrity**
- Do not bypass `next-auth` flow in `pages/api/auth/[...nextauth].js`.
- Preserve secure redirect behavior and cookie/session settings.
2. **Security headers and CSP**
- Do not weaken `middleware.js` CSP/header behavior or `next.config.js` security headers without explicit rationale.
3. **Data model safety**
- Treat `prisma/schema.prisma` as source-of-truth for auth/account persistence.
4. **Bilingual parity (EN/CY)**
- Any user-facing route/content change must validate both locales.
- Keep `i18n.js`, `locales/`, and `next.config.js` rewrites aligned.
5. **Public-service reliability**
- Avoid breaking core flows: search, case, account, myportal, admin.
## Sensitive Flow Protection
Apply extra checks for:
- `pages/api/auth/**`
- `pages/api/file/**`
- `pages/api/email/**`
- `pages/api/endpoint/*_api.js` handling account/user data
## Relay Hash Integrity Rules (CRM-bound APIs)
Apply when changing relay-bound portal endpoints and helpers (notably `pages/api/endpoint/**` and `actions/index.js`):
1. Keep relay endpoint configuration sourced from `API_ROOT`.
2. Preserve path-hash generation behavior:
- hash input must be request path/query (excluding domain),
- appended hash parameter must remain compatible with relay expectations.
3. Do not change hash algorithm/key usage contract without coordinated relay change.
4. Treat hash validation failures as security-relevant negative paths; verify graceful rejection handling.
5. Never log hash key material or sensitive request payloads.
Required for sensitive changes:
- Negative-path validation (unauthorized, invalid input, malformed payload).
- Safe logging (no secrets/tokens/personal data in cleartext).
- Explicit rollback steps.
## Relay Policy Change Guardrail (timeouts/retries/logging)
When changing shared relay forwarding policy (for example in `pages/api/middleware/relayForwarding.js`), treat this as an operationally sensitive change even if endpoint contracts are unchanged.
Minimum required before merge:
1. Complete the relay governance gate in `context/runbook.md` (Relay Hardening Rollout Playbook).
2. Attach non-prod smoke evidence for:
- deterministic non-retry classes (`400`, `401`, `403`, `404`)
- transient retry classes (`429`, `503`, timeout/network-transient)
3. Confirm structured redacted logging and duplicate-log suppression behavior.
4. Provide fast mitigation + rollback path (`RELAY_RETRY_MAX=0` and commit-revert path).
## Pre-PR Quick Checklist
- [ ] Ran `npm run lint` (or documented why unavailable)
- [ ] Verified changed routes/APIs manually
- [ ] Verified EN + CY behavior for impacted user-facing flow
- [ ] Performed accessibility smoke checks (keyboard, focus, labels, headings)
- [ ] Added risk notes (auth/data/i18n/a11y)
- [ ] Updated `memory-bank/change-log.md` for non-trivial changes
## If Assumptions Are Unclear
1. Record assumptions in PR notes.
2. Add unresolved items to `memory-bank/open-questions.md`.
3. Choose the safer behavior and clearly mark as temporary.
## Related Docs
- `.clinerules`
- `CONTRIBUTING_AI.md`
- `context/runbook.md`
- `context/integration-map.md`
- `memory-bank/README.md`
Vendored
+50
View File
@@ -0,0 +1,50 @@
pipeline {
agent {
docker {
image 'node:20' // ✅ Node.js 20 with npm included
}
}
environment {
IMAGE = "pedw-app"
}
stages {
stage('Install & Build') {
steps {
sh 'npm ci'
sh 'npm run build'
}
}
stage('Build Docker Image') {
steps {
sh "docker build -t $REGISTRY/$IMAGE:latest ."
}
}
stage('Push Docker Image') {
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub-credentials', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh "echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin"
sh "docker push $REGISTRY/$IMAGE:latest"
}
}
}
stage('Deploy to Plesk') {
steps {
sshagent(['plesk-ssh-key']) {
sh """
ssh root@your-plesk-domain.com '
docker pull $REGISTRY/$IMAGE:latest &&
docker stop nextjs-app || true &&
docker rm nextjs-app || true &&
docker run -d --restart=always --name nextjs-app -p 3000:3000 $REGISTRY/$IMAGE:latest
'
"""
}
}
}
}
}
+242 -183
View File
@@ -11,7 +11,7 @@ const {
ContainerSASPermissions,
generateBlobSASQueryParameters,
SASProtocol,
SASProtocol
} = require("@azure/storage-blob");
const {
QueueServiceClient,
@@ -23,7 +23,7 @@ const {
generateAccountSASQueryParameters,
generateQueueSASQueryParameters,
StorageSharedKeyCredential,
QueueClient,
QueueClient
} = require("@azure/storage-queue");
const STORAGE_PATH = process.env.AZURE_PEDW_STORAGE_ENDPOINT;
const STORAGE_CONTAINER = process.env.AZURE_PEDW_CONTAINER;
@@ -31,6 +31,88 @@ const QUEUE_PATH = process.env.AZURE_PEDW_QUEUE_ENDPOINT;
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
const buildDownloadBlobQueryPath = ({
containerName,
casefolderID,
blobname,
encodeCasefolderID = true,
encodeBlobname = true
}) => {
const casefolderPart = encodeCasefolderID
? encodeURIComponent(casefolderID)
: casefolderID;
const blobPart = encodeBlobname ? encodeURIComponent(blobname) : blobname;
return (
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
casefolderPart +
"&blobname=" +
blobPart
);
};
const buildDeleteBlobQueryPath = ({
containerName,
casefolderID,
blobname,
encodeCasefolderID = true,
encodeBlobname = true
}) => {
const casefolderPart = encodeCasefolderID
? encodeURIComponent(casefolderID)
: casefolderID;
const blobPart = encodeBlobname ? encodeURIComponent(blobname) : blobname;
return (
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
casefolderPart +
"&blobname=" +
blobPart
);
};
const buildGetBlobListQueryPath = ({ containerName, casefolderID }) => {
return (
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID
);
};
const buildCaseObjectPath = (casefolderID) => {
return casefolderID + "/" + casefolderID + "_case.json";
};
const buildHashMetadataPaths = ({ containerName, casefolderID, blobname }) => {
return {
"hashedfilepath": hashAPIPath(
buildDownloadBlobQueryPath({
containerName,
casefolderID,
blobname
})
),
"hasheddeletepath": hashAPIPath(
buildDeleteBlobQueryPath({
containerName,
casefolderID,
blobname
})
),
"hashgetblobs": hashAPIPath(
buildGetBlobListQueryPath({
containerName,
casefolderID
})
)
};
};
export const createContainerSas = async (containerName) => {
// Get environment variables
@@ -67,7 +149,7 @@ export const createContainerSas = async (containerName) => {
permissions: ContainerSASPermissions.parse(containerPermissions),
protocol: SASProtocol.HttpsAndHttp,
startsOn: TEN_MINUTES_BEFORE_NOW,
expiresOn: TEN_MINUTES_AFTER_NOW,
expiresOn: TEN_MINUTES_AFTER_NOW
};
// console.log(
@@ -126,7 +208,7 @@ export const createBlobSas = async (containerName, blobName) => {
permissions: BlobSASPermissions.parse(blobPermissionsForAnonymousUser),
protocol: SASProtocol.HttpsAndHttp,
startsOn: TEN_MINUTES_BEFORE_NOW,
expiresOn: TEN_MINUTES_AFTER_NOW,
expiresOn: TEN_MINUTES_AFTER_NOW
};
const sasToken = generateBlobSASQueryParameters(
@@ -187,61 +269,38 @@ export const getBlobs = async (containerName, casefolderID) => {
const blobObj = [];
for await (const blob of containerClient.listBlobsFlat({
prefix: casefolderID + "/files/",
prefix: casefolderID + "/files/"
})) {
let blobDocumentType = blob.name
.split("/")[2]
.slice(0, blob.name.split("/")[2].indexOf("_"));
const blobPathParts = blob.name.split("/");
const fileName = blobPathParts[2];
const contentLength = blob.properties.contentLength;
blobObj.push({
"name": blob.name.split("/")[2],
"name": fileName,
"path": blob.name,
"documentType": getDocumentTypeFromFilename(
blob.name.split("/")[2]
),
"documentType": getDocumentTypeFromFilename(fileName),
"versionId": blob.versionId,
"caseObj": casefolderID + "/" + casefolderID + "_case.json",
"caseObj": buildCaseObjectPath(casefolderID),
"isCurrentVersion": blob.isCurrentVersion,
"contentLength": blob.properties.contentLength,
"size": blob.properties.contentLength,
"contentLength": contentLength,
"size": contentLength,
"contentType": blob.contentType,
"lastModified": blob.properties.lastModified,
"filepath":
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name),
"hashedfilepath": hashAPIPath(
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[2])
),
"deletepath":
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[2]),
"hasheddeletepath": hashAPIPath(
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[2])
),
"hashgetblobs": hashAPIPath(
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID
),
"filepath": buildDownloadBlobQueryPath({
containerName,
casefolderID,
blobname: blob.name
}),
...buildHashMetadataPaths({
containerName,
casefolderID,
blobname: fileName
}),
"deletepath": buildDeleteBlobQueryPath({
containerName,
casefolderID,
blobname: fileName
})
});
}
//console.log("blobObj:", blobObj);
@@ -277,7 +336,7 @@ export const createBlob = async (formContent, containerName, caseref) => {
const tags = {
containerid: containerName,
caseID: caseID,
blobType: "Appeal",
blobType: "Appeal"
};
//console.log("the tags:", tags);
@@ -309,8 +368,8 @@ export const createRepBlob = async (formContent, containerName, caseref) => {
const tags = {
containerid: containerName,
caseID: formContent.ticketnumber,
blobType: "Representation",
caseID: formContent.ticketnumber || formContent.caseRef,
blobType: "Representation"
};
//console.log("the tags:", tags);
@@ -356,12 +415,14 @@ export const createAppealPDFBlob = async (
const uploadOptions = {
blockSize: 4 * 1024 * 1024, // 4 MiB max block size
concurrency: 2, // maximum number of parallel transfer workers
maxSingleShotSize: 8 * 1024 * 1024, // 8 MiB initial transfer size
maxSingleShotSize: 8 * 1024 * 1024 // 8 MiB initial transfer size
};
console.log("blobName:", blobName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.uploadStream(content);
const uploadBlobResponse = Buffer.isBuffer(content)
? await blockBlobClient.uploadData(content)
: await blockBlobClient.uploadStream(content);
console.log(uploadBlobResponse);
let whichLocation = "846040000"; // Default value
@@ -371,7 +432,7 @@ export const createAppealPDFBlob = async (
caseID: caseID,
blobType: "Appeal PDF",
ishareLocation: whichLocation,
documentLocationId: whichLocation,
documentLocationId: whichLocation
};
//console.log("the tags:", tags);
@@ -405,13 +466,15 @@ export const createRepPDFBlob = async (
const uploadOptions = {
blockSize: 4 * 1024 * 1024, // 4 MiB max block size
concurrency: 2, // maximum number of parallel transfer workers
maxSingleShotSize: 8 * 1024 * 1024, // 8 MiB initial transfer size
maxSingleShotSize: 8 * 1024 * 1024 // 8 MiB initial transfer size
};
console.log("blobName:", blobName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.uploadStream(content);
const uploadBlobResponse = Buffer.isBuffer(content)
? await blockBlobClient.uploadData(content)
: await blockBlobClient.uploadStream(content);
let whichLocation =
repName.indexOf("_IP_") > 0
@@ -423,7 +486,9 @@ export const createRepPDFBlob = async (
: repName.indexOf("_Comments") > 0
? "846040003"
: repName.indexOf("_Impact") > 0
? "846040001"
? "846040029"
: repName.indexOf("_Consultation_Response_") > 0
? "846040036"
: "846040002";
const tags = {
@@ -431,7 +496,7 @@ export const createRepPDFBlob = async (
caseID: caseID,
blobType: "Representation PDF",
ishareLocation: whichLocation,
documentLocationId: whichLocation,
documentLocationId: whichLocation
};
//console.log("the tags:", tags);
@@ -445,7 +510,7 @@ export const deleteBlob = async (containerName, blobName) => {
const creds = new DefaultAzureCredential();
const options = {
deleteSnapshots: "include", // or 'only'
deleteSnapshots: "include" // or 'only'
};
const containerToken = await createContainerSas(containerName);
@@ -465,7 +530,7 @@ export const deleteBlobCase = async (containerName, blobName) => {
const creds = new DefaultAzureCredential();
const options = {
deleteSnapshots: "include", // or 'only'
deleteSnapshots: "include" // or 'only'
};
const containerToken = await createContainerSas(containerName);
@@ -477,7 +542,7 @@ export const deleteBlobCase = async (containerName, blobName) => {
console.log("blob to delete:", blobName);
for await (const blob of containerClient.listBlobsFlat({
prefix: blobName,
prefix: blobName
})) {
console.log(" ------ :", blob.name);
containerClient.deleteBlob(blob.name);
@@ -495,7 +560,7 @@ export const deleteBlobRep = async (containerName, blobName) => {
const creds = new DefaultAzureCredential();
const options = {
deleteSnapshots: "include", // or 'only'
deleteSnapshots: "include" // or 'only'
};
const containerToken = await createContainerSas(containerName);
@@ -505,7 +570,7 @@ export const deleteBlobRep = async (containerName, blobName) => {
console.log("blob to delete:", blobName);
for await (const blob of containerClient.listBlobsFlat({
prefix: blobName,
prefix: blobName
})) {
console.log("------ :", blob.name);
const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
@@ -603,13 +668,20 @@ export const uploadFile = async (formContent, containerName, foldername) => {
? "846040005"
: files[prop][0].fieldName.indexOf("_Statement_") > 0
? "846040002"
: files[prop][0].fieldName.indexOf("_Questionnaire_") >
: files[prop][0].fieldName.indexOf(
"_Questionnaire_"
) > 0
? "846040001"
: files[prop][0].fieldName.indexOf("_Comments_") >
0
? "846040003"
: files[prop][0].fieldName.indexOf("_Impact_") >
0
? "846040001"
: files[prop][0].fieldName.indexOf("_Comments_") > 0
? "846040003"
: files[prop][0].fieldName.indexOf("_Impact_") > 0
? "846040001"
: files[prop][0].fieldName.indexOf(
"_Consultation_Response_"
) > 0
? "846040036"
: "846040000";
const tags = {
@@ -618,7 +690,7 @@ export const uploadFile = async (formContent, containerName, foldername) => {
documentType: files[prop][0].fieldName.split(".")[1],
blobType: "RepresentationFile",
ishareLocation: whichLocation,
documentLocationId: whichLocation,
documentLocationId: whichLocation
};
const withTags = blockBlobClient.setTags(tags);
const withMeta = blockBlobClient.setMetadata(tags);
@@ -729,13 +801,14 @@ export const uploadSingleFile = async (
{
pattern:
/_Statement_of_Case|_-_Statement_of_Case|_-_Application_Form|_-_Site_Ownership_Certificate|_-_Decision_Notice|_-_Site_Location_Plan|_-_Plans_Drawing_Documents|_-_Additional_Plans_Drawings_Documents|_-_Design_and_Access_Statement|_-_NSB_LPA_Additional_Documents|_-_LPA_Correspondence|_-_LPA_Original_Permission|_-_LPA's_Registration_Letter|_-_Environmental_Statement|_-_Cost_of_Application|_-_Other_Relevant_Material|_-_S106_Agreement_or_Unilateral_Undertaking/,
value: "846040000",
value: "846040000"
},
{ pattern: /_IP_/, value: "846040005" },
{ pattern: /_Statement_/, value: "846040002" },
{ pattern: /_Questionnaire_/, value: "846040001" },
{ pattern: /_Comments_/, value: "846040003" },
{ pattern: /_Impact_/, value: "846040001" },
{ pattern: /_Consultation_Response_/, value: "846040036" }
];
// Find a matching pattern or fallback to default
@@ -756,7 +829,7 @@ export const uploadSingleFile = async (
documentType: files[prop][0].fieldName.split(".")[1],
blobType: "RepresentationFile",
ishareLocation: whichLocation,
documentLocationId: whichLocation,
documentLocationId: whichLocation
};
const withTags = await blockBlobClient.setTags(tags);
const withMeta = await blockBlobClient.setMetadata(tags);
@@ -793,7 +866,7 @@ export const uploadRepFiles = async (
const tags = {
containerid: containerName,
caseID: formContent.split("tmp/")[0],
documentType: "Rep docs",
documentType: "Rep docs"
};
console.log(tags);
const withTags = await blockBlobClient.setTags(tags);
@@ -838,7 +911,7 @@ export const uploadPDFRepFiles = async (
containerid: containerName,
caseID: foldername.split("/")[0],
documentType: repType,
blobType: "RepresentationPDF",
blobType: "RepresentationPDF"
};
//console.log("the tags:", tags);
@@ -891,7 +964,7 @@ export const uploadPDFAppealFiles = async (
const tags = {
containerid: containerName,
caseID: foldername.split("/")[0],
blobType: "AppealPDF",
blobType: "AppealPDF"
};
//console.log("the tags:", tags);
@@ -930,7 +1003,7 @@ export const uploadPDFCaseFiles = async (
const tags = {
containerid: containerName,
caseID: foldername.split("/")[0],
blobType: "AppealPDF",
blobType: "AppealPDF"
};
//console.log("the tags:", tags);
@@ -1136,7 +1209,7 @@ export const downloadAllRepsFiles = async (containerName, repsBlobObj) => {
return {
"@odata.count": blobCount,
"value": downloaded,
"value": downloaded
};
};
@@ -1175,7 +1248,7 @@ export const getCaseBlob = async (
const tags = {
containerid: containerName,
caseID: caseReference,
blobType: "Case",
blobType: "Case"
};
//console.log("the tags:", tags);
@@ -1206,7 +1279,7 @@ export const getProgressBlobs = async (containerName, caseReference) => {
let blobCount = 0;
for await (const blob of containerClient.listBlobsFlat({
prefix: caseReference,
prefix: caseReference
})) {
blobCount++;
}
@@ -1219,47 +1292,34 @@ export const getProgressBlobs = async (containerName, caseReference) => {
let blobObj = [];
for await (const blob of containerClient.listBlobsFlat({
prefix: caseReference + "/" + caseReference + "_appeal.json",
prefix: caseReference + "/" + caseReference + "_appeal.json"
})) {
console.log("getProgressBlobs in here", blob.name.split("/"));
const blobPathParts = blob.name.split("/");
const appealBlobName = blobPathParts[1];
const contentLength = blob.properties.contentLength;
//consoleLogger("getProgressBlobs in here", blobPathParts);
blobObj.push({
"name": blob.name.split("/")[1],
"name": appealBlobName,
"path": blob.name,
"versionId": blob.versionId,
"caseObj": caseReference + "/" + caseReference + "_case.json",
"caseObj": buildCaseObjectPath(caseReference),
"isCurrentVersion": blob.isCurrentVersion,
"contentLength": blob.properties.contentLength,
"contentLength": contentLength,
"contentType": blob.contentType,
"lastModified": blob.properties.lastModified,
"hashedfilepath": hashAPIPath(
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(caseReference) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[1])
),
"hasheddeletepath": hashAPIPath(
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(caseReference) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[1])
),
"hashgetblobs": hashAPIPath(
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
caseReference
),
...buildHashMetadataPaths({
containerName,
casefolderID: caseReference,
blobname: appealBlobName
})
});
}
blobObj = _.sortBy(blobObj, [
function (o) {
return o.lastModified;
},
}
]).reverse()[0];
return blobObj;
@@ -1278,8 +1338,24 @@ export const getAllProgressBlobs = async (containerName) => {
for await (const blob of containerClient.findBlobsByTags(
"blobType='Appeal'"
)) {
blob.name.split("/")[1].indexOf("_appeal.json") > 0 &&
blob.name.split("/")[1].indexOf("undefined") < 0 &&
const namePart = blob.name.split("/")[1] ?? "";
// keep your existing name filters
if (
namePart.indexOf("_appeal.json") <= 0 ||
namePart.indexOf("undefined") >= 0
)
continue;
// existence check (filters out soft-deleted / stale index hits)
const blobClient = containerClient.getBlobClient(blob.name);
try {
await blobClient.getProperties();
} catch (e) {
// errors usually expose statusCode
if (e?.statusCode === 404) continue;
throw e;
}
blobObj.push({
"name": blob.name.split("/")[1],
"path": blob.name,
@@ -1288,7 +1364,7 @@ export const getAllProgressBlobs = async (containerName) => {
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[0] +
"_case.json",
"_case.json"
// "isCurrentVersion": blob.isCurrentVersion,
// "contentLength": blob.properties.contentLength,
// "contentType": blob.contentType,
@@ -1345,7 +1421,7 @@ export const getRepsBlobs = async (containerName) => {
includeMetadata: true,
includeSnapshots: false,
includeTags: true,
includeVersions: true,
includeVersions: true
};
let blobObj = [];
@@ -1354,14 +1430,27 @@ export const getRepsBlobs = async (containerName) => {
listOptions
)) {
const blobClient = containerClient.getBlobClient(blob.name);
//console.log("getreps blob:", blob);
blob.name.split("/")[2].indexOf("_rep.json") > 0 &&
blob.name.split("/")[2].indexOf("undefined") < 0 &&
// Filter out soft-deleted/stale tag entries and malformed names.
const namePart = blob.name.split("/")[2] ?? "";
if (
namePart.indexOf("_rep.json") <= 0 ||
namePart.indexOf("undefined") >= 0
)
continue;
try {
const properties = await blobClient.getProperties();
blobObj.push({
"name": blob.name.split("/")[2],
"name": namePart,
"path": blob.name,
"size": blobClient.getProperties().contentLength,
"size": properties.contentLength
});
} catch (error) {
if (error?.statusCode === 404) continue;
throw error;
}
}
//console.log("blobObjwwwww:", blobObj);
@@ -1384,73 +1473,43 @@ export const getRepsFilesBlobs = async (
const blobObj = [];
for await (const blob of containerClient.listBlobsFlat({
prefix: casefolderID + "/" + filenamePrefix + "/files/",
prefix: casefolderID + "/" + filenamePrefix + "/files/"
})) {
let blobDocumentType = blob.name
.split("/")[2]
.slice(0, blob.name.split("/")[2].indexOf("_"));
const blobPathParts = blob.name.split("/");
const casefolderPath = blobPathParts[0] + "/" + blobPathParts[1];
const repFileName = blobPathParts[3];
const contentLength = blob.properties.contentLength;
console.log(blob.name);
//consoleLogger(blob.name);
blobObj.push({
"name": blob.name.split("/")[3],
"name": repFileName,
"path": blob.name,
"documentType": getDocumentTypeFromFilename(
blob.name.split("/")[3]
),
"documentType": getDocumentTypeFromFilename(repFileName),
"versionId": blob.versionId,
"isCurrentVersion": blob.isCurrentVersion,
"contentLength": blob.properties.contentLength,
"contentLength": contentLength,
"filenameprefix": filenamePrefix,
"filepath":
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[3]),
"hashedfilepath": hashAPIPath(
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[3])
),
"deletepath":
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[1] +
"&blobname=" +
blob.name.split("/")[3],
"hasheddeletepath": hashAPIPath(
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[3])
),
"hashgetblobs": hashAPIPath(
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[1]
),
"filepath": buildDownloadBlobQueryPath({
containerName,
casefolderID: casefolderPath,
blobname: repFileName
}),
...buildHashMetadataPaths({
containerName,
casefolderID: casefolderPath,
blobname: repFileName
}),
"deletepath": buildDeleteBlobQueryPath({
containerName,
casefolderID: casefolderPath,
blobname: repFileName,
encodeCasefolderID: false,
encodeBlobname: false
})
});
}
console.log("blobObj:", blobObj);
//consoleLogger("blobObj:", blobObj);
return blobObj;
};
@@ -1490,7 +1549,7 @@ export const createQueueSas = async (queueName) => {
var sas_signature_val = {
expiresOn: TEN_MINUTES_AFTER_NOW,
permissions: permission,
queueName: queueName,
queueName: queueName
};
const sasToken = generateQueueSASQueryParameters(
@@ -1534,7 +1593,7 @@ export const createCaseCompleteMessage = async (
caseReference +
"_case.json",
filespath:
endpointURL + "/" + containerName + "/" + caseReference + "/files",
endpointURL + "/" + containerName + "/" + caseReference + "/files"
// uploadUrl: fileRecord.uploadUrl,
// filename: fileRecord.file.name,
@@ -1595,7 +1654,7 @@ export const createRepCompleteMessage = async (
caseReference +
"/" +
filename +
"/files",
"/files"
};
console.log("======================", message, "=====================");
@@ -1621,7 +1680,7 @@ export const listContainers = async (
includeDeleted: false,
includeMetadata: true,
includeSystem: true,
prefix: containerNamePrefix,
prefix: containerNamePrefix
};
console.log(
"\n\n\n=================================================\n",
@@ -1664,7 +1723,7 @@ export const listBlobHierarchical = async (
includeTags: true, // include indexable tags
includeUncommitedBlobs: false, // include uncommitted blobs
includeVersions: false, // include all blob version
prefix: "", // filter by blob name prefix
prefix: "" // filter by blob name prefix
};
let i = 1;
@@ -1750,7 +1809,7 @@ export async function listBlobHierarchicalForUser(containerClient) {
? lastModifiedDate.toISOString()
: null,
createdOn: createdOnDate ? createdOnDate.toISOString() : null,
displayDate,
displayDate
});
}
return blobNames;
@@ -1765,7 +1824,7 @@ export async function listContainersForUser(
includeDeleted: false,
includeMetadata: true,
includeSystem: true,
prefix: containerNamePrefix,
prefix: containerNamePrefix
};
const results = [];
@@ -1783,7 +1842,7 @@ export async function listContainersForUser(
if (blobNames && blobNames.length > 0) {
results.push({
container: containerItem.name,
blobs: blobNames,
blobs: blobNames
});
}
}
+18
View File
@@ -0,0 +1,18 @@
# Actions Clients
This folder contains extracted client wrappers from `actions/index.js` as part of the Priority 1 refactor plan.
Current extracted clients:
- `relayClient` (shared hash-signing helper used by account/portal/document direct services)
- `endpointClient` (shared JSON request helpers for GET and generic axios config requests)
Current usage includes:
- relay hash-signing helper reuse across account/portal/document direct services
- shared endpoint JSON request helpers reused by admin/notify/integration/reference-data direct services
Notes:
- Core helper extraction and compatibility barrel support remain in place.
- Additional client extraction (`fileClient`, `notifyClient`) can be layered in incrementally without changing public exports from `actions/index.js`.
+11
View File
@@ -0,0 +1,11 @@
import axios from "axios";
export const getJson = async (url, config) => {
const response = await axios.get(url, config);
return response.data;
};
export const requestJson = async (config) => {
const response = await axios(config);
return response.data;
};
+22
View File
@@ -0,0 +1,22 @@
import { getJson, requestJson } from "./endpointClient";
import { getSignedJson, postSignedJson } from "./signedRequestClient";
export const getFileJson = (url) => {
return getJson(url);
};
export const getSignedFileJson = async (queryUrl) => {
return getSignedJson(queryUrl);
};
export const downloadFileBlob = (url) => {
return requestJson({
method: "get",
url,
responseType: "blob"
});
};
export const postSignedFileJson = async (queryUrl, data, config = {}) => {
return postSignedJson(queryUrl, data, config);
};
+37
View File
@@ -0,0 +1,37 @@
const encodeRouteParam = (value) => encodeURIComponent(String(value));
export const buildFileQuery = (path, params = {}, options = {}) => {
const { encode = false } = options;
const entries = Object.entries(params).filter(([, value]) => {
return value !== undefined && value !== null;
});
if (entries.length === 0) {
return path;
}
const query = entries
.map(([key, value]) => {
if (!encode) {
return `${key}=${String(value)}`;
}
return `${encodeRouteParam(key)}=${encodeRouteParam(value)}`;
})
.join("&");
return `${path}?${query}`;
};
export const withBaseUrl = (baseUrl, route) => {
return `${baseUrl}${route}`;
};
export const appendQuerySuffix = (route, suffix = "") => {
return `${route}${suffix}`;
};
export const appendHashSuffix = (route, hashBuilder) => {
return appendQuerySuffix(route, hashBuilder(route));
};
+5
View File
@@ -0,0 +1,5 @@
export * from "./relayClient";
export * from "./endpointClient";
export * from "./fileClient";
export * from "./fileRouteBuilder";
export * from "./signedRequestClient";
+22
View File
@@ -0,0 +1,22 @@
import axios from "axios";
import { hashAPIPath } from "../core/hash";
export const buildHashedQueryUrl = async (queryUrl) => {
try {
const signRes = await axios.get(
"/api/endpoint/gethash_api?path=" + encodeURIComponent(queryUrl)
);
if (!signRes?.data?.hash) {
throw new Error("Hash signature unavailable");
}
return queryUrl + signRes.data.hash;
} catch (error) {
if (typeof window === "undefined" && process.env.HASHKEY) {
return queryUrl + hashAPIPath(queryUrl);
}
throw error;
}
};
+45
View File
@@ -0,0 +1,45 @@
import { requestJson } from "./endpointClient";
import { buildHashedQueryUrl } from "./relayClient";
export const buildSignedUrl = async (queryUrl, config = {}) => {
const { baseUrl = "" } = config;
return `${baseUrl}${await buildHashedQueryUrl(queryUrl)}`;
};
const signedRequestJson = async ({ method, queryUrl, data, config = {} }) => {
const { baseUrl, ...requestConfig } = config;
const signedUrl = await buildSignedUrl(queryUrl, { baseUrl });
return requestJson({
method,
url: signedUrl,
data,
...requestConfig
});
};
export const getSignedJson = (queryUrl, config = {}) => {
return signedRequestJson({
method: "get",
queryUrl,
config
});
};
export const postSignedJson = (queryUrl, data, config = {}) => {
return signedRequestJson({
method: "post",
queryUrl,
data,
config
});
};
export const deleteSignedJson = (queryUrl, config = {}) => {
return signedRequestJson({
method: "delete",
queryUrl,
config
});
};
+15
View File
@@ -0,0 +1,15 @@
const port = parseInt(process.env.PORT, 10) || 3000;
export const BASE_URL =
typeof window !== "undefined"
? window.apiRoot
: process.env.API_ROOT || `http://localhost:${port}`;
export const API_PATH = "/api/data/v8.2/";
export const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export const ACCESS_TOKEN_ENDPOINT = process.env.ACCESS_TOKEN_ENDPOINT;
export const TENANT_ID = process.env.TENANT;
+11
View File
@@ -0,0 +1,11 @@
export const isNonEmptyString = (value) => {
return typeof value === "string" && value.trim().length > 0;
};
export const sanitizeString = (value) => {
return typeof value === "string" ? value.trim() : "";
};
export const escapeODataString = (value) => {
return sanitizeString(value).replace(/'/g, "''");
};
+22
View File
@@ -0,0 +1,22 @@
import CryptoJS from "crypto-js";
const WORDKEY = process.env.HASHKEY;
export const hashAPIPath = (queryPath) => {
var hashlink = CryptoJS.HmacSHA256(
queryPath,
CryptoJS.enc.Hex.parse(WORDKEY)
);
hashlink = hashlink.toString(CryptoJS.enc.Hex);
return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink;
};
export const hashString = (stringToHash) => {
let hashedStr = CryptoJS.AES.encrypt(stringToHash, "pedw");
return hashedStr.toString();
};
export const dehashString = (stringToDeHash) => {
let dehashedStr = CryptoJS.AES.decrypt(decodeURI(stringToDeHash), "pedw");
return dehashedStr.toString(CryptoJS.enc.Utf8);
};
+50
View File
@@ -0,0 +1,50 @@
export const azureHeaders = (access_token) => {
return {
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json;odata.metadata=none",
"Prefer": 'odata.include-annotations="*",return=representation',
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token
}
};
};
export const azureHeadersNoOdata = (access_token) => {
return {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token
}
};
};
export const azureHeadersPaged = (access_token) => {
return {
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json;odata.metadata=none",
"Prefer":
'odata.include-annotations="*",return=representation, odata.maxpagesize=10',
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token
}
};
};
export const azureHeadersPagedCustom = (access_token, showNumberOfRecords) => {
return {
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json;odata.metadata=none",
"Prefer":
'odata.include-annotations="*",return=representation, odata.maxpagesize=' +
showNumberOfRecords,
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token
}
};
};
+106
View File
@@ -0,0 +1,106 @@
import _ from "lodash";
const MASK = "[REDACTED]";
const redactString = (value) => {
if (typeof value !== "string") return value;
return value
.replace(
/([a-zA-Z0-9._%+-]{1,})@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g,
`${MASK}@${MASK}`
)
.replace(/Bearer\s+[A-Za-z0-9._\-]+/gi, `Bearer ${MASK}`)
.replace(/("client_secret"\s*:\s*")[^"]+("?)/gi, `$1${MASK}$2`)
.replace(/("access_token"\s*:\s*")[^"]+("?)/gi, `$1${MASK}$2`);
};
export const redactSensitive = (value) => {
if (value == null) return value;
if (typeof value === "string") {
return redactString(value);
}
try {
const json = JSON.stringify(value);
return redactString(json);
} catch (_error) {
return MASK;
}
};
export const getIP = (req) => {
const forwarded = req.headers["x-forwarded-for"];
const ip =
typeof forwarded === "string"
? forwarded.split(/, /)[0]
: req.socket.remoteAddress;
console.info(
"\n====================\n Client IP address: " + ip,
"\n=====================\n"
);
};
export const consoleLogger = (err) => {
console.log(
"\n\n/////////////////////////////////////////////////\nRaw Error " +
redactSensitive(err) +
"\n\n/////////////////////////////////////////////////\n"
);
var errStr =
"\n\n/////////////////////////////////////////////////\nServer Error " +
"\n" +
(_.has(err, "name") ? "Name: " + err.name + "\n" : "") +
(_.has(err, "code") ? "Code: " + err.code + "\n" : "") +
(_.has(err, "query") ? "Query: " + err.query + "\n" : "") +
(_.has(err, "message") ? "Message: " + err.message + "\n" : "") +
(_.has(err, "request.url")
? "request.url: " + err.request.url + "\n"
: "") +
(_.has(err, "response.status") ? "Status: " + err.code + "\n" : "") +
(_.has(err, "response.statusText")
? "\nStatusText: " + err.response.status + "\n"
: "") +
(_.has(err, "response.request.socket.remoteAddress")
? "\nRequest IP: " +
err.response.request.socket.remoteAddress +
"\n"
: "") +
(_.has(err, "response.data.error.message")
? "\nMessage: " +
redactSensitive(err.response.data.error.message) +
"\n"
: "") +
(_.has(err, "response.config.url")
? "\nRequest URL: " + redactSensitive(err.config.url) + "\n"
: "") +
(_.has(err, "response.headers.date")
? "\nRequest Time: " + err.response.headers.date + "\n"
: "") +
(_.has(err, "config.url")
? "\nAxios config url: " + redactSensitive(err.config.url) + "\n"
: "") +
(_.has(err, "config.url")
? "\nAxios message url: " + redactSensitive(err.message) + "\n"
: "") +
(_.has(err, "config.data")
? "\nRequest payload: " + redactSensitive(err.config.data) + "\n"
: "") +
"\n/////////////////////////////////////////////////\n";
console.log(errStr);
};
export const conLog = (err) => {
var errStr =
"\n\n/////////////////////////////////////////////////\nResponse: " +
"\n" +
err +
"\n/////////////////////////////////////////////////\n\n";
console.log(errStr);
return errStr;
};
+41
View File
@@ -0,0 +1,41 @@
import { requestJson } from "../clients/endpointClient";
import { ACCESS_TOKEN_ENDPOINT, TENANT_ID } from "./env";
import { consoleLogger } from "./logger";
const cache = {};
const tokenBody =
"grant_type=" +
process.env.GRANT_TYPE +
"&client_id=" +
process.env.CLIENT_ID +
"&client_secret=" +
process.env.CLIENT_SECRET +
"&scope=" +
"https://" +
process.env.RELAYURI +
"/.default";
const tokenConfig = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "no-cache"
}
};
export const getToken = async () => {
try {
const data = await requestJson({
method: "post",
url: `${ACCESS_TOKEN_ENDPOINT}${TENANT_ID}/oauth2/v2.0/token`,
data: tokenBody,
...tokenConfig
});
cache.tokenResponse = data;
return data;
} catch (error) {
consoleLogger(error);
return error;
}
};
+9 -2197
View File
File diff suppressed because it is too large Load Diff
+32 -21
View File
@@ -1,53 +1,58 @@
export const getSelectQuery = (appealTypeName) => {
switch (appealTypeName) {
case "pinswg_advertses":
return "&$select=pinswg_speacialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_procedure,pinswg_questionnaireduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_startdate,pinswg_otherpartiesstatement,pinswg_finalcommentsduedate,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_speacialistcaseprocess,pinswg_statementduedate";
return "&$select=pinswg_detailedeiascreeningrequired,pinswg_speacialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_procedure,pinswg_questionnaireduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_startdate,pinswg_otherpartiesstatement,pinswg_finalcommentsduedate,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_speacialistcaseprocess,pinswg_statementduedate";
break;
case "pinswg_callinss77s":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_startdate,statuscode,pinswg_dateexportedtoiss,pinswg_startdateofevent,pinswg_typeofevent,pinswg_statementsduedate,pinswg_otherpartiesstatement";
return "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_startdate,statuscode,pinswg_dateexportedtoiss,pinswg_startdateofevent,pinswg_typeofevent,pinswg_statementsduedate,pinswg_otherpartiesstatement";
break;
case "pinswg_commonlands":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_starttimeoftheevent,pinswg_typeofevent,pinswg_statementsduedate";
return "&$select=pinswg_detailedeiascreeningrequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_starttimeoftheevent,pinswg_typeofevent,pinswg_statementsduedate";
break;
case "pinswg_environmentalpermittings":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
break;
case "pinswg_hedgeshedgerowstreepreservationreplacems":
return "&$select=pinswg_specialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate,pinswg_specialistcaseprocess";
return "&$select=pinswg_detailedeiascreening,pinswg_specialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate,pinswg_specialistcaseprocess";
break;
case "pinswg_miscellaneouscaseworks":
return "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate";
break;
case "pinswg_wayleaves":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate";
return "&$select=pinswg_detailedeiascreening,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate";
break;
case "pinswg_communityinfrastructurelevys117118119s":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_questionnaireduedate,pinswg_statementduedate";
return "&$select=pinswg_detailedeiascreeningrequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_questionnaireduedate,pinswg_statementduedate";
break;
case "pinswg_compulsorypurchaseorderses":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate";
return "&$select=pinswg_detailedeiascreening,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddresscounty,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate";
break;
case "pinswg_dnses":
return "&$select=pinswg_anticipatedgridreferenceeastingtext,pinswg_anticipatedgridreferencenorthingtext,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_addresscounty,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_choiceofprocedure,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofdecision,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_decision,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectdescription,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_suspensionenddates,pinswg_suspensionstartdates,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_casestage,_pinswg_dnsids_value,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=pinswg_eiarequired,pinswg_anticipatedgridreferenceeastingtext,pinswg_anticipatedgridreferencenorthingtext,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_addresscounty,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_choiceofprocedure,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofdecision,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_decision,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectdescription,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_suspensionenddates,pinswg_suspensionstartdates,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_casestage,_pinswg_dnsids_value,pinswg_startdateofevent,pinswg_typeofevent";
break;
case "pinswg_electricityacts":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_decision,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_decision,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
break;
case "pinswg_harbourrevisionorders":
return "&$select=pinswg_eiarequired,pinswg_anticipatedgridreferenceeastingtext,pinswg_anticipatedgridreferencenorthingtext,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_addresscounty,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_choiceofprocedure,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofdecision,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_decision,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectdiscription,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_suspensionenddates,pinswg_suspensionstartdate,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_casestage,pinswg_startdateofevent,pinswg_typeofevent";
break;
case "pinswg_householderappealhases":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstmt,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode";
return "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstmt,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode";
break;
case "pinswg_enforcementnoticeappeals174s":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
return "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
break;
case "pinswg_enforcementlistedbuildingconservationaps":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatements,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdates,pinswg_statementduedate,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatements,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdates,pinswg_statementduedate,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
break;
case "pinswg_maintenanceoflands217s":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdates,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdates,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
break;
case "pinswg_lawfuldevelopmentcertificates":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatements,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequiered,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatements,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
break;
case "pinswg_listedbuildingandconservationareaconsens":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_sitepostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_starttimeofevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequiered,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_sitepostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_starttimeofevent,pinswg_typeofevent";
break;
case "pinswg_planningappeals78s":
//console.log(
@@ -55,17 +60,17 @@ export const getSelectQuery = (appealTypeName) => {
// process.env.RELAYPATH == "test-pedw-hc"
// );
return process.env.RELAYPATH == "dev-pedw-hc"
? "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_representation_period_end_date,pinswg_extend_representation_date,pinswg_typeofevent"
: "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_typeofevent";
? "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,pinswg_siteaddresscounty,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_representation_period_end_date,pinswg_extend_representation_date,pinswg_typeofevent"
: "&$select=pinswg_eiarequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,pinswg_siteaddresscounty,_pinswg_localplanningauthority_value,pinswg_casedecisiondate,pinswg_dicision,pinswg_dateeventrequested,pinswg_finalcommentsduedate,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdatetimeiftheevent,pinswg_typeofevent,pinswg_developmentdescription,pinswg_whyisinspectorneededonsite,pinswg_outlinehealthandsafetyissuesonsite,pinswg_additionalappealsmadedetails,pinswg_chosenprocedureexplanation,pinswg_dateofapplication,pinswg_lpaapplicationreference,pinswg_caseworkreason,pinswg_dateoflpadecision,pinswg_areaofsiteinhectaresdec,pinswg_floorspaceinsquaremeters,pinswg_ownershipcertificate,pinswg_siteviewablefromroad,pinswg_costappliedforindicator,pinswg_s106unilateralsubmitted";
break;
case "pinswg_planningconditionss73s79s":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequiered,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateofevent,pinswg_typeofevent";
break;
case "pinswg_planningobligationappeals106s":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatements,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
return "&$select=pinswg_detailedeiascreeningrequired,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatements,pinswg_procedure,pinswg_questionnaireduedate,pinswg_startdate,pinswg_statementduedate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent";
break;
case "pinswg_rows":
return "&$select=pinswg_specialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate,pinswg_specialistcaseprocess";
return "&$select=pinswg_detailedeiascreeningrequired,pinswg_specialistcaseprocess,modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,pinswg_casedecisiondate,pinswg_dateeventrequested,pinswg_decision,pinswg_finalcommentsduedate,pinswg_otherpartiesstatement,pinswg_procedure,pinswg_proofsofevidencewrittenstatementsofeviden,pinswg_questionnaireduedate,pinswg_relevantauthorityname,pinswg_startdate,statuscode,pinswg_startdateoftheevent,pinswg_typeofevent,pinswg_statementduedate,pinswg_specialistcaseprocess";
break;
case "pinswg_ldps":
return "&$select=modifiedon,pinswg_name";
@@ -73,6 +78,12 @@ export const getSelectQuery = (appealTypeName) => {
case "pinswg_nonvalidations":
return "&$select=modifiedon,_pinswg_appellant_value,pinswg_name,pinswg_siteaddressline1,pinswg_siteaddressline2,pinswg_siteaddresspostcode,pinswg_siteaddresstown,_pinswg_associatedlpa_value,statuscode";
break;
case "pinswg_transportandworkacts":
return "&$select=pinswg_eiarequired,pinswg_anticipatedgridreferenceeastingtext,pinswg_anticipatedgridreferencenorthingtext,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_addresscounty,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_choiceofprocedure,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofdecision,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectdescription,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_suspensionenddates,pinswg_suspensionstartdate,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_casestage,pinswg_startdateofevent,pinswg_typeofevent";
case "pinswg_sipses":
// return "&$select=pinswg_anticipatedgridreferenceeastingtext,pinswg_anticipatedgridreferencenorthingtext,_pinswg_appellant_value,pinswg_name,pinswg_addressline1,pinswg_addressline2,pinswg_postcode,pinswg_addresstown,_pinswg_associatedlpa_value,pinswg_addresscounty,pinswg_applicationacceptedasvalid,pinswg_applicationrejectedasinvalid,pinswg_choiceofprocedure,pinswg_confirmrequesttovaryapplication,pinswg_dateeventrequested,pinswg_dateofdecision,pinswg_dateofrequesttovarytheapplication,pinswg_dateofsubmissionofapplication,pinswg_dateprojectpublishedonwebsite,pinswg_deadlineforsubmissionofapplication,pinswg_decision,pinswg_endofrepresentationperiod,pinswg_inspectorreportsubmittedtowelshgovernment,pinswg_procedureconfirmed,pinswg_projectdescription,pinswg_projectlocation,pinswg_recommendation,pinswg_rejectrequesttovaryapplication,pinswg_reportdueby,pinswg_suspensiondates,pinswg_suspensionenddates,pinswg_suspensionstartdates,pinswg_validnoticeofintentiontosubmitanapplicati,pinswg_webaddress,pinswg_withdrawndate,pinswg_writtenacceptanceofnotification,statuscode,pinswg_casestage,_pinswg_dnsids_value,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=_pinswg_projecttype_value,pinswg_eiarequired,pinswg_anticipatedgridrefeasting,pinswg_anticipatedgridrefnorthing,_pinswg_appellant_value,pinswg_name,_pinswg_associatedlpa_value,pinswg_choiceofprocedure,pinswg_decision,pinswg_projectdescription,pinswg_projectlocation,pinswg_recommendation,statuscode,pinswg_casestage,_pinswg_sipscase_value,pinswg_writtenacceptanceofnotification,pinswg_validnoticeofintentiontosubmit,pinswg_deadlineforsubmissionofapplication,pinswg_screeningstartdate,pinswg_eiatargetdate,pinswg_targetextended,pinswg_directionissueddate,pinswg_eiarequired,pinswg_scopingstartdate,pinswg_scopingtargetdate,pinswg_scopingtargetextended,pinswg_scopingoutcomeissueddate,pinswg_essubmitted,pinswg_deemedmarinelicenseappliedfor,pinswg_validationdeadlineextension,pinswg_casesuspensionstartdate,pinswg_casesuspensionenddate,pinswg_consultationopen,pinswg_consultationclose,pinswg_reportissuedtowelshministers,pinswg_decisionissuedtoapplicant,pinswg_decisionoutcome,pinswg_redetermination,pinswg_redeterminationconsultationdeadline";
break;
default:
return "";
break;
+108
View File
@@ -0,0 +1,108 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { getJson, requestJson } from "../clients/endpointClient";
import { getSignedJson } from "../clients/signedRequestClient";
export const getPersonalAccount = (contactid) => {
return getJson(
BASE_URL + "/api/endpoint/getpersonalaccount_api?contactid=" + contactid
).catch((error) => {
consoleLogger(error);
});
};
export const getLogin = (emailAddress, pwd) => {
return getJson(
"/api/endpoint/getlogin_api?emailAddress=" +
emailAddress +
"&pwd=" +
pwd
).catch((error) => {
consoleLogger(error);
});
};
export const updatePassword = (contactId, newpassword) => {
var queryUrl = "/api/endpoint/updateaccount_api?contactId=" + contactId;
var data = { "pinswg_custom_password": newpassword };
var config = {
method: "post",
url: queryUrl,
data: data
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const updateAccount = async (contactId, updateBody, ssr) => {
var queryUrl =
(ssr ? BASE_URL : "") +
"/api/endpoint/updateaccount_api?contactId=" +
contactId;
var data = updateBody;
var config = {
method: "post",
url: queryUrl,
data: data
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const createAccount = (formValues) => {
var data = formValues;
var queryUrl = "/api/endpoint/createaccount_api";
var config = {
method: "post",
url: queryUrl,
data: data
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const getEmailAccountCheck = (emailAddress) => {
return getJson(
"/api/endpoint/getemailaccountcheck_api?emailAddress=" + emailAddress
).catch((error) => {
consoleLogger(error);
});
};
export const getPortalLogin = async (emailAddress) => {
var queryUrl =
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
return getSignedJson(queryUrl, { baseUrl: BASE_URL }).catch((error) => {
consoleLogger(error);
return JSON.stringify(error);
});
};
export const getPortalLoginProxy = async (emailAddress) => {
var queryUrl =
"/api/endpoint/getportalloginproxy_api?emailAddress=" + emailAddress;
return getJson(queryUrl).catch((error) => {
consoleLogger(error);
return JSON.stringify(error);
});
};
export const getPreferredLanguage = async (email) => {
return getJson(
BASE_URL +
"/api/endpoint/getpreferredlanguage_api?emailAddress=" +
email
).catch((error) => {
consoleLogger(error);
return error.response;
});
};
+23
View File
@@ -0,0 +1,23 @@
import {
getPersonalAccount,
getLogin,
updatePassword,
updateAccount,
createAccount,
getEmailAccountCheck,
getPortalLogin,
getPortalLoginProxy,
getPreferredLanguage
} from "./accountDirectService";
export {
getPersonalAccount,
getLogin,
updatePassword,
updateAccount,
createAccount,
getEmailAccountCheck,
getPortalLogin,
getPortalLoginProxy,
getPreferredLanguage
};
+67
View File
@@ -0,0 +1,67 @@
import { BASE_URL } from "../core/env";
import { logAndReturnResponse } from "./httpServiceUtils";
import { getJson } from "../clients/endpointClient";
export const getNewAppeals = async (searchString) => {
try {
return await getJson(BASE_URL + "/api/admin/getnewappeals_api");
} catch (error) {
return logAndReturnResponse(error);
}
};
export const getNewAppealsPage = async (
searchString,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
) => {
try {
return await getJson(
"/api/admin/getnewappeals_api?searchString=" +
searchString +
"&pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
);
} catch (error) {
return logAndReturnResponse(error);
}
};
export const getNewDocumentsPaged = async (
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords,
documentType,
documentOrigin,
selectedWeeks
) => {
try {
return await getJson(
"/api/admin/getlatestdocuments_api?pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords +
"&documentType=" +
documentType +
"&numberWeeks=" +
selectedWeeks +
"&documentOrigin=" +
documentOrigin
);
} catch (error) {
return logAndReturnResponse(error);
}
};
+7
View File
@@ -0,0 +1,7 @@
import {
getNewAppeals,
getNewAppealsPage,
getNewDocumentsPaged
} from "./adminDirectService";
export { getNewAppeals, getNewAppealsPage, getNewDocumentsPaged };
+276
View File
@@ -0,0 +1,276 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { logAndReturnResponse } from "./httpServiceUtils";
import { getJson, requestJson } from "../clients/endpointClient";
import { buildFileQuery, withBaseUrl } from "../clients/fileRouteBuilder";
export const getCaseMessage = (searchString) => {
const route = buildFileQuery("/api/endpoint/getcasemessage_api", {
id: searchString
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getIncidentbyID = (searchString) => {
const route = buildFileQuery("/api/endpoint/getincidentbyid_api", {
searchString
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getIsPublishedbyID = (searchString) => {
const route = buildFileQuery("/api/endpoint/getispublishedbyid_api", {
searchString
});
return getJson(route).catch(logAndReturnResponse);
};
export const getPartSavedAppeal = (searchString) => {
const route = buildFileQuery("/api/endpoint/getpartsavedappeal_api", {
searchString
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getSIPSEvents = async (caseid) => {
const route = buildFileQuery("/api/endpoint/getsipsevents_api", {
caseid
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getSIPSMedia = async (caseid) => {
const route = buildFileQuery("/api/endpoint/getsipsmedia_api", {
caseid
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getAppealID = (
caseReference,
updateFormCollection,
primaryAttribute
) => {
const route = buildFileQuery("/api/endpoint/getappealid_api", {
updateFormCollection,
primaryAttribute,
caseReference
});
return getJson(route)
.then((data) => {
const result = Object.entries(data.value[0]).filter(
([key]) => !key.startsWith("_")
)[0][1];
var appealID = result;
return appealID;
})
.catch((error) => {
consoleLogger(error);
});
};
export const createNewCase = (
appealTypeId,
lpaID,
contactid,
createBody,
containerName
) => {
appealTypeId = parseInt(appealTypeId);
var data = createBody;
var queryUrl = buildFileQuery("/api/endpoint/createcase_api", {
appealTypeId,
lpaID,
contactid,
containername: containerName
});
var config = {
method: "post",
url: withBaseUrl(BASE_URL, queryUrl),
data: data
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const createNewCaseBlob = (
appealTypeId,
lpaID,
contactid,
createBody,
containerName,
lpaName
) => {
appealTypeId = parseInt(appealTypeId);
var data = createBody;
data.pinswg_lpaname = lpaName;
data.createdon = new Date();
var queryUrl = buildFileQuery("/api/file/createcase_api", {
appealTypeId,
lpaID,
contactid,
containername: containerName
});
var config = {
method: "post",
url: queryUrl,
data: data
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const updateCase = async (
incidentId,
updateBody,
updateFormCollection,
primaryAttribute,
caseReference
) => {
var data = updateBody;
var appealObj = await getAppealID(
caseReference,
updateFormCollection,
primaryAttribute
);
var queryUrl = buildFileQuery("/api/endpoint/updatecase_api", {
updateFormCollection,
appealObj
});
var config = {
method: "post",
url: queryUrl,
data: updateBody
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const updateCaseBlob = async (
incidentId,
updateBody,
updateFormCollection,
primaryAttribute,
caseReference
) => {
var data = updateBody;
var appealObj = await getAppealID(
caseReference,
updateFormCollection,
primaryAttribute
);
var queryUrl = buildFileQuery("/api/file/updatecase_api", {
updateFormCollection,
appealObj
});
var config = {
method: "post",
url: queryUrl,
data: updateBody
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const patchCase = async (incidentid) => {
var queryUrl = buildFileQuery("/api/endpoint/patchcase_api", {
incidentid
});
return getJson(queryUrl).catch((error) => {
//console.log("this error:", error);
});
};
export const getCase = (incidentID) => {
const route = buildFileQuery("/api/endpoint/getcase_api", {
incidentID
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getCaseByID = (incidentID) => {
const route = buildFileQuery("/api/endpoint/getcasebyid_api", {
incidentID
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getAppealPDFDocs = (incidentID) => {
const route = buildFileQuery("/api/endpoint/getappealpdfdocuments_api", {
incidentid: incidentID
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getAppealPDFDocument = async (incidentid) => {
const route = buildFileQuery("/api/endpoint/getappealpdfdocuments_api", {
incidentid
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
return error.response;
});
};
export const getPortalModuleDetails = async (appealType, caseReference) => {
const route = buildFileQuery("/api/endpoint/getportalmoduledetails_api", {
appealType,
caseReference: encodeURI(caseReference)
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getPortalModuleDetailsProxy = (appealType, caseReference) => {
const route = buildFileQuery(
"/api/endpoint/getportalmoduledetailsproxy_api",
{
appealType,
caseReference: caseReference.replace(/\'/g, "''")
}
);
return getJson(route).catch((error) => {
consoleLogger(error);
});
};
+41
View File
@@ -0,0 +1,41 @@
import {
getCaseMessage,
getIncidentbyID,
getSIPSEvents,
getSIPSMedia,
getAppealID,
createNewCase,
createNewCaseBlob,
updateCase,
updateCaseBlob,
patchCase,
getCase,
getCaseByID,
getAppealPDFDocs,
getAppealPDFDocument,
getPartSavedAppeal,
getIsPublishedbyID,
getPortalModuleDetails,
getPortalModuleDetailsProxy
} from "./caseDirectService";
export {
getCaseMessage,
getIncidentbyID,
getSIPSEvents,
getSIPSMedia,
getAppealID,
createNewCase,
createNewCaseBlob,
updateCase,
updateCaseBlob,
patchCase,
getCase,
getCaseByID,
getAppealPDFDocs,
getAppealPDFDocument,
getPartSavedAppeal,
getIsPublishedbyID,
getPortalModuleDetails,
getPortalModuleDetailsProxy
};
+394
View File
@@ -0,0 +1,394 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { hashAPIPath } from "../core/hash";
import {
buildFileQuery,
withBaseUrl,
appendQuerySuffix,
appendHashSuffix
} from "../clients/fileRouteBuilder";
import {
getFileJson,
getSignedFileJson,
downloadFileBlob,
postSignedFileJson
} from "../clients/fileClient";
const MAX_FILES_PER_UPLOAD_BATCH = 10;
export const getAwaitingSubmissionFromBlob = (containerName) => {
const route = buildFileQuery("/api/file/getawaitingsubmissionfromblob", {
container: containerName
});
return getFileJson(
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
};
export const getRepsFromBlob = (containerName) => {
const route = buildFileQuery("/api/file/getrepsblob", {
container: containerName
});
return getFileJson(
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
};
export const getRepsFromBlobProxy = async (containerName) => {
const route = buildFileQuery("/api/file/getrepsblobproxy", {
container: containerName
});
return getFileJson(route).catch((error) => {
consoleLogger(error);
});
};
export const getAwaitingSubmissionFromBlobProxy = async (containerName) => {
const route = buildFileQuery(
"/api/file/getawaitingsubmissionfromblobproxy",
{
container: containerName
}
);
return getFileJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getFilesFromBlobproxy = (containerName, casefolderID) => {
const route = buildFileQuery("/api/file/getbloblistproxy", {
container: containerName,
casefolderID
});
return getFileJson(route).catch((error) => {
consoleLogger(error);
});
};
export const deleteAwaitingSubmissionsFromBlob = (
containerID,
casefolderID
) => {
var queryUrl = buildFileQuery("/api/file/deleteblobcase", {
container: containerID,
casefolderID
});
return getSignedFileJson(queryUrl).catch((error) => {
consoleLogger(error);
});
};
export const deleteMyRepresentationsFromBlob = (
containerID,
casefolderID,
repfile
) => {
var queryUrl = buildFileQuery("/api/file/deleteblobrep", {
container: containerID,
casefolderID,
repfile
});
return getSignedFileJson(queryUrl).catch((error) => {
consoleLogger(error);
});
};
export const uploadFiles = async (
formValues,
filesObj,
containerID,
casefolderID
) => {
let files = filesObj;
var formData = new FormData();
formData.append("appealData", JSON.stringify(formValues));
formData.append("containerID", containerID);
formData.append("casefolderID", casefolderID);
for (let i = 0; i < files.length; i++) {
for (let j = 0; j < files[i].length; j++) {
formData.append(files[i][j].name, files[i][j]);
}
}
var queryUrl = "/api/file/upload";
try {
return await postSignedFileJson(queryUrl, formData);
} catch (error) {
consoleLogger(error);
}
};
export const uploadSingleFile = async (
filesObj,
containerID,
casefolderID,
options = {}
) => {
let files = filesObj;
try {
const queryUrl = "/api/file/uploadsinglefile";
const uploadResponse = {
data: [],
invalidFiles: []
};
let cumulativeUploaded = 0;
let cumulativeInvalid = 0;
const totalFiles = files.length;
const totalChunks = Math.ceil(totalFiles / MAX_FILES_PER_UPLOAD_BATCH);
for (
let index = 0;
index < files.length;
index += MAX_FILES_PER_UPLOAD_BATCH
) {
const chunkIndex =
Math.floor(index / MAX_FILES_PER_UPLOAD_BATCH) + 1;
const fileChunk = files.slice(
index,
index + MAX_FILES_PER_UPLOAD_BATCH
);
const formData = new FormData();
formData.append("containerID", containerID);
formData.append("casefolderID", casefolderID);
for (let i = 0; i < fileChunk.length; i++) {
formData.append(fileChunk[i].name, fileChunk[i]);
}
const chunkResult = await postSignedFileJson(queryUrl, formData);
if (Array.isArray(chunkResult?.data)) {
uploadResponse.data.push(...chunkResult.data);
cumulativeUploaded += chunkResult.data.length;
}
if (Array.isArray(chunkResult?.invalidFiles)) {
uploadResponse.invalidFiles.push(...chunkResult.invalidFiles);
cumulativeInvalid += chunkResult.invalidFiles.length;
}
if (typeof options.onChunkComplete === "function") {
options.onChunkComplete({
chunkIndex,
totalChunks,
totalFiles,
cumulativeUploaded,
cumulativeInvalid,
processedFiles: cumulativeUploaded + cumulativeInvalid
});
}
}
return uploadResponse;
} catch (error) {
consoleLogger(error);
}
};
export const uploadRepFiles = async (
formValues,
filesObj,
containerID,
casefolderID
) => {
var formData = new FormData();
formData.append("appealData", JSON.stringify(formValues));
formData.append("containerID", containerID);
formData.append("casefolderID", casefolderID);
formData.append("repOrAppeal", true);
var queryUrl = "/api/file/upload";
try {
return await postSignedFileJson(queryUrl, formData);
} catch (error) {
consoleLogger(error);
}
};
export const generateRepPDF = async (
formValues,
containerID,
casefolderID,
options = {}
) => {
var queryUrl =
"/api/file/generatepdf" + (options.download ? "?download=true" : "");
formValues.containerID = containerID;
try {
return await postSignedFileJson(queryUrl, formValues, {
...(options.download ? { responseType: "blob" } : {})
});
} catch (error) {
consoleLogger(error);
}
};
export const generateAppealPDF = async (
formValues,
containerID,
casefolderID,
appealType,
fileList,
options = {}
) => {
Object.assign(formValues, {
"filesList": fileList
});
var queryUrl =
"/api/file/generateappealpdf?appealType=" +
appealType +
(options.download ? "&download=true" : "");
try {
return await postSignedFileJson(queryUrl, formValues, {
...(options.download ? { responseType: "blob" } : {})
});
} catch (error) {
consoleLogger(error);
}
};
export const getFilesFromBlob = (containerName, casefolderID) => {
const route = buildFileQuery("/api/file/getbloblist", {
container: containerName,
casefolderID
});
return getFileJson(
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
};
export const getFilesFromBlobHashed = (
containerName,
getblobshash,
casefolderID
) => {
const route = buildFileQuery("/api/file/getbloblist", {
container: containerName,
casefolderID
});
return getFileJson(appendQuerySuffix(route, getblobshash)).catch(
(error) => {
consoleLogger(error);
}
);
};
export const deleteBlob = async (
containerName,
blobName,
deleteblobhash,
casefolderID
) => {
const route = buildFileQuery(
"/api/file/deleteblob",
{
container: containerName,
casefolderID,
blobname: blobName
},
{
encode: true
}
);
return getFileJson(appendQuerySuffix(route, deleteblobhash)).catch(
(error) => {
consoleLogger(error);
}
);
};
export const deleteRepBlob = async (
containerName,
blobName,
deleteblobhash,
casefolderID,
filenamePrefix
) => {
const route = buildFileQuery(
"/api/file/deleteblob",
{
container: containerName,
casefolderID: casefolderID + "/" + filenamePrefix,
blobname: blobName
},
{
encode: true
}
);
return getFileJson(appendQuerySuffix(route, deleteblobhash)).catch(
(error) => {
consoleLogger(error);
}
);
};
export const downloadBlob = (containerName, blobName) => {
const queryUrl = withBaseUrl(
BASE_URL,
buildFileQuery("/api/file/downloadblob", {
container: containerName.toLowerCase(),
blobname: blobName
})
);
return downloadFileBlob(queryUrl).catch((error) => {
consoleLogger(error);
});
};
export const getProgressFromBlob = async (containerName, casereference) => {
const route = buildFileQuery(
"/api/file/getprogressobjblob",
{
container: containerName,
casefolderID: casereference
},
{
encode: true
}
);
return getFileJson(
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
};
export const createContainerProxy = (containerName) => {
var route = buildFileQuery("/api/file/setupcontainer", {
ident: containerName
});
var queryUrl = appendHashSuffix(route, hashAPIPath);
return getFileJson(withBaseUrl(BASE_URL, queryUrl)).catch((error) => {
consoleLogger(error);
return JSON.stringify(error);
});
};
+43
View File
@@ -0,0 +1,43 @@
import {
getAwaitingSubmissionFromBlob,
getRepsFromBlob,
getRepsFromBlobProxy,
getAwaitingSubmissionFromBlobProxy,
deleteAwaitingSubmissionsFromBlob,
deleteMyRepresentationsFromBlob,
uploadFiles,
uploadSingleFile,
uploadRepFiles,
generateRepPDF,
generateAppealPDF,
getFilesFromBlob,
getFilesFromBlobproxy,
getFilesFromBlobHashed,
deleteBlob,
deleteRepBlob,
downloadBlob,
getProgressFromBlob,
createContainerProxy
} from "./documentDirectService";
export {
getAwaitingSubmissionFromBlob,
getRepsFromBlob,
getRepsFromBlobProxy,
getAwaitingSubmissionFromBlobProxy,
deleteAwaitingSubmissionsFromBlob,
deleteMyRepresentationsFromBlob,
uploadFiles,
uploadSingleFile,
uploadRepFiles,
generateRepPDF,
generateAppealPDF,
getFilesFromBlob,
getFilesFromBlobproxy,
getFilesFromBlobHashed,
deleteBlob,
deleteRepBlob,
downloadBlob,
getProgressFromBlob,
createContainerProxy
};
+19
View File
@@ -0,0 +1,19 @@
import { consoleLogger } from "../core/logger";
export const buildEmptyValueErrorResponse = (error) => {
return {
value: [],
errorCode: error.response.status,
errorMsg: error.response.statusText
};
};
export const logAndReturnResponse = (error) => {
consoleLogger(error);
return error.response;
};
export const logAndReturnEmptyValueErrorResponse = (error) => {
consoleLogger(error);
return buildEmptyValueErrorResponse(error);
};
+9
View File
@@ -0,0 +1,9 @@
export * from "./searchService";
export * from "./caseService";
export * from "./accountService";
export * from "./portalService";
export * from "./documentService";
export * from "./referenceDataService";
export * from "./notifyService";
export * from "./adminService";
export * from "./integrationService";
@@ -0,0 +1,19 @@
import { consoleLogger } from "../core/logger";
import { requestJson } from "../clients/endpointClient";
export const createCRMTask = async (formValues) => {
var queryUrl = "/api/endpoint/createcrmtask_api";
var data = formValues;
var config = {
method: "post",
url: queryUrl,
data: data
};
try {
return await requestJson(config);
} catch (error) {
consoleLogger(error);
}
};
+3
View File
@@ -0,0 +1,3 @@
import { createCRMTask } from "./integrationDirectService";
export { createCRMTask };
+27
View File
@@ -0,0 +1,27 @@
import { consoleLogger } from "../core/logger";
import { requestJson } from "../clients/endpointClient";
export const sendEmail = async (
templateId,
emailAddress,
personalisation,
reference
) => {
var mailData = {
"templateId": templateId,
"emailAddress": emailAddress,
"reference": reference,
"personalisation": personalisation
};
try {
return await requestJson({
method: "post",
url: "/api/email/notify",
data: mailData
});
} catch (error) {
consoleLogger(error);
throw error;
}
};
+3
View File
@@ -0,0 +1,3 @@
import { sendEmail } from "./notifyDirectService";
export { sendEmail };
+309
View File
@@ -0,0 +1,309 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { getJson, requestJson } from "../clients/endpointClient";
import {
deleteSignedJson,
buildSignedUrl
} from "../clients/signedRequestClient";
import {
buildFileQuery,
withBaseUrl,
appendQuerySuffix
} from "../clients/fileRouteBuilder";
export const getMyCases = (loggedInUserId) => {
const route = buildFileQuery("/api/endpoint/getmycases_api", {
loggedInUserId
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getMyInvolvements = async (loggedInUserId) => {
const route = buildFileQuery("/api/endpoint/getmyinvolvements_api", {
loggedInUserId
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getMyLPACases = (lpaid) => {
const route = buildFileQuery("/api/endpoint/getmylpacases_api", {
lpaid
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getMyRepresentations = (loggedInUserId) => {
const route = buildFileQuery("/api/endpoint/getmyrepresentations_api", {
loggedInUserId
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getMyRepresentationsProxy = (loggedInUserId) => {
const route = buildFileQuery(
"/api/endpoint/getmyrepresentationsproxy_api",
{
loggedInUserId
}
);
return getJson(route).catch((error) => {
consoleLogger(error);
});
};
export const getRepresentations = (incidentID) => {
const route = buildFileQuery("/api/endpoint/getrepresentations_api", {
incidentID
});
return getJson(route).catch((error) => {
consoleLogger(error);
});
};
export const getRepresentationsProxy = (incidentID) => {
const route = buildFileQuery("/api/endpoint/getrepresentationsproxy_api", {
incidentID
});
return getJson(route).catch((error) => {
consoleLogger(error);
});
};
export const getWatchedCases = (loggedInUserId) => {
const route = buildFileQuery("/api/endpoint/getwatchedcases_api", {
loggedInUserId
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getWatchedCasesProxy = (loggedInUserId) => {
const route = buildFileQuery("/api/endpoint/getwatchedcasesproxy_api", {
loggedInUserId
});
return getJson(route).catch((error) => {
consoleLogger(error);
});
};
export const getAwaitingSubmissionProxy = (loggedInUserId) => {
const route = buildFileQuery(
"/api/endpoint/getawaitingsubmissionproxy_api",
{
loggedInUserId
}
);
return getJson(route).catch((error) => {
consoleLogger(error);
});
};
export const getAwaitingSubmission = (loggedInUserId) => {
const route = buildFileQuery("/api/endpoint/getawaitingsubmission_api", {
loggedInUserId
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const createWatchedCases = async (formValues) => {
var data = formValues;
var queryUrl = buildFileQuery("/api/endpoint/createwatchedcases_api");
var config = {
method: "post",
url: queryUrl,
data: data
};
try {
return await requestJson(config);
} catch (error) {
consoleLogger(error);
}
};
export const deleteMyRepresentations = (myRepresentationsID) => {
var queryUrl = buildFileQuery("/api/endpoint/deletemyrepresentations_api", {
myRepresentationsID
});
return deleteSignedJson(queryUrl, {
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json;odata.metadata=none",
"Prefer": 'odata.include-annotations="*",return=representation',
"Content-Type": "application/json"
}
}).catch((error) => {
consoleLogger(error);
});
};
export const deleteAwaitingSubmissions = (incidentID) => {
var queryUrl = buildFileQuery(
"/api/endpoint/deleteawaitingsubmissions_api",
{
incidentID
}
);
var config = {
method: "delete",
url: queryUrl
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const deleteWatchedCases = async (watchedCaseID) => {
var queryUrl = buildFileQuery("/api/endpoint/deletewatchedcases_api", {
watchedCaseID
});
return deleteSignedJson(queryUrl).catch((error) => {
consoleLogger(error);
});
};
export const sendCaseCompleteMessage = async (
containerID,
caseReference,
inv
) => {
var hashQueryPath = buildFileQuery(
"/api/file/createappealcompletemessage_api",
{
container: containerID,
tempcaseref: caseReference
}
);
var queryUrl = buildFileQuery("/api/file/createappealcompletemessage_api", {
container: containerID,
tempcaseref: caseReference,
inv
});
var signedQueryUrl = await buildSignedUrl(hashQueryPath);
queryUrl = appendQuerySuffix(
queryUrl,
signedQueryUrl.replace(hashQueryPath, "")
);
var config = {
method: "get",
url: queryUrl
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const sendCaseCompleteMessageProxy = async (
containerID,
caseReference
) => {
var queryUrl = buildFileQuery(
"/api/file/createappealcompletemessageproxy_api",
{
container: containerID,
tempcaseref: caseReference
}
);
var config = {
method: "get",
url: queryUrl
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const sendRepCompleteMessage = async (
containerID,
caseReference,
fileName
) => {
var hashQueryPath = buildFileQuery(
"/api/file/createrepcompletemessage_api",
{
container: containerID,
tempcaseref: caseReference,
repid: fileName
}
);
var queryUrl = await buildSignedUrl(hashQueryPath);
var config = {
method: "get",
url: queryUrl
};
return requestJson(config).catch((error) => {
consoleLogger(error);
});
};
export const setRepInvolvment = async (caseid, contactid) => {
var queryUrl = buildFileQuery("/api/file/createrepinvolvement_api");
var data = { "incidentid": caseid, "contactid": contactid };
var config = {
method: "post",
url: queryUrl,
data: data
};
try {
return await requestJson(config);
} catch (error) {
consoleLogger(error);
}
};
export const setCaseInvolvment = async (caseid, contactid) => {
var queryUrl = buildFileQuery("/api/file/createrepinvolvement_api");
var data = { "incidentid": caseid, "contactid": contactid };
var config = {
method: "post",
url: queryUrl,
data: data
};
try {
return await requestJson(config);
} catch (error) {
consoleLogger(error);
}
};
+45
View File
@@ -0,0 +1,45 @@
import {
getMyCases,
getMyInvolvements,
getMyLPACases,
getMyRepresentations,
getMyRepresentationsProxy,
getRepresentations,
getRepresentationsProxy,
getWatchedCases,
getWatchedCasesProxy,
getAwaitingSubmission,
getAwaitingSubmissionProxy,
createWatchedCases,
deleteWatchedCases,
deleteMyRepresentations,
deleteAwaitingSubmissions,
sendCaseCompleteMessage,
sendCaseCompleteMessageProxy,
sendRepCompleteMessage,
setRepInvolvment,
setCaseInvolvment
} from "./portalDirectService";
export {
getMyCases,
getMyInvolvements,
getMyLPACases,
getMyRepresentations,
getMyRepresentationsProxy,
getRepresentations,
getRepresentationsProxy,
getWatchedCases,
getWatchedCasesProxy,
getAwaitingSubmission,
getAwaitingSubmissionProxy,
createWatchedCases,
deleteWatchedCases,
deleteMyRepresentations,
deleteAwaitingSubmissions,
sendCaseCompleteMessage,
sendCaseCompleteMessageProxy,
sendRepCompleteMessage,
setRepInvolvment,
setCaseInvolvment
};
@@ -0,0 +1,58 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { logAndReturnEmptyValueErrorResponse } from "./httpServiceUtils";
import { getJson } from "../clients/endpointClient";
export const getAppealsTypes = () => {
return getJson(BASE_URL + "/api/endpoint/getappealtypes_api").catch(
logAndReturnEmptyValueErrorResponse
);
};
export const getProjectTypes = () => {
return getJson(BASE_URL + "/api/endpoint/getprojecttypes_api").catch(
logAndReturnEmptyValueErrorResponse
);
};
export const getAppealsTypesForNewAppeal = () => {
return getJson(
BASE_URL + "/api/endpoint/getappealtypesfornewappeal_api"
).catch(logAndReturnEmptyValueErrorResponse);
};
export const getLPA = () => {
return getJson(BASE_URL + "/api/endpoint/getlpa_api").catch(
logAndReturnEmptyValueErrorResponse
);
};
export const getFormData = (whichForm) => {
return getJson(
BASE_URL + "/api/endpoint/getformdata_api?whichForm=" + whichForm
).catch((error) => {
consoleLogger(error);
});
};
export const getMandatoryFields = (whichForm) => {
return getJson(
BASE_URL + "/api/endpoint/getmandatoryfields_api?whichForm=" + whichForm
).catch((error) => {
consoleLogger(error);
});
};
export const getPickLists = (whichForm) => {
return getJson(
BASE_URL + "/api/endpoint/getpicklists_api?whichForm=" + whichForm
).catch((error) => {
consoleLogger(error);
});
};
export const getNotice = () => {
return getJson(BASE_URL + "/api/notices").catch((error) => {
consoleLogger(error);
});
};
+21
View File
@@ -0,0 +1,21 @@
import {
getAppealsTypes,
getProjectTypes,
getAppealsTypesForNewAppeal,
getLPA,
getFormData,
getMandatoryFields,
getPickLists,
getNotice
} from "./referenceDataDirectService";
export {
getAppealsTypes,
getProjectTypes,
getAppealsTypesForNewAppeal,
getLPA,
getFormData,
getMandatoryFields,
getPickLists,
getNotice
};
+261
View File
@@ -0,0 +1,261 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { getJson } from "../clients/endpointClient";
import {
logAndReturnResponse,
logAndReturnEmptyValueErrorResponse
} from "./httpServiceUtils";
export const getBasicSearch = (searchString) => {
return getJson(
BASE_URL +
"/api/endpoint/getbasicsearch_api?searchString=" +
searchString
).catch(logAndReturnResponse);
};
export const getBasicDNSURLSearch = (searchString) => {
return getJson(
BASE_URL +
"/api/endpoint/getbasicdnsurlsearch_api?searchString=" +
searchString
).catch(logAndReturnResponse);
};
export const getAddressSearchPaged = (
searchString,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
) => {
return getJson(
"/api/endpoint/getbasicsearch_by_address_paged_api?searchString=" +
searchString +
"&pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
).catch(logAndReturnResponse);
};
export const getBasicSearchPaged = (
searchString,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
) => {
return getJson(
"/api/endpoint/getbasicsearchpaged_api?searchString=" +
searchString +
"&pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
).catch(logAndReturnResponse);
};
export const getDNSCoords = () => {
return getJson(BASE_URL + "/api/endpoint/getdnscoords_api").catch(
logAndReturnEmptyValueErrorResponse
);
};
export const getDNSList = (searchString) => {
return getJson(BASE_URL + "/api/endpoint/getdnslist_api").catch(
logAndReturnResponse
);
};
export const getBasicDNSSearch = (searchString) => {
return getJson(BASE_URL + "/api/endpoint/getbasicdnssearch_api").catch(
logAndReturnResponse
);
};
export const getBasicDNSSearchPaged = (
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
) => {
return getJson(
"/api/endpoint/getbasicdnssearchpaged_api?pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
).catch(logAndReturnResponse);
};
export const getAdvancedSearch = (searchString) => {
return getJson(
BASE_URL +
"/api/endpoint/getadvancedsearch_api?searchstring=" +
JSON.stringify(searchString)
).catch(logAndReturnEmptyValueErrorResponse);
};
export const getAdvancedSearchPaged = (
searchString,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
) => {
return getJson(
"/api/endpoint/getadvancedsearchpaged_api?searchstring=" +
JSON.stringify(searchString) +
"&pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords
).catch(logAndReturnEmptyValueErrorResponse);
};
export const getBasicSearchDetails = async (
appealTypeName,
caseReference,
primaryIdAttribute,
incidentID
) => {
return getJson(
BASE_URL +
"/api/endpoint/getbasicsearchdetails_api?appealTypeName=" +
appealTypeName +
"&primaryIdAttribute=" +
primaryIdAttribute +
"&incidentID=" +
incidentID
).catch((error) => {
consoleLogger(error);
});
};
export const getBasicPartSavedDetails = (
appealTypeName,
caseReference,
primaryIdAttribute,
incidentID
) => {
return getJson(
BASE_URL +
"/api/endpoint/getbasicpartsaveddetails_api?appealTypeName=" +
appealTypeName +
"&primaryIdAttribute=" +
primaryIdAttribute +
"&incidentID=" +
incidentID
).catch((error) => {
consoleLogger(error);
});
};
export const getBasicSearchDetailsPaged = async (
appealTypeName,
caseReference,
primaryIdAttribute,
incidentIDs
) => {
if (!incidentIDs || incidentIDs.length === 0) return [];
const filter = incidentIDs
.map((id) => {
const suffix =
primaryIdAttribute === "pinswg_sipscase"
? `_${primaryIdAttribute}_value`
: `_${primaryIdAttribute}s_value`;
return `${suffix} eq ${id}`;
})
.join(" or ");
const url = `/api/endpoint/getbasicsearchdetailspaged_api?appealTypeName=${appealTypeName}&primaryIdAttribute=${primaryIdAttribute}&incidentID=${encodeURIComponent(
filter
)}`;
try {
const data = await getJson(url);
return data.value || [];
} catch (error) {
consoleLogger(error);
return [];
}
};
export const getSearchDocumentDetails = (incidentID) => {
return getJson(
"/api/endpoint/getsearchdocumentdetails_api?incidentid=" + incidentID
).catch((error) => {
consoleLogger(error);
throw error;
});
};
export const getSearchDocumentTypes = (incidentID) => {
return getJson(
"/api/endpoint/getsearchdocumentTypes_api?incidentid=" + incidentID
).catch((error) => {
consoleLogger(error);
});
};
export const getSearchDocumentDetailsPaged = (
incidentID,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords,
documentType
) => {
return getJson(
"/api/endpoint/getsearchdocumentdetailspaged_api?incidentid=" +
incidentID +
"&pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords +
"&documentType=" +
documentType
).catch((error) => {
consoleLogger(error);
});
};
export const getLinkedCases = (parentIncidentid) => {
return getJson(
"/api/endpoint/getlinkedcases_api?parentincidentid=" + parentIncidentid
).catch((error) => {
consoleLogger(error);
});
};
export const getAddressSearch = async (searchString) => {
const params = new URLSearchParams(searchString);
const str = params.toString();
var queryUrl =
BASE_URL + "/api/endpoint/getbasicsearch_by_address_api?" + str;
return getJson(queryUrl).catch(logAndReturnEmptyValueErrorResponse);
};
+41
View File
@@ -0,0 +1,41 @@
import {
getBasicSearch,
getAddressSearch,
getAddressSearchPaged,
getBasicSearchPaged,
getAdvancedSearch,
getAdvancedSearchPaged,
getBasicDNSURLSearch,
getBasicDNSSearch,
getBasicDNSSearchPaged,
getDNSCoords,
getDNSList,
getBasicSearchDetails,
getBasicSearchDetailsPaged,
getBasicPartSavedDetails,
getSearchDocumentDetails,
getSearchDocumentTypes,
getSearchDocumentDetailsPaged,
getLinkedCases
} from "./searchDirectService";
export {
getBasicSearch,
getAddressSearch,
getAddressSearchPaged,
getBasicSearchPaged,
getAdvancedSearch,
getAdvancedSearchPaged,
getBasicDNSURLSearch,
getBasicDNSSearch,
getBasicDNSSearchPaged,
getDNSCoords,
getDNSList,
getBasicSearchDetails,
getBasicSearchDetailsPaged,
getBasicPartSavedDetails,
getSearchDocumentDetails,
getSearchDocumentTypes,
getSearchDocumentDetailsPaged,
getLinkedCases
};
+57
View File
@@ -0,0 +1,57 @@
# Refactor Session Handoff (Usable Prompt)
## Purpose
Copy/paste this directly into a new chat session to continue the current PEDW refactor stream with the right context and next-step planning.
## Copy/Paste Prompt
```
You are continuing an in-flight refactor stream in the PEDW FrontEnd repository.
Before any analysis or code changes:
1) Ask for the Azure DevOps work item ID (required).
2) Create a new branch from `origin/SIPS-Development` prefixed with that ID (e.g. `US12345-progress-update` or `TASK67890-progress-update`).
3) Confirm branch name, then continue.
Follow this order before implementing:
1) Read `.clinerules/default-rules.md` and `.clinerules/CONTRIBUTING_AI.md`
2) Read `GUARDRAILS.md`
3) Read current-state docs:
- `context/architecture.md`
- `context/current-state-scorecard.md`
- `context/next-work-plan-sequence-b.md`
- `context/test-coverage-map.md`
4) Read memory context:
- `memory-bank/change-log.md` (latest entries)
- `memory-bank/debt-list.md`
- `memory-bank/architect-review.md`
- `memory-bank/decisions.md`
- `memory-bank/open-questions.md`
Then provide (based on what you read):
1. Current state summary (what is done, what remains)
2. Recommended next slice from this exact shortlist (pick one):
- Slice B1.1: create shared signed-request helper(s) in `actions/clients/` and migrate one pilot path in `actions/services/portalDirectService.js`
- Slice B1.2: migrate remaining signed flows in `actions/services/documentDirectService.js` to the shared signed helper(s)
- Slice B2.1: auth logging hardening in `pages/api/auth/[...nextauth].js` (redacted structured logs, no behavior change)
- Slice B2.2: file/email logging hardening in selected `pages/api/file/**` and `pages/api/email/**`
3. Exact files to change
4. Validation plan (lint + targeted tests + EN/CY parity + negative-path checks)
5. Rollback plan
Constraints:
- Preserve behavior unless explicitly requested otherwise
- Do not weaken auth/session/CSP/security flows
- Keep EN/CY parity
- Keep scope to one bounded slice per commit
- Update `memory-bank/change-log.md` for non-trivial changes
After proposing the slice, wait for approval before editing files.
```
## Best use
- Start-of-day continuation
- Branch handover between engineers/agents
- Recovery after interrupted context window
+183
View File
@@ -0,0 +1,183 @@
# Refactor Slice Execution (Usable Prompts)
## Purpose
Copy/paste one of these prompts directly to execute a specific next slice (not just a template).
## Prompt A — Slice B1.1 (signed-request helper + pilot migration)
```
Act as a senior architect + senior software engineer working in PEDW FrontEnd.
Before any analysis or code changes:
1) Ask for the Azure DevOps work item ID (required).
2) Create a new branch from `origin/SIPS-Development` prefixed with that ID (e.g. `US12345-signed-helper-pilot`).
3) Confirm branch name and only then proceed.
Goal:
Implement Slice B1.1 only: introduce shared signed-request helper(s) and migrate one pilot signed path in portal service.
Exact scope:
- Add shared signed-request helper(s) in `actions/clients/` for signed GET/POST/DELETE execution.
- Migrate one pilot signed flow in `actions/services/portalDirectService.js` to use the helper.
- Do not migrate additional modules in this slice.
Mandatory context to read before coding:
- `.clinerules/default-rules.md`
- `GUARDRAILS.md`
- `context/architecture.md`
- `context/current-state-scorecard.md`
- `context/next-work-plan-sequence-b.md`
- `memory-bank/debt-list.md`
- latest relevant `memory-bank/change-log.md` entries
Required output before edits:
1. Scope and non-goals
2. Risks and how they are contained
3. Files to touch
4. Acceptance criteria
5. Validation checklist
Implementation constraints:
- Smallest viable diff
- Preserve API/route/public behavior
- Preserve auth/session/security and EN/CY parity
- No dependency churn unless required by scope
- No unrelated cleanup
Validation minimum:
- `npm run lint`
- `node tests/phase22/index.test.cjs`
- `node tests/phase7/service-behaviour.test.cjs`
- explicit negative-path verification for sensitive flows
Completion output:
1. Files changed
2. Behavior parity statement
3. Validation evidence (commands + pass/fail)
4. Rollback steps
5. `memory-bank/change-log.md` entry content
```
## Prompt B — Slice B1.2 (document signed-flow migration)
```
Act as a senior architect + senior software engineer working in PEDW FrontEnd.
Before any analysis or code changes:
1) Ask for the Azure DevOps work item ID (required).
2) Create a new branch from `origin/SIPS-Development` prefixed with that ID (e.g. `TASK67890-document-signed-migration`).
3) Confirm branch name and only then proceed.
Goal:
Implement Slice B1.2 only: migrate remaining signed flows in `documentDirectService` to shared signed-request helper(s) with no behavior change.
Exact scope:
- Update `actions/services/documentDirectService.js` signed GET/POST/DELETE paths to use shared signed helper(s).
- Preserve hash generation, headers, methods, and catch-path behavior.
- Do not modify unrelated services.
Mandatory context to read before coding:
- `.clinerules/default-rules.md`
- `GUARDRAILS.md`
- `context/architecture.md`
- `context/current-state-scorecard.md`
- `context/next-work-plan-sequence-b.md`
- `context/test-coverage-map.md`
- latest relevant `memory-bank/change-log.md` entries
Required output before edits:
1. Scope and non-goals
2. Risks and containment
3. Files to touch
4. Acceptance criteria
5. Validation checklist
Validation minimum:
- `npm run lint`
- `node tests/phase22/index.test.cjs`
- `node tests/phase7/service-behaviour.test.cjs`
Completion output:
1. Files changed
2. Behavior parity statement
3. Validation evidence
4. Rollback steps
5. `memory-bank/change-log.md` entry
```
## Prompt C — Slice B2.1 (auth logging hardening)
```
Act as a senior architect + senior software engineer working in PEDW FrontEnd.
Before any analysis or code changes:
1) Ask for the Azure DevOps work item ID (required).
2) Create a new branch from `origin/SIPS-Development` prefixed with that ID (e.g. `US12345-auth-logging-hardening`).
3) Confirm branch name and only then proceed.
Goal:
Implement Slice B2.1 only: harden logging in `pages/api/auth/[...nextauth].js` using redacted structured logging with no behavior change.
Exact scope:
- Replace sensitive/verbose direct logs in auth route with structured logger usage.
- Do not alter auth/session/redirect functional behavior.
Mandatory context to read before coding:
- `.clinerules/default-rules.md`
- `GUARDRAILS.md`
- `context/architecture.md`
- `context/next-work-plan-sequence-b.md`
- `memory-bank/open-questions.md` (Q-001)
Validation minimum:
- `npm run lint`
- `node tests/phase22/auth-redirect-safety.test.cjs`
- targeted manual negative-path check for auth error logging paths
Completion output must include:
1. Redaction policy applied (fields excluded)
2. Files changed
3. Behavior parity statement
4. Validation evidence
5. Rollback plan
6. `memory-bank/change-log.md` update text
```
## Prompt D — Slice B2.2 (file/email logging hardening cluster)
```
Act as a senior architect + senior software engineer working in PEDW FrontEnd.
Before any analysis or code changes:
1) Ask for the Azure DevOps work item ID (required).
2) Create a new branch from `origin/SIPS-Development` prefixed with that ID (e.g. `TASK67890-file-email-logging-hardening`).
3) Confirm branch name and only then proceed.
Goal:
Implement Slice B2.2 only: harden logging in one bounded file/email route cluster using redacted structured logs.
Exact scope:
- Choose a small cluster in `pages/api/file/**` and/or `pages/api/email/**`.
- Replace risky direct logs with structured redacted logging.
- Preserve endpoint contracts and negative-path semantics.
Validation minimum:
- `npm run lint`
- targeted tests for changed handlers
- explicit negative-path check(s) for unauthorized/invalid input behavior
Completion output:
1. Cluster chosen and why
2. Files changed
3. Redaction fields/policy applied
4. Validation evidence
5. Rollback steps
6. `memory-bank/change-log.md` update text
```
## Best use
- Run Sequence B slices as independent, reviewable commits
- Start a fresh chat and execute one prompt at a time
- Keep each slice bounded with explicit rollback
+5 -5
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import React, { useState } from "react";
import { connect } from "react-redux";
import { Field, formValueSelector, reduxForm } from "redux-form";
import { updatePassword } from "../../actions";
import { updatePassword } from "../../actions/services/accountService";
const RenderTextfield = ({
id,
@@ -60,7 +60,7 @@ const ChangePassword = (props) => {
handleSubmit,
value,
setPasswordUpdated,
passwordUpdated,
passwordUpdated
} = props;
let { t, lang } = useTranslation();
@@ -158,7 +158,7 @@ const mapStateToProps = (state) => {
searchResultsObj: state.searchResultsObj,
formData: state.formData,
appealType: state.appealType,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -167,7 +167,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentSection: (currentSection) => {
dispatch(setCurrentSection(currentSection));
},
}
};
};
@@ -179,6 +179,6 @@ export default connect(
)(
reduxForm({
form: "changepasswordForm",
destroyOnUnmount: false,
destroyOnUnmount: false
})(ChangePassword)
);
+16 -5
View File
@@ -246,6 +246,7 @@ let PersonalDetails = (props) => {
return (
<>
<div className="govuk-form-group">
{props.accountUpdatedComplete == false ? (
props.personalDetailsComplete == false ? (
<form onSubmit={handleSubmit(onHandleSubmit)}>
@@ -286,7 +287,9 @@ let PersonalDetails = (props) => {
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t("account:account-first-name-label")}
label={t(
"account:account-first-name-label"
)}
errorMsg={t("account:is-required-label")}
/>
<Field
@@ -306,7 +309,9 @@ let PersonalDetails = (props) => {
type="email"
className="govuk-input govuk-input--width-20"
validate={[required, email]}
label={t("account:account-email-address-label")}
label={t(
"account:account-email-address-label"
)}
disabled
/>
<Field
@@ -324,14 +329,18 @@ let PersonalDetails = (props) => {
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t("account:account-company-group-label")}
label={t(
"account:account-company-group-label"
)}
errorMsg={t("account:is-required-label")}
/>
</fieldset>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
{t("account:account-your-address-label")}
{t(
"account:account-your-address-label"
)}
</h2>
</legend>
@@ -399,7 +408,8 @@ let PersonalDetails = (props) => {
<Link
href={
"/myportal?q=" +
props.props.accountDetails.loggedinUserId
props.props.accountDetails
.loggedinUserId
}
className="govuk-link"
>
@@ -423,6 +433,7 @@ let PersonalDetails = (props) => {
) : (
<PersonalDetailsComplete props={props} />
)}
</div>
</>
);
};
+39 -12
View File
@@ -3,28 +3,54 @@ import Link from "next/link";
import { useRouter } from "next/router";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { updateAccount } from "../../actions";
import { updateAccount } from "../../actions/services/accountService";
import { setCookie } from "nookies";
const PersonalDetailsComplete = (props) => {
const {
handleSubmit,
value,
setRegisterFormComplete,
setAccountCreatedComplete,
setAccountCreatedComplete
} = props;
let { t, lang } = useTranslation();
const router = useRouter();
const { locale } = router;
// let contactid = props.accountDetails.loggedinUserId;
// let updateBody = props.props.props.form.personalDetailsForm.values;
// let prefLang =
// props.props.props.form.personalDetailsForm.values
// .pinswg_preferredlanguage;
const { loggedinUserId } = props.accountDetails || {};
const formValues = props.props?.props?.form?.personalDetailsForm?.values;
const prefLang = formValues?.pinswg_preferredlanguage;
const userLang = prefLang === "846040000" ? "cy" : "en"; // Move this outside useEffect
useEffect(() => {
let contactid = props.accountDetails.loggedinUserId;
let updateBody = props.props.props.form.personalDetailsForm.values;
updateAccount(contactid, updateBody);
}, [
props.accountDetails.loggedinUserId,
props.props.props.form.personalDetailsForm.values,
]);
// let contactid = props.accountDetails.loggedinUserId;
// let updateBody = props.props.props.form.personalDetailsForm.values;
// let prefLang =
// props.props.props.form.personalDetailsForm.values
// .pinswg_preferredlanguage;
if (!loggedinUserId || !formValues) return;
console.log("=============== pref lang", prefLang);
setCookie(null, "pedw_locale", userLang, {
path: "/"
});
console.log("Set cookie to:", userLang);
async function doUpdate() {
const response = await updateAccount(loggedinUserId, formValues);
}
doUpdate();
}, [loggedinUserId, formValues, prefLang]);
return (
<>
@@ -34,7 +60,8 @@ const PersonalDetailsComplete = (props) => {
<p className="govuk-body">
<Link
href={"/myportal?q=" + props.accountDetails.loggedinUserId}
locale={userLang}
href={userLang === "cy" ? "/cy/fymhorth" : "/myportal"}
className="govuk-link"
>
{" "}
@@ -51,7 +78,7 @@ const mapStateToProps = (state) => {
searchResultsObj: state.searchResultsObj,
formData: state.formData,
appealType: state.appealType,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -60,7 +87,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentSection: (currentSection) => {
dispatch(setCurrentSection(currentSection));
},
}
};
};
+10 -7
View File
@@ -8,7 +8,10 @@ import { connect } from "react-redux";
import { setAccCr, setLogout } from "../../store/accountDetails/action";
import { createAccount, getEmailAccountCheck } from "../../actions";
import {
createAccount,
getEmailAccountCheck
} from "../../actions/services/accountService";
const RegisterComplete = (props) => {
const {
@@ -18,7 +21,7 @@ const RegisterComplete = (props) => {
value,
setRegisterFormComplete,
setAccountCreatedComplete,
setAccCr,
setAccCr
} = props;
let { t, lang } = useTranslation();
@@ -34,7 +37,7 @@ const RegisterComplete = (props) => {
// was an appellant "pinswg_typeofinvolvement": 846040001,
Object.assign(accountBody, {
"pinswg_typeofinvolvement": 846040061,
"pinswg_typeofinvolvement": 846040061
});
accountBody = _.omit(accountBody, ["custom_password_check"]);
@@ -105,9 +108,9 @@ const RegisterComplete = (props) => {
<p className="govuk-body">
<a
onClick={() => {
setRegisterFormComplete(false),
(setRegisterFormComplete(false),
setAccountCreatedComplete(false),
setAccCr(false);
setAccCr(false));
}}
className="govuk-link"
>
@@ -130,7 +133,7 @@ const mapStateToProps = (state) => {
searchResultsObj: state.searchResultsObj,
formData: state.formData,
appealType: state.appealType,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -142,7 +145,7 @@ const mapDispatchToProps = (dispatch) => {
},
setLogout: () => {
dispatch(setLogout());
},
}
};
};
+4
View File
@@ -137,6 +137,8 @@ const RegisterForm = (props) => {
{props.accountCreatedComplete != true ? (
props.registerFormComplete == false ? (
<form onSubmit={handleSubmit(onHandleSubmit)}>
<div className="card" id="personalDetailsCard">
<div className="card-body">
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
@@ -281,6 +283,8 @@ const RegisterForm = (props) => {
{t("common:continue-button")}
</button>
</div>
</div>
</div>
</form>
) : (
<RegisterFormCheck
+15 -14
View File
@@ -9,14 +9,15 @@ import { connect } from "react-redux";
import {
setSearchResults,
setSearchDetails,
setSearchDetails
} from "../../../store/searchOutput/action";
import { getDetailsProxy, getSearchDetailsPaged } from "../../utils";
import { getAdvancedSearchPaged, getNewAppealsPage } from "../../../actions";
import { getAdvancedSearchPaged } from "../../../actions/services/searchService";
import { getNewAppealsPage } from "../../../actions/services/adminService";
import {
setCurrentPage,
setCurrentReference,
setCurrentReference
} from "../../../store/currentView/action";
function AppealsTab(props) {
@@ -86,7 +87,7 @@ function AppealsTab(props) {
searchString,
selectedOption,
props.setSearchDetails,
props.setSearchResults,
props.setSearchResults
]
);
@@ -105,7 +106,7 @@ function AppealsTab(props) {
orderByState,
searchLoaded,
getSearchPageResults,
router.query,
router.query
]);
const ResultsView = (resultsArr) => {
@@ -225,7 +226,7 @@ function AppealsTab(props) {
item.ticketnumber +
'")]',
json: searchDetailsObj,
eval: true,
eval: true
});
detailsObj = detailsObj[0] || {};
@@ -275,7 +276,7 @@ function AppealsTab(props) {
"pinswg_specialistcaseprocess"
)
? detailsObj.pinswg_specialistcaseprocess
: "",
: ""
});
}}
>
@@ -396,7 +397,7 @@ function AppealsTab(props) {
" " +
item.pinswg_appellantlastname
: _.has(detailsObj, [
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue",
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
])
? detailsObj[
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
@@ -411,7 +412,7 @@ function AppealsTab(props) {
:
</span>
{_.has(item, [
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue",
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
])
? router.locale == "cy"
? jsonpath({
@@ -422,7 +423,7 @@ function AppealsTab(props) {
] +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
@@ -445,7 +446,7 @@ function AppealsTab(props) {
] +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item[
"pinswg_appealcasetype@OData.Community.Display.V1.FormattedValue"
@@ -468,7 +469,7 @@ function AppealsTab(props) {
] +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item[
"statuscode@OData.Community.Display.V1.FormattedValue"
@@ -555,13 +556,13 @@ const mapDispatchToProps = (dispatch) => {
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
}
};
};
const mapStateToProps = (state) => {
return {
searchResultsObj: state.searchResultsObj,
searchResultsObj: state.searchResultsObj
};
};
+59 -89
View File
@@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { useCallback, useEffect, useRef, useState } from "react";
import { connect } from "react-redux";
import transLookup from "../../../data/lookuptranslations.json";
import { formatDates } from "../../utils";
import { formatDates, getDocLink } from "../../utils";
import PaginationControl from "../../../components/case/pagination";
import DocumentLink from "../../utils/downloads";
import { useDownloadQueue } from "../../utils/downloadmanager";
@@ -15,177 +15,177 @@ import { sendGAEvent } from "@next/third-parties/google";
import {
getSearchDocumentDetails,
getSearchDocumentDetailsPaged,
getSearchDocumentTypes,
getNewDocumentsPaged,
} from "../../../actions";
getSearchDocumentTypes
} from "../../../actions/services/searchService";
import { getNewDocumentsPaged } from "../../../actions/services/adminService";
import { setCurrentPage } from "../../../store/currentView/action";
import { setDocumentDetails } from "../../../store/searchOutput/action";
const getDocumentTypes = [
{
"Value": 846040009,
"Label": "Main Party - Pre-Submission Docs",
"Label": "Main Party - Pre-Submission Docs"
},
{
"Value": 846040000,
"Label": "Main Party - Submission Document",
"Label": "Main Party - Submission Document"
},
{
"Value": 846040001,
"Label": "Main Party - Questionnaire",
"Label": "Main Party - Questionnaire"
},
{
"Value": 846040002,
"Label": "Main Party - Statements",
"Label": "Main Party - Statements"
},
{
"Value": 846040003,
"Label": "Main Party - Comments",
"Label": "Main Party - Comments"
},
{
"Value": 846040004,
"Label": "Main Party - Written Statements of Evidence",
"Label": "Main Party - Written Statements of Evidence"
},
{
"Value": 846040010,
"Label": "Main Party - Environmental Statements",
"Label": "Main Party - Environmental Statements"
},
{
"Value": 846040005,
"Label": "Interested Party Representations",
"Label": "Interested Party Representations"
},
{
"Value": 846040006,
"Label": "Correspondence",
"Label": "Correspondence"
},
{
"Value": 846040008,
"Label": "Inspector Notes",
"Label": "Inspector Notes"
},
{
"Value": 846040007,
"Label": "Final Decision/Report",
"Label": "Final Decision/Report"
},
{
"Value": 846040011,
"Label": "Post Decision Correspondence",
"Label": "Post Decision Correspondence"
},
{
"Value": 846040012,
"Label": "High Court Challenge Correspondence",
"Label": "High Court Challenge Correspondence"
},
{
"Value": 846040013,
"Label": "Event - Correspondence",
"Label": "Event - Correspondence"
},
{
"Value": 846040014,
"Label": "Event - Documents",
"Label": "Event - Documents"
},
{
"Value": 846040015,
"Label": "Pre-Application - General",
"Label": "Pre-Application - General"
},
{
"Value": 846040016,
"Label": "Pre-Application - EIA Screening",
"Label": "Pre-Application - EIA Screening"
},
{
"Value": 846040017,
"Label": "Pre-Application - EIA Scoping",
"Label": "Pre-Application - EIA Scoping"
},
{
"Value": 846040018,
"Label": "EIA Screening",
"Label": "EIA Screening"
},
{
"Value": 846040019,
"Label": "EIA Scoping",
"Label": "EIA Scoping"
},
{
"Value": 846040020,
"Label": "Pre-Application Services Documents",
"Label": "Pre-Application Services Documents"
},
{
"Value": 846040021,
"Label": "Notification",
"Label": "Notification"
},
{
"Value": 846040022,
"Label":
"Submission - Environmental Statement - written statement and NTS",
"Submission - Environmental Statement - written statement and NTS"
},
{
"Value": 846040023,
"Label": "Submission - Environmental Statement - Appendices",
"Label": "Submission - Environmental Statement - Appendices"
},
{
"Value": 846040024,
"Label": "Submission - Environmental Statement - Figures",
"Label": "Submission - Environmental Statement - Figures"
},
{
"Value": 846040025,
"Label": "Submission - Draft Infrastructure Consent Orders",
"Label": "Submission - Draft Infrastructure Consent Orders"
},
{
"Value": 846040026,
"Label": "Submission - Compulsory Acquisition Information",
"Label": "Submission - Compulsory Acquisition Information"
},
{
"Value": 846040027,
"Label": "Submission - Application Documents",
"Label": "Submission - Application Documents"
},
{
"Value": 846040028,
"Label": "Examination Notices",
"Label": "Examination Notices"
},
{
"Value": 846040029,
"Label": "Local/Marine Impact Reports",
"Label": "Local/Marine Impact Reports"
},
{
"Value": 846040030,
"Label": "Submission - Further Information",
"Label": "Submission - Further Information"
},
{
"Value": 846040031,
"Label": "Formal Variation requests",
"Label": "Formal Variation requests"
},
{
"Value": 846040032,
"Label": "Further Information - Interested Party Representations",
"Label": "Further Information - Interested Party Representations"
},
{
"Value": 846040033,
"Label": "Hearing Statements",
"Label": "Hearing Statements"
},
{
"Value": 846040034,
"Label": "Events - Recordings",
"Label": "Events - Recordings"
},
{
"Value": 846040035,
"Label": "Internal Correspondence",
"Label": "Internal Correspondence"
},
{
"Value": 846040036,
"Label": "Consultation Response",
},
"Label": "Consultation Response"
}
];
const getOrigin = [
{
"Value": 846040000,
"Label": "MDU",
"Label": "MDU"
},
{
"Value": 846040001,
"Label": "Portal",
"Label": "Portal"
},
{
"Value": 846040002,
"Label": "Manual",
},
"Label": "Manual"
}
];
const DocumentsTab = (props) => {
@@ -200,7 +200,7 @@ const DocumentsTab = (props) => {
setDocumentDetails,
setCurrentPage,
docsOffline,
showFilteredDocs,
showFilteredDocs
} = props;
const router = useRouter();
@@ -236,7 +236,7 @@ const DocumentsTab = (props) => {
setCheckedItems((prev) => ({
...prev,
[name]: checked,
[name]: checked
}));
setSelectAll(false);
};
@@ -246,7 +246,7 @@ const DocumentsTab = (props) => {
setCheckedOriginItems((prev) => ({
...prev,
[name]: checked,
[name]: checked
}));
setSelectOriginAll(false);
};
@@ -305,37 +305,7 @@ const DocumentsTab = (props) => {
setSelectedDocumentOrigin("all")
);
};
let ShowDocLinks = false;
let checkShowDocLinks = docsOffline != false ? true : false;
const getDocLink = (docsOffline) => {
var startTime = docsOffline.split(",")[0];
var endTime = docsOffline.split(",")[1];
// console.log(startTime);
// console.log(endTime);
var currentDate = new Date();
var startDate = new Date(currentDate.getTime());
startDate.setHours(startTime.split(":")[0]);
startDate.setMinutes(startTime.split(":")[1]);
startDate.setSeconds(startTime.split(":")[2]);
var endDate = new Date(currentDate.getTime());
endDate.setHours(endTime.split(":")[0]);
endDate.setMinutes(endTime.split(":")[1]);
endDate.setSeconds(endTime.split(":")[2]);
// console.log(startDate);
// console.log(endDate);
// console.log(currentDate);
var valid = startDate < currentDate && endDate > currentDate;
ShowDocLinks = valid;
};
checkShowDocLinks && getDocLink(docsOffline);
let ShowDocLinks = getDocLink(docsOffline);
const FilteredDocumentView = (documentDetailsArr) => {
documentDetailsArr = documentDetailsArr.value;
@@ -434,7 +404,7 @@ const DocumentsTab = (props) => {
item.Label +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item.Label ||
t(
@@ -507,7 +477,7 @@ const DocumentsTab = (props) => {
item.Label +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item.Label ||
t(
@@ -526,7 +496,7 @@ const DocumentsTab = (props) => {
const checkedValues = [
...document.querySelectorAll(
".govuk-checkboxes__input:checked"
),
)
].map((e) => e.value);
const origins = checkedValues
@@ -727,7 +697,7 @@ const DocumentsTab = (props) => {
incidentid +
"')]",
json: item,
eval: true,
eval: true
});
detailsObj = item;
return (
@@ -861,7 +831,7 @@ const DocumentsTab = (props) => {
] +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
@@ -976,7 +946,7 @@ const DocumentsTab = (props) => {
selectedDocumentType,
selectedWeeks,
selectedDocumentOrigin,
props.setDocumentDetails,
props.setDocumentDetails
]
);
@@ -1111,7 +1081,7 @@ const DocumentsTab = (props) => {
getOrigin,
getDocumentResults,
lang,
selectedWeeks,
selectedWeeks
]);
function countFieldValue(obj, field, targetValue) {
@@ -1196,7 +1166,7 @@ const DocumentsTab = (props) => {
const mapStateToProps = (state) => {
return {
...state,
...state
};
};
@@ -1207,7 +1177,7 @@ const mapDispatchToProps = (dispatch) => {
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
}
};
};
+8 -8
View File
@@ -4,9 +4,9 @@ import Link from "next/link";
import { bytesToSize } from "../../../components/utils";
import {
deleteMyRepresentationsFromBlob,
deleteAwaitingSubmissionsFromBlob,
sendRepCompleteMessage,
} from "../../../actions";
deleteAwaitingSubmissionsFromBlob
} from "../../../actions/services/documentService";
import { sendRepCompleteMessage } from "../../../actions/services/portalService";
import { formatBlobDates } from "../utils/adminHelper";
@@ -150,7 +150,7 @@ export default function StorageTab({ data, repCompleteCount }) {
headers:
{
"Content-Type":
"application/json",
"application/json"
},
body: JSON.stringify(
{
@@ -159,18 +159,18 @@ export default function StorageTab({ data, repCompleteCount }) {
blobName:
folder
.repJsonBlob
.name,
.name
}
),
)
}
);
const result =
await response.json();
if (response.ok) {
alert(
(alert(
"repComplete removed!"
),
window.location.reload();
window.location.reload());
// Optionally refresh the page or update UI state here
} else {
alert(
+17 -9
View File
@@ -5,9 +5,9 @@ import { PrismaClient } from "@prisma/client";
import CryptoJS from "crypto-js";
import { getUpToLastSlash, streamToString } from "../utils/adminHelper";
import { listContainersForUser } from "../../../actions/azurestorage";
import pLimit from "p-limit";
const prisma = global.prisma || new PrismaClient();
const WORDKEY = process.env.HASHKEY;
/**
* Generate HMAC hash for blob API link.
*/
@@ -34,8 +34,10 @@ export async function fetchAdminStorageData() {
const users = await prisma.user.findMany();
const limit = pLimit(5);
let data = await Promise.all(
users.map(async (user) => {
limit(async () => {
const containers = await listContainersForUser(
blobServiceClient,
`${user.id}`,
@@ -54,7 +56,9 @@ export async function fetchAdminStorageData() {
`/api/file/downloadblob?container=${
container.container
}&casefolderID=${folderPath}&blobname=${blob.name
.substring(blob.name.lastIndexOf("/") + 1)
.substring(
blob.name.lastIndexOf("/") + 1
)
.trim()}`,
process.env.HASHKEY
);
@@ -62,12 +66,13 @@ export async function fetchAdminStorageData() {
const fileNameOnly = blob.name.substring(
blob.name.lastIndexOf("/") + 1
);
const isTmpFile = fileNameOnly.startsWith("TMP-");
const isTmpFile =
fileNameOnly.startsWith("TMP-");
const blobWithHash = {
...blob,
hashedfilepath,
isTmpFile,
isTmpFile
};
let folderGroup = acc.find(
@@ -80,7 +85,7 @@ export async function fetchAdminStorageData() {
hasRepJson: false,
repJsonBlob: null,
repComplete: false,
hasTmpFile: false,
hasTmpFile: false
};
acc.push(folderGroup);
}
@@ -92,7 +97,9 @@ export async function fetchAdminStorageData() {
}
// rep.json handling
if (blob.name.toLowerCase().endsWith("rep.json")) {
if (
blob.name.toLowerCase().endsWith("rep.json")
) {
folderGroup.hasRepJson = true;
folderGroup.repJsonBlob = blobWithHash;
@@ -137,9 +144,10 @@ export async function fetchAdminStorageData() {
? {
id: user.id,
email: user.email,
containers: containersWithHashes,
containers: containersWithHashes
}
: null;
});
})
);
@@ -150,7 +158,7 @@ export async function fetchAdminStorageData() {
.map((user) => ({
id: user.id,
email: user.email,
created: user.emailVerified?.toLocaleString("en-GB") || "",
created: user.emailVerified?.toLocaleString("en-GB") || ""
}));
let repCompleteCount = 0;
+274 -1133
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -42,6 +42,8 @@ const Case = (props) => {
documentDetailsObj={props.documentDetailsObj}
showFilteredDocs={props.showFilteredDocs}
showLoginCheck={props.showLoginCheck}
eventsObj={props.eventsObj}
mediaObj={props.mediaObj}
/>
{showRepresentations == true && (
<RepresentationList
+29 -7
View File
@@ -15,12 +15,22 @@ export default function CaseNoticeBanner(props) {
const bannerOpenCookie = typeof cookie.get("pedwNotice") == "undefined";
let messagesObjArr = messagesObj.value;
let todaysDate = new Date();
return (
<>
{messagesObjArr.map((message, index) => (
<div className="govuk-warning-text noticebanner" key={index}>
<div key={index}>
{todaysDate.toISOString() >= message.scheduledstart &&
todaysDate.toISOString() <= message.scheduledend && (
<div
className="govuk-warning-text noticebanner"
key={index}
>
<strong className="govuk-warning-text__text">
<span className="govuk-visually-hidden">Warning</span>
<span className="govuk-visually-hidden">
{t("case:notice-label")}
</span>
</strong>
<div>
<div className=" govuk-body-s">
@@ -49,7 +59,9 @@ export default function CaseNoticeBanner(props) {
: message.subject
.split("Banner ")[1]
.split(";")[0]
: message.subject.split("Banner ")[1]}
: message.subject.split(
"Banner "
)[1]}
</h2>
</div>
<div className="govuk-body-s">
@@ -59,16 +71,24 @@ export default function CaseNoticeBanner(props) {
) || []
).length > 0
? router.locale == "cy"
? message.description.split(">>>")[1]
: message.description.split(">>>")[0]
? message.description.split(
">>>"
)[1]
: message.description.split(
">>>"
)[0]
: (
message.description.match(
new RegExp(";", "g")
) || []
).length > 0
? router.locale == "cy"
? message.description.split(";")[1]
: message.description.split(";")[0]
? message.description.split(
";"
)[1]
: message.description.split(
";"
)[0]
: message.description}
</div>
<div className="govuk-body-xs govuk-!-margin-bottom-0 ">
@@ -77,6 +97,8 @@ export default function CaseNoticeBanner(props) {
</div>
</div>
</div>
)}
</div>
))}
</>
);
+21 -51
View File
@@ -5,15 +5,15 @@ import { useRouter } from "next/router";
import { useCallback, useEffect, useRef, useState } from "react";
import { connect } from "react-redux";
import transLookup from "../../data/lookuptranslations.json";
import { formatDates } from "../utils";
import { formatDates, getDocLink } from "../utils";
import PaginationControl from "./pagination";
import { sendGAEvent } from "@next/third-parties/google";
import {
getSearchDocumentDetails,
getSearchDocumentDetailsPaged,
getSearchDocumentTypes,
} from "../../actions";
getSearchDocumentTypes
} from "../../actions/services/searchService";
import { setCurrentPage } from "../../store/currentView/action";
import { setDocumentDetails } from "../../store/searchOutput/action";
@@ -32,7 +32,7 @@ const DocumentDetails = (props) => {
setDocumentDetails,
setCurrentPage,
docsOffline,
showFilteredDocs,
showFilteredDocs
} = props;
const router = useRouter();
@@ -57,7 +57,7 @@ const DocumentDetails = (props) => {
setCheckedItems((prev) => ({
...prev,
[name]: checked,
[name]: checked
}));
setSelectAll(false);
};
@@ -90,37 +90,7 @@ const DocumentDetails = (props) => {
setCurrentPage(1)
);
};
let ShowDocLinks = false;
let checkShowDocLinks = docsOffline != false ? true : false;
const getDocLink = (docsOffline) => {
var startTime = docsOffline.split(",")[0];
var endTime = docsOffline.split(",")[1];
// console.log(startTime);
// console.log(endTime);
var currentDate = new Date();
var startDate = new Date(currentDate.getTime());
startDate.setHours(startTime.split(":")[0]);
startDate.setMinutes(startTime.split(":")[1]);
startDate.setSeconds(startTime.split(":")[2]);
var endDate = new Date(currentDate.getTime());
endDate.setHours(endTime.split(":")[0]);
endDate.setMinutes(endTime.split(":")[1]);
endDate.setSeconds(endTime.split(":")[2]);
// console.log(startDate);
// console.log(endDate);
// console.log(currentDate);
var valid = startDate < currentDate && endDate > currentDate;
ShowDocLinks = valid;
};
checkShowDocLinks && getDocLink(docsOffline);
let ShowDocLinks = getDocLink(docsOffline);
const DocumentView = (documentDetailsArr) => {
documentDetailsArr = documentDetailsArr.value;
@@ -162,7 +132,7 @@ const DocumentDetails = (props) => {
? jsonpath({
path: '$..[?(@ && @.value=="All")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: "All"}
</option>
@@ -181,7 +151,7 @@ const DocumentDetails = (props) => {
item.pinswg_isharedocumentlocationsLabel +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item.pinswg_isharedocumentlocationsLabel ||
t(
@@ -328,7 +298,7 @@ const DocumentDetails = (props) => {
incidentid +
"')]",
json: item,
sanbox: {},
sanbox: {}
});
detailsObj = item;
return (
@@ -360,7 +330,7 @@ const DocumentDetails = (props) => {
}
key={key}
onClick={() => {
toggleDownLoadLink(key),
(toggleDownLoadLink(key),
sendGAEvent(
"event",
"DownloadedFile",
@@ -368,9 +338,9 @@ const DocumentDetails = (props) => {
caseReference:
props.caseReference,
filename:
detailsObj.pinswg_name,
detailsObj.pinswg_name
}
);
));
}}
>
<span className="results-visually-hidden">
@@ -418,7 +388,7 @@ const DocumentDetails = (props) => {
] +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
@@ -584,7 +554,7 @@ const DocumentDetails = (props) => {
item.pinswg_isharedocumentlocationsLabel +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: item.pinswg_isharedocumentlocationsLabel ||
t(
@@ -604,7 +574,7 @@ const DocumentDetails = (props) => {
const data = [
...document.querySelectorAll(
".govuk-checkboxes__input:checked"
),
)
].map((e) => e.value);
setSelectedDocumentType(data);
setCurrentPage(1);
@@ -740,7 +710,7 @@ const DocumentDetails = (props) => {
incidentid +
"')]",
json: item,
eval: true,
eval: true
});
detailsObj = item;
return (
@@ -857,7 +827,7 @@ const DocumentDetails = (props) => {
] +
'")].value_cy',
json: transLookup,
eval: true,
eval: true
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
@@ -935,7 +905,7 @@ const DocumentDetails = (props) => {
props.incidentid,
selectedOption,
selectedDocumentType,
setDocumentDetails,
setDocumentDetails
]
);
@@ -1059,7 +1029,7 @@ const DocumentDetails = (props) => {
orderByState,
getDocumentTypes,
getDocumentResults,
lang,
lang
]);
return (
<div className="govuk-grid-row">
@@ -1089,7 +1059,7 @@ const DocumentDetails = (props) => {
const mapStateToProps = (state) => {
return {
...state,
...state
};
};
@@ -1100,7 +1070,7 @@ const mapDispatchToProps = (dispatch) => {
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
}
};
};
+221
View File
@@ -0,0 +1,221 @@
import { setEventDetails } from "../../store/searchOutput/action";
import { setCurrentPage } from "../../store/currentView/action";
import { connect } from "react-redux";
import useTranslation from "next-translate/useTranslation";
import { formatDates } from "../utils";
const EIADetails = (props) => {
let { t } = useTranslation();
const EventView = (eventsArr) => {
eventsArr = eventsArr.value;
return eventsArr.map((item, key) => {
<div>{item.pinswg_name}</div>;
});
};
const eventsArr = props.eventsObj?.value || [];
// event publishiing flag set to true displa
function hasOwnPropertyAndNotNull(obj, property) {
return obj.hasOwnProperty(property) && obj[property] !== null;
}
//console.log(eventsArr.length > 0);
return (
eventsArr.length > 0 && (
<div className="govuk-grid-row">
<div className="card">
<div className="card-body">
<h2 className="govuk-heading-m ">
{t("case:event-title")}
</h2>
{eventsArr.map(
(item, key) =>
item.pinswg_eventpublishflag && (
<>
{" "}
<div className="eventContainer">
<div className="eventColumn">
<div>
<b>
{" "}
{t(
"case:event-name-label"
)}
</b>
: {item.pinswg_name}
</div>
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventname"
) && (
<div>
<b>
{t(
"case:event-event-name-label"
)}
</b>
:{" "}
{item.pinswg_eventname}
</div>
)}
</div>
<div className="eventColumn">
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddressline1"
) && (
<>
<div>
<b>
{t(
"case:event-address-label"
)}
</b>
:
</div>
<div>
{
item.pinswg_eventaddressline1
}
</div>
</>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddressline2"
) && (
<div>
{
item.pinswg_eventaddressline2
}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresstown"
) && (
<div>
{
item.pinswg_eventaddresstown
}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresscounty"
) && (
<div>
{
item.pinswg_eventaddresscounty
}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresscounty"
) && (
<div>
{
item.pinswg_eventaddresspostcode
}
</div>
)}
</div>
{/* type of event
start date date of event - end date of event
if virtual event no address showNoOfRecords
show event address */}
<div className="eventColumn">
{hasOwnPropertyAndNotNull(
item,
"pinswg_dateeventrequested"
) && (
<div>
<b>
{t(
"case:event-start-date-label"
)}
</b>
:{" "}
{formatDates(
item.pinswg_dateeventrequested
)}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_enddateoftheevent"
) && (
<div>
<b>
{t(
"case:event-end-date-label"
)}
</b>
:{" "}
{item.pinswg_enddateoftheevent !=
null &&
formatDates(
item.pinswg_enddateoftheevent
)}
</div>
)}
</div>
<div className="eventColumn">
{hasOwnPropertyAndNotNull(
item,
[
"pinswg_typeofevent@OData.Community.Display.V1.FormattedValue",
]
) && (
<div>
<b>
{t(
"case:event-type-of-event-label"
)}
</b>
:{" "}
{
item[
"pinswg_typeofevent@OData.Community.Display.V1.FormattedValue"
]
}
</div>
)}
</div>
</div>
</>
)
)}{" "}
</div>
</div>
</div>
)
);
};
const mapStateToProps = (state) => {
return {
...state,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setEventDetails: (eventsObj) => {
dispatch(setEventDetails(eventsObj));
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(EIADetails);
+221
View File
@@ -0,0 +1,221 @@
import { setEventDetails } from "../../store/searchOutput/action";
import { setCurrentPage } from "../../store/currentView/action";
import { connect } from "react-redux";
import useTranslation from "next-translate/useTranslation";
import { formatDates } from "../utils";
const EventsDetails = (props) => {
let { t } = useTranslation();
const EventView = (eventsArr) => {
eventsArr = eventsArr.value;
return eventsArr.map((item, key) => {
<div>{item.pinswg_name}</div>;
});
};
const eventsArr = props.eventsObj?.value || [];
// event publishiing flag set to true displa
function hasOwnPropertyAndNotNull(obj, property) {
return obj.hasOwnProperty(property) && obj[property] !== null;
}
//console.log(eventsArr.length > 0);
return (
eventsArr.length > 0 && (
<div className="govuk-grid-row">
<div className="card">
<div className="card-body">
<h2 className="govuk-heading-m ">
{t("case:event-title")}
</h2>
{eventsArr.map(
(item, key) =>
item.pinswg_eventpublishflag && (
<>
{" "}
<div className="eventContainer">
<div className="eventColumn">
<div>
<b>
{" "}
{t(
"case:event-name-label"
)}
</b>
: {item.pinswg_name}
</div>
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventname"
) && (
<div>
<b>
{t(
"case:event-event-name-label"
)}
</b>
:{" "}
{item.pinswg_eventname}
</div>
)}
</div>
<div className="eventColumn">
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddressline1"
) && (
<>
<div>
<b>
{t(
"case:event-address-label"
)}
</b>
:
</div>
<div>
{
item.pinswg_eventaddressline1
}
</div>
</>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddressline2"
) && (
<div>
{
item.pinswg_eventaddressline2
}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresstown"
) && (
<div>
{
item.pinswg_eventaddresstown
}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresscounty"
) && (
<div>
{
item.pinswg_eventaddresscounty
}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresscounty"
) && (
<div>
{
item.pinswg_eventaddresspostcode
}
</div>
)}
</div>
{/* type of event
start date date of event - end date of event
if virtual event no address showNoOfRecords
show event address */}
<div className="eventColumn">
{hasOwnPropertyAndNotNull(
item,
"pinswg_dateeventrequested"
) && (
<div>
<b>
{t(
"case:event-start-date-label"
)}
</b>
:{" "}
{formatDates(
item.pinswg_dateeventrequested
)}
</div>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_enddateoftheevent"
) && (
<div>
<b>
{t(
"case:event-end-date-label"
)}
</b>
:{" "}
{item.pinswg_enddateoftheevent !=
null &&
formatDates(
item.pinswg_enddateoftheevent
)}
</div>
)}
</div>
<div className="eventColumn">
{hasOwnPropertyAndNotNull(
item,
[
"pinswg_typeofevent@OData.Community.Display.V1.FormattedValue",
]
) && (
<div>
<b>
{t(
"case:event-type-of-event-label"
)}
</b>
:{" "}
{
item[
"pinswg_typeofevent@OData.Community.Display.V1.FormattedValue"
]
}
</div>
)}
</div>
</div>
</>
)
)}{" "}
</div>
</div>
</div>
)
);
};
const mapStateToProps = (state) => {
return {
...state,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setEventDetails: (eventsObj) => {
dispatch(setEventDetails(eventsObj));
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(EventsDetails);
+646
View File
@@ -0,0 +1,646 @@
// // import { setEventDetails } from "../../store/searchOutput/action";
// // import { setCurrentPage } from "../../store/currentView/action";
// // import { connect } from "react-redux";
// // import useTranslation from "next-translate/useTranslation";
// // import { formatDates } from "../utils";
// // const MediaDetails = (props) => {
// // let { t } = useTranslation();
// // const MediaView = (mediaArr) => {
// // mediaArr = mediaArr.value;
// // return mediaArr.map((item, key) => {
// // <div>{item.pinswg_name}</div>;
// // });
// // };
// // const mediaArr = props.mediaObj?.value || [];
// // // event publishiing flag set to true displa
// // function hasOwnPropertyAndNotNull(obj, property) {
// // return obj.hasOwnProperty(property) && obj[property] !== null;
// // }
// // //console.log(eventsArr.length > 0);
// // return (
// // mediaArr.length > 0 && (
// // <div className="govuk-grid-row">
// // <div className="card">
// // <div className="card-body">
// // <h2 className="govuk-heading-m ">
// // {t("case:event-title")}Media
// // </h2>
// // {mediaArr.map(
// // (item, key) =>
// // item.pinswg_publishtoweb && (
// // <>
// // {" "}
// // <div className="">
// // <div className="">
// // <div>
// // <b>
// // {" "}
// // {t(
// // "case:event-name-label",
// // )}
// // </b>
// // : {item.pinswg_name}
// // </div>
// // {hasOwnPropertyAndNotNull(
// // item,
// // "pinswg_description",
// // ) && (
// // <div>
// // <b>
// // {t(
// // "case:event-event-name-label",
// // )}
// // </b>
// // :{" "}
// // {
// // item.pinswg_description
// // }
// // </div>
// // )}
// // </div>
// // </div>
// // </>
// // ),
// // )}{" "}
// // </div>
// // </div>
// // </div>
// // )
// // );
// // };
// // const mapStateToProps = (state) => {
// // return {
// // ...state,
// // };
// // };
// // const mapDispatchToProps = (dispatch) => {
// // return {
// // setEventDetails: (mediaObj) => {
// // dispatch(setEventDetails(mediaObj));
// // },
// // setCurrentPage: (currentPage) => {
// // dispatch(setCurrentPage(currentPage));
// // },
// // };
// // };
// // export default connect(mapStateToProps, mapDispatchToProps)(MediaDetails);
// import { useEffect, useMemo, useState } from "react";
// import { connect } from "react-redux";
// import useTranslation from "next-translate/useTranslation";
// import { useRouter } from "next/router";
// const extractYouTubeId = (input) => {
// if (!input) return null;
// if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
// try {
// const url = new URL(input);
// if (url.hostname.includes("youtu.be"))
// return url.pathname.slice(1) || null;
// const v = url.searchParams.get("v");
// if (v) return v;
// const embedMatch = url.pathname.match(/\/embed\/([^/]+)/);
// if (embedMatch?.[1]) return embedMatch[1];
// const shortsMatch = url.pathname.match(/\/shorts\/([^/]+)/);
// if (shortsMatch?.[1]) return shortsMatch[1];
// return null;
// } catch {
// return null;
// }
// };
// export default function MediaDetails({ mediaObj, whichTab }) {
// const router = useRouter();
// const mediaArr = mediaObj?.value || [];
// const videoItems = useMemo(() => {
// const isWelsh = router.locale === "cy";
// return (mediaArr || [])
// .filter((x) => x?.pinswg_publishtoweb)
// .map((item) => {
// const url = isWelsh
// ? item.pinswg_mediaLinkCY
// : item.pinswg_mediaLinkEN;
// const videoId = extractYouTubeId(url);
// if (!videoId) return null;
// return {
// key:
// item.pinswg_documentid ||
// `${item.pinswg_name}-${videoId}`,
// videoId,
// name: item.pinswg_name || "Video",
// description: item.pinswg_description || "",
// thumbnail: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
// };
// })
// .filter(Boolean);
// }, [mediaArr, router.locale]);
// const [watchedIds, setWatchedIds] = useState(new Set());
// const [selectedIndex, setSelectedIndex] = useState(0);
// // Forces a fresh iframe instance whenever we need to hard-stop playback
// const [playerInstance, setPlayerInstance] = useState(0);
// const isActive = whichTab === "case-media";
// const selected = videoItems[selectedIndex] || null;
// // When leaving the media tab, hard-stop playback by unmounting (and bumping instance)
// useEffect(() => {
// if (!isActive) {
// setPlayerInstance((n) => n + 1);
// }
// }, [isActive]);
// // When list/locale changes, reset to first (no autoplay)
// useEffect(() => {
// setSelectedIndex(0);
// setPlayerInstance((n) => n + 1);
// }, [videoItems.length, router.locale]);
// if (!videoItems.length) return null;
// const embedBase = "https://www.youtube-nocookie.com/embed";
// // IMPORTANT:
// // - autoplay=0 for initial load
// // - autoplay=1 only when user clicks an item
// const [autoplay, setAutoplay] = useState(false);
// useEffect(() => {
// // whenever we leave tab or reset player, don't autoplay next time
// if (!isActive) setAutoplay(false);
// }, [isActive]);
// const embedSrc = selected
// ? `${embedBase}/${selected.videoId}?rel=0&modestbranding=1&playsinline=1&autoplay=${autoplay ? "1" : "0"}`
// : "";
// return (
// <div className="govuk-grid-row">
// <div className="card">
// <div className="card-body">
// <h2 className="govuk-heading-m">Media</h2>
// {/* Player: only render when tab is active */}
// {isActive && (
// <div className="videoWrapper govuk-!-margin-bottom-4">
// <iframe
// key={`${selected?.videoId}-${playerInstance}`}
// src={embedSrc}
// title={selected?.name || "YouTube video"}
// frameBorder="0"
// allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
// allowFullScreen
// />
// </div>
// )}
// <h3 className="govuk-heading-s">Videos</h3>
// <ul className="govuk-list govuk-list--spaced">
// {videoItems.map((v, idx) => {
// const isSelected = idx === selectedIndex;
// return (
// <li key={v.key}>
// <button
// type="button"
// className={`videoTabButton ${isSelected ? "active" : ""}`}
// onClick={() => {
// setSelectedIndex(idx);
// setAutoplay(true); // play only on user action
// setPlayerInstance((n) => n + 1); // ensure clean switch / stop previous
// setWatchedIds((prev) =>
// new Set(prev).add(v.videoId),
// );
// }}
// >
// <div className="videoTabContent">
// <div className="thumbnailWrapper">
// <img
// src={v.thumbnail}
// alt=""
// className="videoThumbnail"
// loading="lazy"
// />
// <span
// className="playIcon"
// aria-hidden="true"
// >
// ▶
// </span>
// {/* Duration badge (see section 2) */}
// {v.duration && (
// <span className="durationBadge">
// {v.duration}
// </span>
// )}
// {/* Watched indicator (see section 3) */}
// {watchedIds.has(v.videoId) && (
// <span className="watchedBadge">
// Watched
// </span>
// )}
// </div>
// <span className="videoTitle">
// {v.name}
// </span>
// </div>
// </button>
// </li>
// );
// })}
// </ul>
// </div>
// </div>
// <style jsx>{`
// .videoWrapper {
// position: relative;
// padding-bottom: 56.25%;
// height: 0;
// overflow: hidden;
// border: 1px solid #b1b4b6;
// border-radius: 4px;
// }
// .videoWrapper iframe {
// position: absolute;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// }
// .videoTabButton {
// width: 100%;
// text-align: left;
// background: transparent;
// border: 1px solid #b1b4b6;
// border-radius: 4px;
// padding: 10px 12px;
// cursor: pointer;
// }
// .videoTabButton:hover {
// background: #f3f2f1;
// }
// .videoTabButton.active {
// border-color: #1d70b8;
// box-shadow: inset 0 0 0 1px #1d70b8;
// background: #eef4ff;
// }
// .videoTabContent {
// display: flex;
// gap: 12px;
// align-items: center;
// }
// .videoThumbnail {
// width: 120px;
// aspect-ratio: 16 / 9;
// object-fit: cover;
// border-radius: 4px;
// border: 1px solid #b1b4b6;
// background: #000;
// }
// .videoTitle {
// font-weight: 600;
// }
// @media (max-width: 40.0625em) {
// .videoThumbnail {
// width: 90px;
// }
// }
// .thumbnailWrapper {
// position: relative;
// width: 120px;
// flex-shrink: 0;
// }
// .videoThumbnail {
// width: 100%;
// aspect-ratio: 16 / 9;
// object-fit: cover;
// border-radius: 4px;
// border: 1px solid #b1b4b6;
// background: #000;
// }
// /* ▶ Play icon */
// .playIcon {
// position: absolute;
// inset: 0;
// display: flex;
// align-items: center;
// justify-content: center;
// font-size: 32px;
// color: white;
// text-shadow: 0 0 6px rgba(0, 0, 0, 0.7);
// pointer-events: none;
// }
// /* ⏱ Duration badge */
// .durationBadge {
// position: absolute;
// bottom: 6px;
// right: 6px;
// background: rgba(0, 0, 0, 0.8);
// color: white;
// font-size: 12px;
// padding: 2px 6px;
// border-radius: 3px;
// }
// /* 👁 Watched badge */
// .watchedBadge {
// position: absolute;
// top: 6px;
// left: 6px;
// background: #00703c; /* GOV green */
// color: white;
// font-size: 11px;
// padding: 2px 6px;
// border-radius: 3px;
// }
// .videoTabContent {
// display: flex;
// gap: 12px;
// align-items: center;
// }
// .videoTitle {
// font-weight: 600;
// }
// `}</style>
// </div>
// );
// }
import { useEffect, useMemo, useState } from "react";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
/**
* MediaDetails
* - Renders a YouTube player + a list of videos from mediaObj.value
* - Loads first item by default (no autoplay)
* - Plays only when user selects an item
* - Stops playback when leaving the "case-media" parent tab (pass whichTab)
* - Shows thumbnail with play overlay
* - Optional duration badge (if you supply v.duration)
* - "Watched" badge once a video has been played (session only)
*/
const extractYouTubeId = (input) => {
if (!input) return null;
// Already looks like a YouTube ID?
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
try {
const url = new URL(input);
// youtu.be/<id>
if (url.hostname.includes("youtu.be")) {
return url.pathname.replace("/", "") || null;
}
// youtube.com/watch?v=<id>
const v = url.searchParams.get("v");
if (v) return v;
// youtube.com/embed/<id>
const embedMatch = url.pathname.match(/\/embed\/([^/]+)/);
if (embedMatch?.[1]) return embedMatch[1];
// youtube.com/shorts/<id>
const shortsMatch = url.pathname.match(/\/shorts\/([^/]+)/);
if (shortsMatch?.[1]) return shortsMatch[1];
return null;
} catch {
return null;
}
};
const MediaDetails = ({ mediaObj, whichTab, title, useNoCookie = true }) => {
const { t } = useTranslation();
const router = useRouter();
const mediaArr = mediaObj?.value || [];
const isActive = whichTab ? whichTab === "case-media" : true;
// Build list of playable items (publishable only)
const videoItems = useMemo(() => {
const isWelsh = router.locale === "cy";
return (mediaArr || [])
.filter((x) => x?.pinswg_publishtoweb)
.map((item) => {
const link = isWelsh
? item.pinswg_mediaLinkCY
: item.pinswg_mediaLinkEN;
const videoId = extractYouTubeId(link);
if (!videoId) return null;
return {
key:
item.pinswg_documentid ||
item.pinswg_mediaid ||
`${item.pinswg_name}-${videoId}`,
videoId,
name: item.pinswg_name || "Video",
description: item.pinswg_description || "",
// If you later store duration in CRM, map it here (e.g. "03:42")
duration: item.pinswg_duration || null,
thumbnailHQ: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
thumbnailMax: `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`,
raw: item,
};
})
.filter(Boolean);
}, [mediaArr, router.locale]);
const [selectedIndex, setSelectedIndex] = useState(0);
// Default false so first item loads but does not play.
const [autoplay, setAutoplay] = useState(false);
// Bump this to force iframe remount (hard stop playback).
const [playerInstance, setPlayerInstance] = useState(0);
// Track which videos have been played (session only).
const [watchedIds, setWatchedIds] = useState(() => new Set());
// When list or locale changes: reset selection and do not autoplay
useEffect(() => {
setSelectedIndex(0);
setAutoplay(false);
setPlayerInstance((n) => n + 1);
}, [videoItems.length, router.locale]);
// When leaving the media tab: hard stop playback
useEffect(() => {
if (!isActive) {
setAutoplay(false);
setPlayerInstance((n) => n + 1);
}
}, [isActive]);
const selected = videoItems[selectedIndex] || null;
const domain = useNoCookie
? "https://www.youtube-nocookie.com"
: "https://www.youtube.com";
const embedSrc = selected
? `${domain}/embed/${selected.videoId}?rel=0&modestbranding=1&playsinline=1&autoplay=${
autoplay ? "1" : "0"
}`
: "";
if (!videoItems.length) return null;
return (
<div className="govuk-grid-row">
<div className="card">
<div className="card-body">
<h2 className="govuk-heading-m">{t("case:media-title")}</h2>
{/* Player: unmount when not active (guarantees playback stops) */}
{isActive && (
<div className="videoWrapper govuk-!-margin-bottom-4">
<iframe
key={`${selected?.videoId}-${playerInstance}`}
src={embedSrc}
title={selected?.name || "YouTube video"}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
)}
{/* Selected info */}
{selected && (
<div className="govuk-!-margin-bottom-4">
<div className="govuk-body">
<b>{t("case:event-name-label")}</b>:{" "}
{selected.name}
</div>
{!!selected.description && (
<div className="govuk-body">
<b>{t("case:event-event-name-label")}</b>:{" "}
{selected.description}
</div>
)}
</div>
)}
{/* Video list */}
<h3 className="govuk-heading-s">
{t("case:video-heading")}
</h3>
<ul className="govuk-list govuk-list--spaced">
{videoItems.map((v, idx) => {
const isSelected = idx === selectedIndex;
return (
<li key={v.key}>
<button
type="button"
className={`videoTabButton ${
isSelected ? "active" : ""
}`}
aria-current={
isSelected ? "true" : undefined
}
onClick={() => {
setSelectedIndex(idx);
// Start playback only when user selects
setAutoplay(true);
// Force remount to stop previous + start clean
setPlayerInstance((n) => n + 1);
// Mark watched
setWatchedIds((prev) => {
const next = new Set(prev);
next.add(v.videoId);
return next;
});
}}
>
<div className="videoTabContent">
<div className="thumbnailWrapper">
<img
src={v.thumbnailMax}
onError={(e) => {
// fallback for videos without maxres
e.currentTarget.src =
v.thumbnailHQ;
}}
alt=""
className="videoThumbnail"
loading="lazy"
/>
<span
className="playIcon"
aria-hidden="true"
>
</span>
{!!v.duration && (
<span className="durationBadge">
{v.duration}
</span>
)}
{watchedIds.has(v.videoId) && (
<span className="watchedBadge">
Watched
</span>
)}
</div>
<span className="videoTitle">
{v.name}
</span>
</div>
</button>
</li>
);
})}
</ul>
</div>
</div>
</div>
);
};
export default MediaDetails;
+13 -12
View File
@@ -13,7 +13,7 @@ const PaginationControl = (props) => {
currentPage,
spinnerState,
getDocumentResults,
setCurrentPage,
setCurrentPage
} = props;
let { t } = useTranslation();
@@ -45,7 +45,7 @@ const PaginationControl = (props) => {
const range = {
start: Math.round(currentPage - delta / 2),
end: Math.round(currentPage + delta / 2),
end: Math.round(currentPage + delta / 2)
};
if (range.start - 1 === 1 || range.end + 1 === pageCount) {
@@ -89,7 +89,7 @@ const PaginationControl = (props) => {
<span
className="govuk-body govuk-link govuk-!-font-weight-bold govuk-link--no-underline activePaginationItem leftTextButton"
onClick={() => {
spinnerState(),
(spinnerState(),
setCurrentPage(
props.currentView.currentPage -
1
@@ -99,7 +99,7 @@ const PaginationControl = (props) => {
1,
orderBy,
fieldSort
);
));
}}
>
{t("common:previous-link-button")}
@@ -132,12 +132,12 @@ const PaginationControl = (props) => {
key={i}
onClick={() => {
spinnerState();
setCurrentPage(e),
(setCurrentPage(e),
getDocumentResults(
e,
orderBy,
fieldSort
);
));
}}
>
{e}
@@ -150,7 +150,7 @@ const PaginationControl = (props) => {
<span
className="govuk-body govuk-link govuk-!-font-weight-bold govuk-link--no-underline activePaginationItem rightTextButton"
onClick={() => {
spinnerState(),
(spinnerState(),
setCurrentPage(
props.currentView.currentPage +
1
@@ -160,7 +160,7 @@ const PaginationControl = (props) => {
1,
orderBy,
fieldSort
);
));
}}
>
{t("common:next-link-button")}
@@ -174,8 +174,9 @@ const PaginationControl = (props) => {
</div>
</div>
<div className="govuk-body-s mobilePageCount">
{t("common:pages-label")} {currentPage}{" "}
{t("common:of-label")} {pagesCount}
{t("common:pages-label")}{" "}
{props.currentView.currentPage} {t("common:of-label")}{" "}
{pagesCount}
</div>
</div>
)}
@@ -185,7 +186,7 @@ const PaginationControl = (props) => {
const mapStateToProps = (state) => {
return {
currentView: state.currentView,
currentView: state.currentView
};
};
@@ -193,7 +194,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
}
};
};
@@ -0,0 +1,364 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderCondtionalRadioList,
RenderPickList,
RenderRichMultiline,
shouldDisableButton,
} from "./representationElements";
import { useDropzone } from "react-dropzone";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
} from "../../../store/currentView/action";
const ConsultationAgent = (props) => {
let { t } = useTranslation();
const router = useRouter();
const { locale } = router;
const {
formObj,
appellantApplicantRepresentationArr,
setRepresentationCapacity,
setRepresentationSubmit,
currentView,
setRepFileName,
updateRepresentation,
setSavingStatus,
savingStatus,
setFilesForRepresentations,
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
if (JSON.stringify(formObj) != "{}") {
setRepFileName(formObj.representationForm.values);
let filterFilesList = props.currentView.caseReference.hasOwnProperty(
"repDetails"
)
? props.currentView.caseReference.repDetails.hasOwnProperty(
"filesList"
)
? props.currentView.caseReference.repDetails.filesList.filter(
function (el) {
return el != null;
}
)
: []
: [];
const files = acceptedFiles.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
const required = (value) => (value ? undefined : "Required");
return (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:consultation-from-an-agent-heading"
)}
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t(
"myrepresentations:kind-of-consultation-label"
)}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
optionsArr={
appellantApplicantRepresentationArr
}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{router.locale == "cy"
? formObj.representationForm.values
.representationType == "Questionnaire"
? "Holiadur"
: formObj.representationForm.values
.representationType == "Statement"
? "Datganiad"
: formObj.representationForm.values
.representationType ==
"Final comments"
? "Sylwadau terfynol"
: formObj.representationForm.values
.representationType
: formObj.representationForm.values
.representationType}
</label>
)}
</div>
</div>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
<Field
name="representationOnBehalfOf"
datafieldname="representationOnBehalfOf"
// label={t("myrepresentations:capacity-select-what-capacity-label")}
options={["Yes", "No"]}
id="representationOnBehalfOf"
className="govuk-radios__input"
errorMsg={t(
"myrepresentations:select-an-option-label"
)}
component={RenderCondtionalRadioList}
validate={[required]}
legend={t("case:representation-onbehalfof")}
hint={t(
"myrepresentations:representation-onbehalfof-hint"
)}
/>
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-form-group">
<p className="govuk-body">
{formObj.representationForm.values
.representationType ==
"Consultation Response"
? t(
"myrepresentations:enter-comment-label"
)
: t(
"myrepresentations:consultation-comment-label"
)}
</p>
<fieldset
className="govuk-fieldset"
aria-describedby="commentBox-hint"
>
{formObj.representationForm.values
.representationType ==
"Consultation Response" && (
<div className="govuk-form-group">
<Field
name="representationComments"
id="representationComments"
component={RenderRichMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={
props.name + "-hint"
}
/>
{formObj.representationForm.values
.appealType == "846040004" ? (
<p className="govuk-hint govuk-!-font-size-14 govuk-!-margin-top-5">
{t(
"myrepresentations:publish-policy-label"
)}{" "}
<a
href={t(
"myrepresentations:publish-policy-link"
)}
>
{t(
"myrepresentations:publish-policy-link-label"
)}
</a>{" "}
{t(
"myrepresentations:publish-policy-label-two"
)}
</p>
) : (
<p className="govuk-hint govuk-!-font-size-14 govuk-!-margin-top-5">
{t(
"myrepresentations:publish-policy-label"
)}{" "}
<a
href={t(
"myrepresentations:publish-policy-link"
)}
>
{t(
"myrepresentations:publish-policy-link-label"
)}
</a>{" "}
{t(
"myrepresentations:publish-policy-label-two"
)}
</p>
)}
</div>
)}
<div className="govuk-form-group govuk-!-margin-bottom-9">
<FileUploadField
name={"representationDocuments"}
id={"representationDocuments"}
label={t(
"myrepresentations:add-files-label"
)}
ticketnumber={
props.props.caseReference
}
hint={"sdsd"}
fileList={
props.currentView.fileList
// props.currentView.caseReference.hasOwnProperty(
// "repDetails"
// )
// ? props.currentView.caseReference.repDetails.hasOwnProperty(
// "filesList"
// ) &&
// props.currentView
// .caseReference
// .repDetails.filesList
// .length > 0
// ? props.currentView
// .caseReference
// .repDetails
// .filesList
// : []
// : []
}
setFilesForRepresentations={
setFilesForRepresentations
}
containerID={
props.props.props.accountDetails
.containerID
}
filenamePrefix={props.formObj}
props={props.props.props}
setCurrentReference={
props.setCurrentReference
}
/>
</div>
</fieldset>
</div>{" "}
</div>
</>
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj,
})}
>
{t("common:continue-button")}
</button>
{/* {router.query.state != "edit" && (
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationCapacity",
""
);
}}
className="govuk-link"
>
{t("common:back-link")}
</a>
)} */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)}
</>
);
}
};
const mapDispatchToProps = (dispatch) => ({
updateField: (form, field, newValue) =>
dispatch(change(form, field, newValue)),
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
});
export default connect(
null,
mapDispatchToProps
)(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
})(ConsultationAgent)
);
@@ -0,0 +1,337 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderPickList,
RenderRichMultiline,
shouldDisableButton,
} from "./representationElements";
import { useDropzone } from "react-dropzone";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
} from "../../../store/currentView/action";
const ConsultationAppellant = (props) => {
let { t } = useTranslation();
const router = useRouter();
const { locale } = router;
const {
formObj,
appellantApplicantRepresentationArr,
setRepresentationCapacity,
setRepresentationSubmit,
currentView,
setRepFileName,
updateRepresentation,
setSavingStatus,
savingStatus,
setFilesForRepresentations,
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
console.log(JSON.stringify(formObj) == "{}" ? "empty" : "value");
if (JSON.stringify(formObj) != "{}") {
setRepFileName(formObj.representationForm.values);
let filterFilesList = props.currentView.caseReference.hasOwnProperty(
"repDetails"
)
? props.currentView.caseReference.repDetails.hasOwnProperty(
"filesList"
)
? props.currentView.caseReference.repDetails.filesList.filter(
function (el) {
return el != null;
}
)
: []
: [];
filterFilesList = filterFilesList.filter(
(value, index, self) =>
index === self.findIndex((t) => t.name === value.name)
);
return (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:consultation-from-an-appellant-heading"
)}
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t(
"myrepresentations:kind-of-consultation-label"
)}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
optionsArr={
appellantApplicantRepresentationArr
}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{router.locale == "cy"
? formObj.representationForm.values
.representationType == "Questionnaire"
? "Holiadur"
: formObj.representationForm.values
.representationType == "Statement"
? "Datganiad"
: formObj.representationForm.values
.representationType ==
"Final comments"
? "Sylwadau terfynol"
: formObj.representationForm.values
.representationType
: formObj.representationForm.values
.representationType}
</label>
)}
</div>
</div>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
<div className="govuk-grid-row">
<div className="govuk-form-group">
<p className="govuk-body">
{formObj.representationForm.values
.representationType ==
"Consultation Response"
? t("myrepresentations:enter-comment-label")
: t(
"myrepresentations:consultation-comment-label"
)}
</p>
<fieldset
className="govuk-fieldset"
aria-describedby="commentBox-hint"
>
{formObj.representationForm.values
.representationType ==
"Consultation Response" && (
<div className="govuk-form-group">
<Field
name="representationComments"
id="representationComments"
component={RenderRichMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={
props.name + "-hint"
}
/>
{formObj.representationForm.values
.appealType == "846040004" ? (
<p className="govuk-hint govuk-!-font-size-14 govuk-!-margin-top-5">
{t(
"myrepresentations:publish-policy-label"
)}{" "}
<a
href={t(
"myrepresentations:publish-policy-link"
)}
>
{t(
"myrepresentations:publish-policy-link-label"
)}
</a>{" "}
{t(
"myrepresentations:publish-policy-label-two"
)}
</p>
) : (
<p className="govuk-hint govuk-!-font-size-14 govuk-!-margin-top-5">
{t(
"myrepresentations:publish-policy-label"
)}{" "}
<a
href={t(
"myrepresentations:publish-policy-link"
)}
>
{t(
"myrepresentations:publish-policy-link-label"
)}
</a>{" "}
{t(
"myrepresentations:publish-policy-label-two"
)}
</p>
)}
</div>
)}
<div className="govuk-form-group govuk-!-margin-bottom-9">
<FileUploadField
name={"representationDocuments"}
id={"representationDocuments"}
label={t(
"myrepresentations:add-files-label"
)}
ticketnumber={props.props.caseReference}
hint={"sdsd"}
fileList={
props.currentView.fileList
// props.currentView.caseReference.hasOwnProperty(
// "repDetails"
// )
// ? props.currentView.caseReference.repDetails.hasOwnProperty(
// "filesList"
// ) &&
// props.currentView
// .caseReference.repDetails
// .filesList.length > 0
// ? props.currentView
// .caseReference
// .repDetails.filesList
// : []
// : []
}
setFilesForRepresentations={
setFilesForRepresentations
}
containerID={
props.props.props.accountDetails
.containerID
}
filenamePrefix={props.formObj}
props={props.props.props}
setCurrentReference={
props.setCurrentReference
}
/>
</div>
</fieldset>
</div>{" "}
</div>
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj,
})}
>
{t("common:continue-button")}
</button>
{/* {router.query.state != "edit" && (
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationType",
""
);
}}
className="govuk-link"
>
{t("common:back-link")}
</a>
)} */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)}
</>
);
}
};
//const mapStateToProps = (state, props) => {};
const mapDispatchToProps = (dispatch) => {
return {
updateField: (form, field, newValue) =>
dispatch(change(form, field, newValue)),
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
};
};
export default connect(
null,
mapDispatchToProps
)(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
})(ConsultationAppellant)
);
@@ -0,0 +1,378 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderCondtionalRadioList,
RenderPickList,
RenderRichMultiline,
shouldDisableButton,
} from "./representationElements";
import { useDropzone } from "react-dropzone";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
} from "../../../store/currentView/action";
const ConsultationInterestedPartyPerson = (props) => {
let { t } = useTranslation();
const router = useRouter();
const { locale } = router;
const {
formObj,
interestedPersonRepresentationArr,
setRepresentationCapacity,
setRepresentationSubmit,
setRepFileName,
updateRepresentation,
setSavingStatus,
savingStatus,
setFilesForRepresentations,
} = props;
const required = (value) => (value ? undefined : "Required");
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
if (JSON.stringify(formObj) != "{}") {
setRepFileName(formObj.representationForm.values);
let filterFilesList = props.currentView.caseReference.hasOwnProperty(
"repDetails"
)
? props.currentView.caseReference.repDetails.hasOwnProperty(
"filesList"
)
? props.currentView.caseReference.repDetails.filesList.filter(
function (el) {
return el != null;
}
)
: []
: [];
const files = acceptedFiles.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:consultation-from-an-interested-person-heading"
)}
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t(
"myrepresentations:kind-of-consultation-label"
)}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
validate={[required]}
optionsArr={
interestedPersonRepresentationArr
}
errorMsg={t(
"myrepresentations:select-an-option-label"
)}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{router.locale == "cy"
? formObj.representationForm.values
.representationType == "Questionnaire"
? "Holiadur"
: formObj.representationForm.values
.representationType == "Statement"
? "Datganiad"
: formObj.representationForm.values
.representationType ==
"Final comments"
? "Sylwadau terfynol"
: formObj.representationForm.values
.representationType
: formObj.representationForm.values
.representationType}
</label>
)}
</div>
</div>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
<Field
name="representationOnBehalfOf"
datafieldname="representationOnBehalfOf"
// label="In what capacity do you wish to make representations on this case?"
options={[
t(
"myrepresentations:questionnaire-yes"
),
t("myrepresentations:questionnaire-no"),
]}
id="representationOnBehalfOf"
className="govuk-radios__input"
errorMsg={t(
"myrepresentations:select-an-option-label"
)}
component={RenderCondtionalRadioList}
validate={[required]}
legend={t("case:representation-onbehalfof")}
hint={t(
"myrepresentations:representation-onbehalfof-hint"
)}
/>
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-form-group">
<p className="govuk-body">
{formObj.representationForm.values
.representationType ==
"Consultation Response"
? t(
"myrepresentations:enter-comment-label"
)
: t(
"myrepresentations:consultation-comment-label"
)}
</p>
<fieldset
className="govuk-fieldset"
aria-describedby="commentBox-hint"
>
{" "}
{formObj.representationForm.values
.representationType ==
"Consultation Response" && (
<div className="govuk-form-group">
<Field
name="representationComments"
id="representationComments"
component={RenderRichMultiline}
type="text"
rows="5"
className="govuk-textarea govuk-input--width-30"
aria-describedby={
props.name + "-hint"
}
/>
{formObj.representationForm.values
.appealType == "846040004" ? (
<p className="govuk-hint govuk-!-font-size-14 govuk-!-margin-top-5">
{t(
"myrepresentations:publish-policy-label"
)}{" "}
<a
href={t(
"myrepresentations:publish-policy-link"
)}
>
{t(
"myrepresentations:publish-policy-link-label"
)}
</a>{" "}
{t(
"myrepresentations:publish-policy-label-two"
)}
</p>
) : (
<p className="govuk-hint govuk-!-font-size-14 govuk-!-margin-top-5">
{t(
"myrepresentations:publish-policy-label"
)}{" "}
<a
href={t(
"myrepresentations:publish-policy-link"
)}
>
{t(
"myrepresentations:publish-policy-link-label"
)}
</a>{" "}
{t(
"myrepresentations:publish-policy-label-two"
)}
</p>
)}
</div>
)}
<div className="govuk-form-group govuk-!-margin-bottom-9">
<FileUploadField
name={"representationDocuments"}
id={"representationDocuments"}
label={t(
"myrepresentations:add-files-label"
)}
ticketnumber={
props.props.caseReference
}
hint={"sdsd"}
fileList={
props.currentView.fileList
// props.currentView.caseReference.hasOwnProperty(
// "repDetails"
// )
// ? props.currentView.caseReference.repDetails.hasOwnProperty(
// "filesList"
// ) &&
// props.currentView
// .caseReference
// .repDetails.filesList
// .length > 0
// ? props.currentView
// .caseReference
// .repDetails
// .filesList
// : []
// : []
}
setFilesForRepresentations={
setFilesForRepresentations
}
containerID={
props.props.props.accountDetails
.containerID
}
filenamePrefix={props.formObj}
props={props.props.props}
setCurrentReference={
props.setCurrentReference
}
/>
</div>
</fieldset>
</div>{" "}
</div>
</>
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj,
})}
>
{t("common:continue-button")}
</button>
{/*
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationCapacity",
""
);
props.updateField(
"representationForm",
"representationType",
""
);
}}
className="govuk-link"
>
{t("case:summary-back-button-label")}
</a> */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)}
</>
);
}
};
//const mapStateToProps = (state, props) => {};
const mapDispatchToProps = (dispatch) => ({
updateField: (form, field, newValue) =>
dispatch(change(form, field, newValue)),
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
});
export default connect(
null,
mapDispatchToProps
)(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
})(ConsultationInterestedPartyPerson)
);
@@ -0,0 +1,42 @@
const RepresentationActionButtons = ({
t,
locale,
isSaving,
isContinueDisabled,
onSaveExit
}) => {
return (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={isContinueDisabled}
>
{t("common:continue-button")}
</button>
{isSaving ? (
<span className=" progress-save-active">
{locale == "cy" ? "Nôl data" : "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={locale == "cy" ? "Nôl data" : "Saving data"}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={onSaveExit}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
);
};
export default RepresentationActionButtons;
@@ -0,0 +1,49 @@
import { Field } from "redux-form";
import { RenderPickList } from "../representationElements";
const RepresentationTypeSelectorBlock = ({
t,
headingText,
kindLabelText,
isTypeSelected,
selectedTypeDisplay,
optionsArr,
validate,
errorMsg
}) => {
return (
<div className="govuk-grid-row">
<div className="govuk-form-group">
{!isTypeSelected ? (
<>
<h2 className="govuk-heading-m ">{headingText}</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{kindLabelText}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
validate={validate}
optionsArr={optionsArr}
errorMsg={errorMsg}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{selectedTypeDisplay}
</label>
)}
</div>
</div>
);
};
export default RepresentationTypeSelectorBlock;
File diff suppressed because it is too large Load Diff
@@ -8,15 +8,17 @@ import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderCondtionalRadioList,
RenderPickList,
RenderRichMultiline,
shouldDisableButton
} from "./representationElements";
import RepresentationActionButtons from "./elements/RepresentationActionButtons";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import { useDropzone } from "react-dropzone";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
setFilesForRepresentations
} from "../../../store/currentView/action";
const RepAgent = (props) => {
@@ -34,7 +36,7 @@ const RepAgent = (props) => {
updateRepresentation,
setSavingStatus,
savingStatus,
setFilesForRepresentations,
setFilesForRepresentations
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
@@ -62,58 +64,44 @@ const RepAgent = (props) => {
));
const required = (value) => (value ? undefined : "Required");
return (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:representation-from-an-agent-heading"
)}
</h2>
const hasTypeSelectionInline =
typeof formObj.representationForm.values.representationType ==
"undefined" && props.representationType != "Select...";
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t("myrepresentations:kind-of-rep-label")}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
optionsArr={
appellantApplicantRepresentationArr
}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{router.locale == "cy"
? formObj.representationForm.values
.representationType == "Questionnaire"
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? formObj.representationForm.values.representationType ==
"Questionnaire"
? "Holiadur"
: formObj.representationForm.values
.representationType == "Statement"
: formObj.representationForm.values.representationType ==
"Statement"
? "Datganiad"
: formObj.representationForm.values
.representationType ==
: formObj.representationForm.values.representationType ==
"Final comments"
? "Sylwadau terfynol"
: formObj.representationForm.values
.representationType
: formObj.representationForm.values
.representationType}
</label>
: formObj.representationForm.values.representationType
: formObj.representationForm.values.representationType;
const handleSaveExit = () => {
let valuesObj = props.props.props.form[props.form].values || {};
delete valuesObj["_pinswg_appellant_value"];
updateRepresentation(valuesObj, false, true);
};
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-agent-heading"
)}
</div>
</div>
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={!hasTypeSelectionInline}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
optionsArr={appellantApplicantRepresentationArr}
/>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
@@ -241,72 +229,16 @@ const RepAgent = (props) => {
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
{/* {router.query.state != "edit" && (
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationCapacity",
""
);
}}
className="govuk-link"
>
{t("common:back-link")}
</a>
)} */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)}
</>
);
@@ -319,7 +251,7 @@ const mapDispatchToProps = (dispatch) => ({
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
});
export default connect(
@@ -329,6 +261,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RepAgent)
);
@@ -9,13 +9,16 @@ import {
FileUploadField,
RenderPickList,
RenderRichMultiline,
shouldDisableButton
} from "./representationElements";
import RepresentationActionButtons from "./elements/RepresentationActionButtons";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import { useDropzone } from "react-dropzone";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
setFilesForRepresentations
} from "../../../store/currentView/action";
const RepAppellant = (props) => {
@@ -33,7 +36,7 @@ const RepAppellant = (props) => {
updateRepresentation,
setSavingStatus,
savingStatus,
setFilesForRepresentations,
setFilesForRepresentations
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
@@ -61,56 +64,44 @@ const RepAppellant = (props) => {
index === self.findIndex((t) => t.name === value.name)
);
return (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:representation-from-an-appellant-heading"
)}
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t("myrepresentations:kind-of-rep-label")}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
optionsArr={
appellantApplicantRepresentationArr
}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{router.locale == "cy"
? formObj.representationForm.values
.representationType == "Questionnaire"
const hasTypeSelectionInline =
typeof formObj.representationForm.values.representationType ==
"undefined" && props.representationType != "Select...";
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? formObj.representationForm.values.representationType ==
"Questionnaire"
? "Holiadur"
: formObj.representationForm.values
.representationType == "Statement"
: formObj.representationForm.values.representationType ==
"Statement"
? "Datganiad"
: formObj.representationForm.values
.representationType ==
: formObj.representationForm.values.representationType ==
"Final comments"
? "Sylwadau terfynol"
: formObj.representationForm.values
.representationType
: formObj.representationForm.values
.representationType}
</label>
: formObj.representationForm.values.representationType
: formObj.representationForm.values.representationType;
const handleSaveExit = () => {
let valuesObj = props.props.props.form[props.form].values || {};
delete valuesObj["_pinswg_appellant_value"];
updateRepresentation(valuesObj, false, true);
};
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-appellant-heading"
)}
</div>
</div>
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={!hasTypeSelectionInline}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
optionsArr={appellantApplicantRepresentationArr}
/>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
@@ -228,75 +219,19 @@ const RepAppellant = (props) => {
</div>{" "}
</div>
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
{/* {router.query.state != "edit" && (
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationType",
""
);
}}
className="govuk-link"
>
{t("common:back-link")}
</a>
)} */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)}
</>
);
@@ -312,7 +247,7 @@ const mapDispatchToProps = (dispatch) => {
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
};
};
@@ -323,6 +258,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RepAppellant)
);
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { useDropzone } from "react-dropzone";
import { Field } from "redux-form";
import RepDetails from "./representationDetails";
import { RenderRadioListWithText } from "./representationElements";
import { RenderRadioList } from "./representationElements";
let RepCapacitySelection = (props) => {
let { t } = useTranslation();
@@ -17,7 +17,7 @@ let RepCapacitySelection = (props) => {
casesObj,
validate,
handleSubmit,
setCurrentReference,
setCurrentReference
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const files = acceptedFiles.map((file) => (
@@ -27,6 +27,7 @@ let RepCapacitySelection = (props) => {
));
const required = (value) => (value ? undefined : "Required");
const appealType = props.props.currentView.caseReference.appealType;
return (
<>
@@ -42,6 +43,7 @@ let RepCapacitySelection = (props) => {
myRepresentations={props.props.myRepresentations}
props={props}
setCurrentReference={setCurrentReference}
appealType={appealType}
/>{" "}
<div id="rep-details" className="">
<div className="govuk-grid-column-full">
@@ -50,21 +52,27 @@ let RepCapacitySelection = (props) => {
{t(
"myrepresentations:capacity-select-your-details"
)}{" "}
- {props.firstValue}
- {props.firstValue}{" "}
</h2>
<Field
name="representationCapacity"
datafieldname="representationCapacity"
label={t(
label={
appealType != 846040002
? t(
"myrepresentations:capacity-select-what-capacity-label"
)}
)
: t(
"myrepresentations:sips-capacity-select-what-capacity-label"
)
}
options={capacityOptionsArr}
id="representationCapacity"
className="govuk-radios__input"
errorMsg={t(
"myrepresentations:select-an-option-label"
)}
component={RenderRadioListWithText}
component={RenderRadioList}
validate={[required]}
/>
</div>
@@ -7,10 +7,10 @@ import { connect } from "react-redux";
import { useDropzone } from "react-dropzone";
import {
createWatchedCases,
sendEmail,
sendRepCompleteMessage,
setRepInvolvment,
} from "../../../actions";
setRepInvolvment
} from "../../../actions/services/portalService";
import { sendEmail } from "../../../actions/services/notifyService";
import { setRepresentationReset } from "../../../store/currentView/action";
const RepComplete = (props) => {
@@ -27,7 +27,7 @@ const RepComplete = (props) => {
setSubmitBack,
setRepresentationSubmitConfirmation,
setRepresentationMessageSent,
repFormData,
repFormData
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const files = acceptedFiles.map((file) => (
@@ -40,11 +40,16 @@ const RepComplete = (props) => {
props.props.currentView.caseReference.appealType === 846040002;
useEffect(() => {
const isWelsh = router.locale === "cy";
const reference = isSIPS
? "PEDW-CONSULTATION-COMPLETE"
: "PEDW-REPRESENTATION-COMPLETE";
? "PEDW-CONSULTATION-COMPLETE" + isWelsh && "-CY"
: "PEDW-REPRESENTATION-COMPLETE" + isWelsh && "-CY";
const templateId = isSIPS
? "c3f373ca-df9d-4967-bea0-3ad3a57f8dcc"
? isWelsh
? "7d27b968-6f63-4334-b1c4-2e4a69554212"
: "c3f373ca-df9d-4967-bea0-3ad3a57f8dcc"
: isWelsh
? "6e2f0111-b352-4074-91a2-760403cd185b"
: "c49785e7-3217-40b6-b33f-dffc4e58d5c1";
const emailAddress =
@@ -55,10 +60,10 @@ const RepComplete = (props) => {
"emailAddress":
props.props.props.props.accountDetails.accountDetails
.emailaddress1,
"linkExpiry": 10 * 60,
"linkExpiry": 10 * 60
};
props.props.currentView.representationMessageSent != true &&
(props.props.currentView.representationMessageSent != true &&
(setRepInvolvment(
props.props.currentView.caseReference.incidentid,
props.props.props.props.accountDetails.accountDetails.contactid
@@ -89,10 +94,10 @@ const RepComplete = (props) => {
? props.props.currentView.caseReference.repDetails
.representationType
: props.props.currentView.caseReference
.representationType,
.representationType
}),
setRepresentationMessageSent(true)),
window.scrollTo({ top: 0, behavior: "smooth" });
window.scrollTo({ top: 0, behavior: "smooth" }));
});
return (
<div id="rep-appellant">
@@ -104,6 +109,7 @@ const RepComplete = (props) => {
<p>{t("myrepresentations:rep-complete-para-one")}</p>
<p>{t("myrepresentations:rep-complete-para-two")}</p>
<p> {t("myrepresentations:rep-complete-para-three")}</p>
<p> {t("myrepresentations:rep-complete-para-four")}</p>
</div>
<div className="govuk-grid-row">
<div className="govuk-button-group">
@@ -130,7 +136,7 @@ const mapStateToProps = (state) => {
currentView: state.currentView,
myRepresentations: state.myRepresentations,
initialValues: state.currentView.caseReference.repDetails,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useDropzone } from "react-dropzone";
import { connect } from "react-redux";
import { generateRepPDF } from "../../../actions/services/documentService";
import RepComplete from "./representationComplete";
import RepLPAQuestionnaireCheck_enforcement from "./representationQuestionnairesCheck/representationLPAQuestionnaireCheck_enforcement";
import RepLPAQuestionnaireCheck_s78 from "./representationQuestionnairesCheck/representationLPAQuestionnaireCheck_s78";
@@ -13,16 +14,19 @@ import {
getThumbnailIconByExtension,
formatDates,
updateLinks,
getDocLink
} from "../../utils";
import {
setCurrentReference,
setCurrentView,
setCurrentView
} from "../../../store/currentView/action";
const RepCompleteSubmit = (props) => {
let { t } = useTranslation();
const [confirmSections, setConfirmSections] = useState(false);
const [downloadQuestionnaire, setDownloadQuestionnaire] = useState(false);
const [downloadInProgress, setDownloadInProgress] = useState(false);
const router = useRouter();
const { locale } = router;
@@ -41,7 +45,11 @@ const RepCompleteSubmit = (props) => {
questionnaireCount,
finaliseAppealProcess,
setCurrentReference,
docsOffline
} = props;
let ShowDocLinks = getDocLink(docsOffline);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const files = acceptedFiles.map((file) => (
<li key={file.path}>
@@ -53,6 +61,13 @@ const RepCompleteSubmit = (props) => {
repFormData = updateLinks(repFormData);
const representationOnBehalfOfDisplay =
repFormData.representationOnBehalfOf === "Yes"
? t("myrepresentations:questionnaire-yes")
: repFormData.representationOnBehalfOf === "No"
? t("myrepresentations:questionnaire-no")
: repFormData.representationOnBehalfOf;
//const repDate = new Date();
// let day = repDate.getDate();
@@ -164,7 +179,7 @@ const RepCompleteSubmit = (props) => {
typeof filesUploadObj[key].name != "undefined" &&
buildAppealFilesArray.push({
"name": filesUploadObj[key].name,
"size": filesUploadObj[key].contentLength,
"size": filesUploadObj[key].contentLength
});
}
@@ -180,7 +195,7 @@ const RepCompleteSubmit = (props) => {
// : [];
let arrUniq = [
...new Map(buildAppealFilesArray.map((v) => [v.name, v])).values(),
...new Map(buildAppealFilesArray.map((v) => [v.name, v])).values()
];
buildAppealFilesArray = buildAppealFilesArray.filter(function (el) {
@@ -212,7 +227,7 @@ const RepCompleteSubmit = (props) => {
arrUniq = removeEmptyObjects(arrUniq);
Object.assign(formObj.representationForm.values, {
"filesList": arrUniq,
"filesList": arrUniq
});
let filterFiles = [];
@@ -249,6 +264,64 @@ const RepCompleteSubmit = (props) => {
}
};
const triggerQuestionnaireDownload = async () => {
if (
repFormData.representationType !== "Questionnaire" ||
!downloadQuestionnaire
) {
return;
}
if (downloadInProgress) {
return;
}
try {
setDownloadInProgress(true);
const containerID =
props.accountDetails?.containerID ||
props.props?.accountDetails?.containerID ||
props.props?.props?.accountDetails?.containerID;
const caseRef =
currentView?.caseReference?.ticketnumber ||
repFormData?.caseRef ||
repFormData?.ticketnumber;
if (!containerID || !caseRef) {
console.error(
"Questionnaire download skipped: missing containerID or case reference",
{ containerID, caseRef }
);
return;
}
repFormData.locale = router.locale == "cy" ? "cy" : "en";
const pdfBlob = await generateRepPDF(
repFormData,
containerID,
caseRef,
{ download: true }
);
if (pdfBlob) {
const blobUrl = window.URL.createObjectURL(pdfBlob);
const anchor = document.createElement("a");
anchor.href = blobUrl;
anchor.download = `${repFormData.repfile_name || "questionnaire"}.pdf`;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
window.URL.revokeObjectURL(blobUrl);
}
} catch (error) {
console.error("Questionnaire download failed", error);
} finally {
setDownloadInProgress(false);
}
};
return (
<>
{currentView.representationSubmitConfirmation == true ? (
@@ -264,19 +337,31 @@ const RepCompleteSubmit = (props) => {
<div className="govuk-grid-row govuk-body">
<h1 className="govuk-heading-m ">
{" "}
{t(
{repFormData.appealType == 846040002
? t(
"myrepresentations:consultation-check-submit-heading"
)
: t(
"myrepresentations:rep-check-submit-heading"
)}
</h1>
<dl className="govuk-summary-list ">
<h2 className="govuk-heading-m govuk-!-margin-top-9">
{t(
{repFormData.appealType == 846040002
? t(
"myrepresentations:consultation-check-your-rep-heading"
)
: t(
"myrepresentations:rep-check-your-rep-heading"
)}
</h2>
<div className="govuk-summary-list__row">
<dt className="govuk-summary-list__key">
{t(
{repFormData.appealType == 846040002
? t(
"case:consultation-representation-type"
)
: t(
"case:representation-representation-type"
)}
</dt>
@@ -328,7 +413,7 @@ const RepCompleteSubmit = (props) => {
<dd className="govuk-summary-list__value">
{
repFormData.representationOnBehalfOf
representationOnBehalfOfDisplay
}
</dd>
@@ -350,7 +435,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
});
setRepresentationSubmit(
false
@@ -403,7 +488,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
}
);
setRepresentationSubmit(
@@ -427,17 +512,20 @@ const RepCompleteSubmit = (props) => {
</>
)}
{repFormData.representationType !=
"Questionnaire" && (
"Questionnaire" &&
repFormData.appealType != 846040002 && (
<div className="govuk-summary-list__row">
<dt className="govuk-summary-list__key">
{t("case:representation-comments")}
{t(
"case:representation-comments"
)}
</dt>
<dd className="govuk-summary-list__value">
<span
className="multiline-elipsis"
dangerouslySetInnerHTML={{
__html: repFormData.representationComments,
__html: repFormData.representationComments
}}
/>
</dd>
@@ -461,14 +549,16 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
});
setRepresentationSubmit(
false
);
}}
>
{t("common:change-link-label")}
{t(
"common:change-link-label"
)}
<span className="govuk-visually-hidden">
{" "}
{/* {FieldsTranslations(
@@ -539,7 +629,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
}),
setRepresentationSubmit(
false
@@ -586,6 +676,8 @@ const RepCompleteSubmit = (props) => {
"myrepresentations:rep-check-tandc-para-three"
)}
</p>
<p></p>
{checkRepDatePassed() ? (
<div className="govuk-warning-text noticebanner">
<strong className="govuk-warning-text__text">
@@ -624,8 +716,63 @@ const RepCompleteSubmit = (props) => {
{t("myrepresentations:rep-wait-label")}
</p>
</div>
) : ShowDocLinks ? (
<>
{" "}
<div className="govuk-warning-text ">
<span
className="govuk-warning-text__icon"
aria-hidden="true"
>
!
</span>
<strong className="govuk-warning-text__text govuk-!-margin-top-2">
<span className="govuk-visually-hidden">
Notice
</span>
{t(
"myrepresentations:docsoffline-reps-label"
)}
{" "}
</strong>
</div>{" "}
<Link className="govuk-link" href="/">
{t(
"myrepresentations:return-to-myportal-link"
)}
</Link>
</>
) : (
<>
{repFormData.representationType ==
"Questionnaire" && (
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
<input
name="pinswg_downloadQuestionnaire"
id="pinswg_downloadQuestionnaire"
type="checkbox"
className="govuk-checkboxes__input"
checked={
downloadQuestionnaire
}
onChange={(e) => {
setDownloadQuestionnaire(
e.target.checked
);
}}
/>
<label
className="govuk-label govuk-checkboxes__label"
htmlFor="pinswg_downloadQuestionnaire"
>
{t(
"myrepresentations:rep-check-tandc-para-five"
)}
</label>
</div>
</div>
)}
<div className="govuk-form-group">
<div className="govuk-checkboxes govuk-checkboxes--small">
<div className="govuk-checkboxes__item">
@@ -664,10 +811,11 @@ const RepCompleteSubmit = (props) => {
type="submit"
className="govuk-button"
data-module="govuk-button"
onClick={() => {
onClick={async () => {
// setRepresentationSubmitConfirmation(
// true
// );
await triggerQuestionnaireDownload();
setRepresentationSubmit(
true
);
@@ -704,7 +852,7 @@ const mapStateToProps = (state) => {
currentView: state.currentView,
myRepresentations: state.myRepresentations,
initialValues: state.currentView.caseReference.repDetails,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -713,7 +861,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentReference: (currentReference) => {
dispatch(setCurrentReference(currentReference));
},
}
};
};
@@ -8,7 +8,7 @@ const RepDetails = (props) => {
const router = useRouter();
const { locale } = router;
const { currentType, casesObj } = props;
const { currentType, casesObj, appealType } = props;
let { caseReference, ticketnumber } = props;
@@ -97,10 +97,14 @@ const RepDetails = (props) => {
</div>
<div className="govuk-grid-row">
<p className="govuk-body">
{t("myrepresentations:capacity-para-one")}
{appealType == 846040002
? t("myrepresentations:sips-capacity-para-one")
: t("myrepresentations:capacity-para-one")}
</p>
<p className="govuk-body">
{t("myrepresentations:capacity-para-two")}
{appealType == 846040002
? t("myrepresentations:sips-capacity-para-two")
: t("myrepresentations:capacity-para-two")}
</p>
</div>
</div>
@@ -13,8 +13,8 @@ import {
deleteBlob,
deleteRepBlob,
getFilesFromBlobHashed,
getFilesFromBlobproxy,
} from "../../../actions";
getFilesFromBlobproxy
} from "../../../actions/services/documentService";
const LoadingMessage = () => <div>Loading the editor...</div>;
@@ -23,7 +23,7 @@ const ReactQuill = dynamic(
() => import("react-quill-new").then((mod) => mod.default),
{
ssr: false,
loading: () => <LoadingMessage />, // This is valid as long as it's a valid React component
loading: () => <LoadingMessage /> // This is valid as long as it's a valid React component
}
);
import { useDispatch } from "react-redux"; // Import the useDispatch hook
@@ -35,13 +35,13 @@ import {
parseISO,
subDays,
subMonths,
subYears,
subYears
} from "date-fns";
import {
bytesToSize,
getThumbnailIconByExtension,
updateLinks,
updateLinks
} from "../../utils";
import { useRouter } from "next/router";
@@ -258,6 +258,26 @@ export const RenderCondtionalRadioList = ({
}) => {
const required = (value) => (value ? undefined : "Required");
let { t } = useTranslation();
const yesValue = t("myrepresentations:questionnaire-yes");
const noValue = t("myrepresentations:questionnaire-no");
const normalizeYesNo = (rawValue) => {
if (rawValue === true) return "Yes";
if (rawValue === false) return "No";
if (rawValue === "Yes" || rawValue === yesValue) return "Yes";
if (rawValue === "No" || rawValue === noValue) return "No";
return rawValue;
};
const isYesSelected = normalizeYesNo(value) === "Yes";
const requiredIfYes = (fieldValue, allValues) => {
const selectedValue = normalizeYesNo(allValues?.[id]);
return selectedValue === "Yes" && !String(fieldValue || "").trim()
? "Required"
: undefined;
};
return (
<>
<div
@@ -304,8 +324,16 @@ export const RenderCondtionalRadioList = ({
{custom.hint}
</div>
<div className="govuk-radios">
{Object.keys(options).map((key, index) => (
<div key={key} className="govuk-!-margin-bottom-3">
{Object.keys(options).map((key, index) => {
const normalizedOptionValue = normalizeYesNo(
options[key]
);
return (
<div
key={key}
className="govuk-!-margin-bottom-3"
>
<div
className="govuk-radios__item"
data-children-count={key}
@@ -317,7 +345,7 @@ export const RenderCondtionalRadioList = ({
component="input"
type="radio"
className="govuk-radios__input"
value={options[key]}
value={normalizedOptionValue}
validate={[required]}
/>
<label
@@ -333,7 +361,8 @@ export const RenderCondtionalRadioList = ({
)}
</label>
</div>
{value == "Yes" && options[key] == "Yes" && (
{isYesSelected &&
normalizedOptionValue === "Yes" && (
<div
className="govuk-radios__conditional "
id={"conditional-" + id}
@@ -344,7 +373,9 @@ export const RenderCondtionalRadioList = ({
>
<label
className="govuk-label"
htmlFor={id + "_details"}
htmlFor={
id + "_details"
}
>
{t(
"myrepresentations:representation-onbehalfof-label"
@@ -354,9 +385,13 @@ export const RenderCondtionalRadioList = ({
className="govuk-input govuk-!-width-one-half"
id={id + "_details"}
name={id + "_details"}
component={RenderTextfield}
component={
RenderTextfield
}
type="text"
validate={[required]}
validate={[
requiredIfYes
]}
errorMsg={t(
"myrepresentations:is-required-label"
)}
@@ -365,7 +400,8 @@ export const RenderCondtionalRadioList = ({
</div>
)}
</div>
))}
);
})}
</div>
</fieldset>
</div>
@@ -546,7 +582,8 @@ export const RenderPickList = ({
? "Sylwadau ysgrifenedig"
: optionsArr[key] == "Inquiry"
? "Ymchwiliad"
: optionsArr[key] == "Hearing"
: optionsArr[key] ==
"Hearing"
? "Gwrandawiad"
: optionsArr[key]
: optionsArr[key]}
@@ -734,13 +771,19 @@ export const RenderMultiline = ({
input.onChange(valStr);
};
const handleBlur = () => {
const updated = updateLinks(editValue);
setEditValue(updated);
input.onChange(updated);
};
var modules = {
toolbar: [
[{ "header": [1, 2, false] }],
["bold", "italic", "underline", "blockquote"],
[{ "list": "ordered" }, { "list": "bullet" }],
["clean"],
],
["clean"]
]
};
return (
@@ -764,6 +807,7 @@ export const RenderMultiline = ({
theme="snow"
value={editValue}
onChange={changeEdit}
onBlur={handleBlur}
/>
<textarea
{...input}
@@ -795,18 +839,23 @@ export const RenderRichMultiline = ({
);
const changeEdit = (valStr) => {
valStr = updateLinks(valStr);
setEditValue(valStr);
input.onChange(valStr);
};
const handleBlur = () => {
const updated = updateLinks(editValue);
setEditValue(updated);
input.onChange(updated);
};
var modules = {
toolbar: [
[{ "header": [1, 2, false] }],
["bold", "italic", "underline", "blockquote"],
[{ "list": "ordered" }, { "list": "bullet" }],
["clean"],
],
["clean"]
]
};
return (
@@ -819,6 +868,7 @@ export const RenderRichMultiline = ({
theme="snow"
value={editValue}
onChange={changeEdit}
onBlur={handleBlur}
/>
<textarea
{...input}
@@ -874,19 +924,19 @@ const baseStyle = {
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "#bdbdbd",
transition: "border .3s ease-in-out",
transition: "border .3s ease-in-out"
};
const activeStyle = {
borderColor: "#2196f3",
borderColor: "#2196f3"
};
const acceptStyle = {
borderColor: "#00e676",
borderColor: "#00e676"
};
const rejectStyle = {
borderColor: "#ff1744",
borderColor: "#ff1744"
};
export const RenderFileUpload = (field) => {
@@ -897,7 +947,7 @@ export const RenderFileUpload = (field) => {
const style = useMemo(
() => ({
...baseStyle,
...baseStyle
// ...(isDragActive ? activeStyle : {}),
// ...(isDragAccept ? acceptStyle : {}),
// ...(isDragReject ? rejectStyle : {}),
@@ -993,7 +1043,7 @@ export const RenderFileUpload = (field) => {
typeof filesUploadObj[key].name != "undefined" &&
buildAppealFilesArray.push({
"name": filesUploadObj[key].name,
"size": filesUploadObj[key].size,
"size": filesUploadObj[key].size
});
}
@@ -1042,9 +1092,12 @@ export const RenderFileUpload = (field) => {
});
};
const [errorMessage, setErrorMessage] = useState(null);
const [rejectedFiles, setRejectedFiles] = useState([]); // Store rejected files
const [completedUploadFiles, setCompletedUploadFiles] = useState(false);
const [uploadCountMessage, setUploadCountMessage] = useState("");
const [totalUploadFiles, setTotalUploadFiles] = useState(0);
const handleDropRejected = (fileRejections) => {
const errors = fileRejections
@@ -1060,6 +1113,15 @@ export const RenderFileUpload = (field) => {
"myrepresentations:fileupload-file-error-invalid-type-label"
)}`;
}
if (error.code === "filename-invalid-chars") {
return (
error.message ||
`${file.name} ${t(
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
)}`
);
}
return `${file.name} ${t(
"myrepresentations:fileupload-file-error-invalid-label"
)}`;
@@ -1076,6 +1138,44 @@ export const RenderFileUpload = (field) => {
setRejectedFiles([]); // Clear any previously rejected file errorsss
};
const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-:()—']+$/;
const validateFilename = (file, t) => {
const name = file.name;
// block #
if (name.includes("#")) {
return {
code: "filename-invalid-chars",
message: `${name} ${t(
"myrepresentations:fileupload-file-error-invalid-filename-label"
)}`
};
}
// broader blocklist (optional but recommended)
if (!FILENAME_ALLOWED.test(name)) {
return {
code: "filename-invalid-chars",
message: `${name} ${t(
"myrepresentations:fileupload-file-error-invalid-filename-label"
)}`
};
}
// block characters < > " / \ | ? *
if (/[<>"/\\|?*]/.test(name)) {
return {
code: "filename-invalid-chars",
message: `${name} ${t(
"myrepresentations:fileupload-file-error-invalid-filename-label"
)}`
};
}
return null; // valid
};
return (
<>
<Dropzone
@@ -1089,11 +1189,30 @@ export const RenderFileUpload = (field) => {
"image/jpeg": [".jpg", ".jpeg"],
"image/png": [".png"],
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
[".xlsx"],
[".xlsx"]
}}
onDrop={(filesToUpload, e) => {
validator={(file) => validateFilename(file, t)}
onDrop={(acceptedFiles, fileRejections, e) => {
// build + display rejected messages (ALWAYS, even if some accepted)
if (fileRejections?.length) {
handleDropRejected(fileRejections);
} else {
// only clear rejections when nothing was rejected on this drop
setRejectedFiles([]);
setErrorMessage(null);
}
// if nothing accepted, stop (prevents showing only "uploading" when all invalid)
if (!acceptedFiles || acceptedFiles.length === 0) {
setCompletedUploadFiles(true); // optional: depends on your UX
setUploadCountMessage(0);
setTotalUploadFiles(0);
return;
}
setCompletedUploadFiles(false);
const renamedAcceptedFiles = filesToUpload.map(
const renamedAcceptedFiles = acceptedFiles.map(
(file) =>
new File(
[file],
@@ -1111,12 +1230,22 @@ export const RenderFileUpload = (field) => {
.values.repfile_name
}_-_Attachment_-_${file.name}`,
{
type: file.type,
type: file.type
}
)
);
field.input.onChange(renamedAcceptedFiles);
setUploadCountMessage(renamedAcceptedFiles.length);
if (typeof field.setFileCount === "function") {
field.setFileCount(
(field.uploadCount || 0) +
renamedAcceptedFiles.length
);
}
setUploadCountMessage(0);
setTotalUploadFiles(renamedAcceptedFiles.length);
uploadSingleFile(
renamedAcceptedFiles,
@@ -1124,7 +1253,12 @@ export const RenderFileUpload = (field) => {
field.ticketnumber +
"/" +
field.filenamePrefix.representationForm.values
.repfile_name
.repfile_name,
{
onChunkComplete: ({ cumulativeUploaded }) => {
setUploadCountMessage(cumulativeUploaded);
}
}
).then((data) => {
console.log(data);
let buildAppealFilesArray = [];
@@ -1135,7 +1269,7 @@ export const RenderFileUpload = (field) => {
typeof filesUploadObj[key].name != "undefined" &&
buildAppealFilesArray.push({
"name": filesUploadObj[key].name,
"size": filesUploadObj[key].size,
"size": filesUploadObj[key].size
});
}
@@ -1196,8 +1330,6 @@ export const RenderFileUpload = (field) => {
});
});
}}
onDropRejected={handleDropRejected}
onDropAccepted={handleDropAccepted}
>
{({ getRootProps, getInputProps }) => (
<>
@@ -1224,7 +1356,7 @@ export const RenderFileUpload = (field) => {
</>
)}
</Dropzone>
{rejectedFiles.length > 0 && completedUploadFiles == true && (
{rejectedFiles.length > 0 && (
<div
style={{ color: "red", marginTop: "10px" }}
className="govuk-body govuk-!-font-size-14 "
@@ -1236,17 +1368,20 @@ export const RenderFileUpload = (field) => {
</ul>
</div>
)}
{uploadCountMessage > 0 && completedUploadFiles == false && (
{totalUploadFiles > 0 && completedUploadFiles == false && (
<div className="govuk-body govuk-!-font-size-14">
{t("home:uploading-files-label", {
number: uploadCountMessage,
number:
totalUploadFiles > 0
? `${uploadCountMessage} of ${totalUploadFiles}`
: uploadCountMessage
})}
</div>
)}
{completedUploadFiles > 0 && uploadCountMessage >= 0 && (
<div className="govuk-body govuk-!-font-size-14">
{t("home:completed-uploading-files-label", {
number: uploadCountMessage,
number: uploadCountMessage
})}
</div>
)}
@@ -1385,6 +1520,12 @@ export function FileUploadField(props) {
);
}
export const shouldDisableButton = ({ props, formObj }) =>
!(
props?.currentView?.fileList?.length > 0 ||
typeof formObj?.representationForm?.values?.representationComments !==
"undefined"
);
// export const RepresentationGuidanceText = (appealType) => {
// let { t } = useTranslation();
@@ -8,15 +8,17 @@ import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderCondtionalRadioList,
RenderPickList,
RenderRichMultiline,
shouldDisableButton
} from "./representationElements";
import RepresentationActionButtons from "./elements/RepresentationActionButtons";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import { useDropzone } from "react-dropzone";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
setFilesForRepresentations
} from "../../../store/currentView/action";
const RepInterestedPartyPerson = (props) => {
@@ -33,7 +35,7 @@ const RepInterestedPartyPerson = (props) => {
updateRepresentation,
setSavingStatus,
savingStatus,
setFilesForRepresentations,
setFilesForRepresentations
} = props;
const required = (value) => (value ? undefined : "Required");
@@ -61,62 +63,47 @@ const RepInterestedPartyPerson = (props) => {
{file.path} - {file.size} bytes
</li>
));
return (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:representation-from-an-interested-person-heading"
)}
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t("myrepresentations:kind-of-rep-label")}
</label>
const hasTypeSelectionInline =
typeof formObj.representationForm.values.representationType ==
"undefined" && props.representationType != "Select...";
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
validate={[required]}
optionsArr={
interestedPersonRepresentationArr
}
errorMsg={t(
"myrepresentations:select-an-option-label"
)}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{router.locale == "cy"
? formObj.representationForm.values
.representationType == "Questionnaire"
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? formObj.representationForm.values.representationType ==
"Questionnaire"
? "Holiadur"
: formObj.representationForm.values
.representationType == "Statement"
: formObj.representationForm.values.representationType ==
"Statement"
? "Datganiad"
: formObj.representationForm.values
.representationType ==
: formObj.representationForm.values.representationType ==
"Final comments"
? "Sylwadau terfynol"
: formObj.representationForm.values
.representationType
: formObj.representationForm.values
.representationType}
</label>
: formObj.representationForm.values.representationType
: formObj.representationForm.values.representationType;
const handleSaveExit = () => {
let valuesObj = props.props.props.form[props.form].values || {};
delete valuesObj["_pinswg_appellant_value"];
updateRepresentation(valuesObj, false, true);
};
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:consultation-from-an-interested-person-heading"
)}
</div>
</div>
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={!hasTypeSelectionInline}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
optionsArr={interestedPersonRepresentationArr}
validate={[required]}
errorMsg={t("myrepresentations:select-an-option-label")}
/>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
@@ -127,12 +114,7 @@ const RepInterestedPartyPerson = (props) => {
name="representationOnBehalfOf"
datafieldname="representationOnBehalfOf"
// label="In what capacity do you wish to make representations on this case?"
options={[
t(
"myrepresentations:questionnaire-yes"
),
t("myrepresentations:questionnaire-no"),
]}
options={["Yes", "No"]}
id="representationOnBehalfOf"
className="govuk-radios__input"
errorMsg={t(
@@ -233,76 +215,16 @@ const RepInterestedPartyPerson = (props) => {
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
{/*
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationCapacity",
""
);
props.updateField(
"representationForm",
"representationType",
""
);
}}
className="govuk-link"
>
{t("case:summary-back-button-label")}
</a> */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)}
</>
);
@@ -316,7 +238,7 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(change(form, field, newValue)),
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
});
export default connect(
@@ -326,6 +248,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RepInterestedPartyPerson)
);
@@ -11,6 +11,7 @@ import {
FileUploadField,
RenderMultiline,
RenderPickList,
shouldDisableButton,
} from "./representationElements";
import RepresentationProgress_enforcement from "./representationProgress/representationProgress_enforcement";
import RepresentationProgress_s78 from "./representationProgress/representationProgress_s78";
@@ -510,6 +511,10 @@ let RepLPA = (props) => {
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj,
})}
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
@@ -14,12 +14,12 @@ import {
RenderPickList,
RenderRadioList,
RenderRichMultiline,
RenderTextfield,
RenderTextfield
} from "../representationElements";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
setFilesForRepresentations
} from "../../../../store/currentView/action";
let Enforcement_Questionnaire = (props) => {
@@ -36,7 +36,7 @@ let Enforcement_Questionnaire = (props) => {
procedureTypeValue,
onBehalfOfLPA,
showQuestionnaireSection,
setFilesForRepresentations,
setFilesForRepresentations
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const files = acceptedFiles.map((file) => (
@@ -61,10 +61,10 @@ let Enforcement_Questionnaire = (props) => {
detailsObj = jsonpath({
path:
'$..[?(@ && @.pinswg_name=="' +
currentView.caseReference.currentReference +
currentView.caseReference.ticketnumber +
'")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
let setCaseResultssObj = router.query.hasOwnProperty("state")
@@ -84,10 +84,10 @@ let Enforcement_Questionnaire = (props) => {
resultsObj = jsonpath({
path:
'$..[?(@ && @.ticketnumber=="' +
currentView.caseReference.currentReference +
currentView.caseReference.ticketnumber +
'")]',
json: setCaseResultssObj,
eval: true,
eval: true
});
resultsObj = resultsObj[0];
@@ -118,7 +118,7 @@ let Enforcement_Questionnaire = (props) => {
<div className="govuk-form-group">
<div className="govuk-label">
2. {t("myrepresentations:enf-section-question-2")}:{" "}
{resultsObj.pinswg_lpareference}
{resultsObj.pinswg_lpareference || ""}
</div>
</div>
<div className="govuk-form-group">
@@ -155,7 +155,7 @@ let Enforcement_Questionnaire = (props) => {
optionsArr={[
"Written representations",
"Hearing",
"Inquiry",
"Inquiry"
]}
id="procedureType"
component={RenderPickList}
@@ -190,7 +190,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -207,7 +207,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="enterNeighbouringLandToViewSite"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="enterNeighbouringLandToViewSite"
className="govuk-radios__input"
@@ -236,7 +236,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="enterNeighbouringLandAccessRequired"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="enterNeighbouringLandAccessRequired"
className="govuk-radios__input"
@@ -265,7 +265,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="healthAndSafetyIssues"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="healthAndSafetyIssues"
className="govuk-radios__input"
@@ -328,7 +328,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -342,7 +342,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="feeExempt"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="feeExempt"
className="govuk-radios__input"
@@ -379,7 +379,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="previousGrantedCerts"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="previousGrantedCerts"
className="govuk-radios__input"
@@ -406,7 +406,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="stopNoticeServed"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="stopNoticeServed"
className="govuk-radios__input"
@@ -433,7 +433,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="relatedCases"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="relatedCases"
className="govuk-radios__input"
@@ -460,7 +460,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="previousEnforcement"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="previousEnforcement"
className="govuk-radios__input"
@@ -487,7 +487,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="subjectArticle4"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="subjectArticle4"
className="govuk-radios__input"
@@ -514,7 +514,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="previousPDRRestriction"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="previousPDRRestriction"
className="govuk-radios__input"
@@ -594,7 +594,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -621,7 +621,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -635,7 +635,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="ROW"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="ROW"
className="govuk-radios__input"
@@ -662,7 +662,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="conservationArea"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="conservationArea"
className="govuk-radios__input"
@@ -689,7 +689,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="affectListedBuilding"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="affectListedBuilding"
className="govuk-radios__input"
@@ -716,7 +716,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="TPO"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="TPO"
className="govuk-radios__input"
@@ -743,7 +743,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="natureConservationSite"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="natureConservationSite"
className="govuk-radios__input"
@@ -770,7 +770,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="protectedSpecies"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="protectedSpecies"
className="govuk-radios__input"
@@ -802,7 +802,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -816,7 +816,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="floodingIssue"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="floodingIssue"
className="govuk-radios__input"
@@ -843,7 +843,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="governmentDepartmentComments"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="governmentDepartmentComments"
className="govuk-radios__input"
@@ -884,7 +884,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="developmentCarriedOutInAccordance"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="developmentCarriedOutInAccordance"
className="govuk-radios__input"
@@ -915,7 +915,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -932,7 +932,7 @@ let Enforcement_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -946,7 +946,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="developmentAnyOtherConditions"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="developmentAnyOtherConditions"
className="govuk-radios__input"
@@ -987,7 +987,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="copyOfEnforcementNotice"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="copyOfEnforcementNotice"
className="govuk-radios__input"
@@ -1014,7 +1014,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="enforcementNoticePlan"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="enforcementNoticePlan"
className="govuk-radios__input"
@@ -1041,7 +1041,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="listOfNotifiedOfEnforcement"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="listOfNotifiedOfEnforcement"
className="govuk-radios__input"
@@ -1068,7 +1068,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="copyOfLetterOfAppealNotification"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="copyOfLetterOfAppealNotification"
className="govuk-radios__input"
@@ -1095,7 +1095,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="lpaEIAOS"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="lpaEIAOS"
className="govuk-radios__input"
@@ -1125,7 +1125,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="detailsOfOtherAppeals"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="detailsOfOtherAppeals"
className="govuk-radios__input"
@@ -1152,7 +1152,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="detailsOfPreviousAppeals"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="detailsOfPreviousAppeals"
className="govuk-radios__input"
@@ -1179,7 +1179,7 @@ let Enforcement_Questionnaire = (props) => {
datafieldname="otherRelevantInformation"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="otherRelevantInformation"
className="govuk-radios__input"
@@ -1312,7 +1312,7 @@ Enforcement_Questionnaire = connect((state) => {
return {
procedureTypeValue,
representationTypeValue,
representationTypeValue
};
})(Enforcement_Questionnaire);
@@ -1323,7 +1323,7 @@ const mapDispatchToProps = (dispatch) => {
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
};
};
@@ -1335,7 +1335,7 @@ const mapStateToProps = (state, props) => {
appealType: state.appealType,
currentView: state.currentView,
myRepresentations: state.myRepresentations,
initialValues: state.currentView.caseReference.repDetails,
initialValues: state.currentView.caseReference.repDetails
};
};
@@ -1346,6 +1346,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: false,
destroyOnUnmount: false,
destroyOnUnmount: false
})(Enforcement_Questionnaire)
);
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { useDropzone } from "react-dropzone";
import { Field, reduxForm, formValueSelector } from "redux-form";
import { getFormCollectionByID } from "../../../../components/utils";
import {
FileUploadField,
RenderDatePicker,
@@ -12,12 +13,12 @@ import {
RenderPickList,
RenderRadioList,
RenderRichMultiline,
RenderTextfield,
RenderTextfield
} from "../representationElements";
import {
setRepresentationCapacity,
setRepresentationSubmit,
setFilesForRepresentations,
setFilesForRepresentations
} from "../../../../store/currentView/action";
import { connect } from "react-redux";
@@ -38,7 +39,7 @@ let S78_Questionnaire = (props) => {
procedureTypeValue,
onBehalfOfLPA,
showQuestionnaireSection,
setFilesForRepresentations,
setFilesForRepresentations
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const files = acceptedFiles.map((file) => (
@@ -62,11 +63,11 @@ let S78_Questionnaire = (props) => {
detailsObj = jsonpath({
path:
'$..[?(@ && @.pinswg_name=="' +
currentView.caseReference.currentReference +
'$..[?(@ && @.ticketnumber=="' +
currentView.caseReference.ticketnumber +
'")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
let setCaseResultssObj = router.query.hasOwnProperty("state")
@@ -92,10 +93,10 @@ let S78_Questionnaire = (props) => {
: "ticketnumber"
: "ticketnumber") +
'=="' +
currentView.caseReference.currentReference +
currentView.caseReference.ticketnumber +
'")]',
json: setCaseResultssObj,
eval: true,
eval: true
});
resultsObj = resultsObj[0];
@@ -136,7 +137,7 @@ let S78_Questionnaire = (props) => {
<div className="govuk-form-group">
<div className="govuk-label">
2. {t("myrepresentations:s78-section-question-2")}:{" "}
{resultsObj.pinswg_lpareference}
{resultsObj.pinswg_lpareference || ""}
</div>
</div>
<div className="govuk-form-group">
@@ -175,7 +176,7 @@ let S78_Questionnaire = (props) => {
optionsArr={[
"Written representations",
"Hearing",
"Inquiry",
"Inquiry"
]}
id="procedureType"
component={RenderPickList}
@@ -209,7 +210,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -224,7 +225,7 @@ let S78_Questionnaire = (props) => {
datafieldname="enterNeighbouringLandToViewSite"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="enterNeighbouringLandToViewSite"
className="govuk-radios__input"
@@ -253,7 +254,7 @@ let S78_Questionnaire = (props) => {
datafieldname="enterNeighbouringLandAccessRequired"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="enterNeighbouringLandAccessRequired"
className="govuk-radios__input"
@@ -282,7 +283,7 @@ let S78_Questionnaire = (props) => {
datafieldname="healthAndSafetyIssues"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="healthAndSafetyIssues"
className="govuk-radios__input"
@@ -405,7 +406,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -434,7 +435,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -450,7 +451,7 @@ let S78_Questionnaire = (props) => {
datafieldname="ROW"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="ROW"
className="govuk-radios__input"
@@ -477,7 +478,7 @@ let S78_Questionnaire = (props) => {
datafieldname="conservationArea"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="conservationArea"
className="govuk-radios__input"
@@ -504,7 +505,7 @@ let S78_Questionnaire = (props) => {
datafieldname="affectListedBuilding"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="affectListedBuilding"
className="govuk-radios__input"
@@ -531,7 +532,7 @@ let S78_Questionnaire = (props) => {
datafieldname="TPO"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="TPO"
className="govuk-radios__input"
@@ -558,7 +559,7 @@ let S78_Questionnaire = (props) => {
datafieldname="natureConservationSite"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="natureConservationSite"
className="govuk-radios__input"
@@ -585,7 +586,7 @@ let S78_Questionnaire = (props) => {
datafieldname="protectedSpecies"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="protectedSpecies"
className="govuk-radios__input"
@@ -616,7 +617,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -629,7 +630,7 @@ let S78_Questionnaire = (props) => {
datafieldname="governmentDepartmentComments"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="governmentDepartmentComments"
className="govuk-radios__input"
@@ -675,7 +676,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -691,7 +692,7 @@ let S78_Questionnaire = (props) => {
datafieldname="developmentCarriedOutInAccordance"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="developmentCarriedOutInAccordance"
className="govuk-radios__input"
@@ -722,7 +723,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -741,7 +742,7 @@ let S78_Questionnaire = (props) => {
component={RenderRadioList}
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
type="text"
rows="5"
@@ -757,7 +758,7 @@ let S78_Questionnaire = (props) => {
datafieldname="developmentAnyOtherConditions"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="developmentAnyOtherConditions"
className="govuk-radios__input"
@@ -797,7 +798,7 @@ let S78_Questionnaire = (props) => {
datafieldname="copyOfApplicantsCertificate"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="copyOfApplicantsCertificate"
className="govuk-radios__input"
@@ -825,7 +826,7 @@ let S78_Questionnaire = (props) => {
datafieldname="advertismentGiven"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="advertismentGiven"
className="govuk-radios__input"
@@ -853,7 +854,7 @@ let S78_Questionnaire = (props) => {
datafieldname="copyOfLetterOfAppealNotification"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="copyOfLetterOfAppealNotification"
className="govuk-radios__input"
@@ -880,7 +881,7 @@ let S78_Questionnaire = (props) => {
datafieldname="lpaEIAOS"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="lpaEIAOS"
className="govuk-radios__input"
@@ -907,7 +908,7 @@ let S78_Questionnaire = (props) => {
datafieldname="advertismentConsentDiscontinuanceNotice"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="advertismentConsentDiscontinuanceNotice"
className="govuk-radios__input"
@@ -934,7 +935,7 @@ let S78_Questionnaire = (props) => {
datafieldname="detailsOfOtherAppeals"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="detailsOfOtherAppeals"
className="govuk-radios__input"
@@ -961,7 +962,7 @@ let S78_Questionnaire = (props) => {
datafieldname="detailsOfPreviousAppeals"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="detailsOfPreviousAppeals"
className="govuk-radios__input"
@@ -988,7 +989,7 @@ let S78_Questionnaire = (props) => {
datafieldname="otherRelevantInformation"
options={[
t("myrepresentations:questionnaire-yes"),
t("myrepresentations:questionnaire-no"),
t("myrepresentations:questionnaire-no")
]}
id="otherRelevantInformation"
className="govuk-radios__input"
@@ -1120,7 +1121,7 @@ S78_Questionnaire = connect((state) => {
return {
procedureTypeValue,
representationTypeValue,
representationTypeValue
};
})(S78_Questionnaire);
@@ -1131,7 +1132,7 @@ const mapDispatchToProps = (dispatch) => {
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
};
};
@@ -1143,7 +1144,7 @@ const mapStateToProps = (state, props) => {
appealType: state.appealType,
currentView: state.currentView,
myRepresentations: state.myRepresentations,
initialValues: state.currentView.caseReference.repDetails,
initialValues: state.currentView.caseReference.repDetails
};
};
@@ -1154,6 +1155,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: false,
destroyOnUnmount: false,
destroyOnUnmount: false
})(S78_Questionnaire)
);
@@ -0,0 +1,50 @@
import RepresentationTimetablePanel from "./RepresentationTimetablePanel";
const RepresentationFlowFormStage = ({
t,
appealIsConsultation,
currentReference,
whichControl,
appealIsLocalImpact,
renderDueDate,
renderStatementsDue,
renderMarineImpact,
renderConsultationClose,
renderFinalCommentsDue,
isQuestionnaire,
whichProgress
}) => {
return (
<>
<h1 className="govuk-heading-l">
{appealIsConsultation
? t("myrepresentations:make-a-consultation-on-label")
: t(
"myrepresentations:make-a-representation-on-label"
)}{" "}
{currentReference}{" "}
</h1>
<div id="rep-form">
<div className="govuk-grid-column-three-quarters wgForm">
{whichControl()}
</div>
<RepresentationTimetablePanel
t={t}
appealIsConsultation={appealIsConsultation}
appealIsLocalImpact={appealIsLocalImpact}
renderDueDate={renderDueDate}
renderStatementsDue={renderStatementsDue}
renderMarineImpact={renderMarineImpact}
renderConsultationClose={renderConsultationClose}
renderFinalCommentsDue={renderFinalCommentsDue}
isQuestionnaire={isQuestionnaire}
whichProgress={whichProgress}
/>
</div>
</>
);
};
export default RepresentationFlowFormStage;
@@ -0,0 +1,49 @@
const RepresentationTimetablePanel = ({
t,
appealIsConsultation,
appealIsLocalImpact,
renderDueDate,
renderStatementsDue,
renderMarineImpact,
renderConsultationClose,
renderFinalCommentsDue,
isQuestionnaire,
whichProgress
}) => {
return (
<div className="govuk-grid-column-one-quarter">
<div className="card">
<div className="card-body">
<h2 className="govuk-heading-s">
{t("myrepresentations:timetable-title")}
</h2>
<div className="govuk-summary-list">
<div className="govuk-body-s">
{appealIsLocalImpact
? t("myrepresentations:local-impact-report")
: t(
"myrepresentations:questionnaire-due-label"
)}
:{" "}
<span className="govuk-body-s">
{renderDueDate()}
</span>
</div>
{!appealIsConsultation && renderStatementsDue()}
{renderMarineImpact()}
{renderConsultationClose()}
{renderFinalCommentsDue()}
</div>
</div>
</div>
{isQuestionnaire && whichProgress()}
</div>
);
};
export default RepresentationTimetablePanel;
@@ -0,0 +1,93 @@
import { JSONPath as jsonpath } from "jsonpath-plus";
export const resolveInitialCaseDataSources = ({
hasState,
isEditState,
currentType,
myRepresentations,
searchDetailsObj,
searchResultsObj,
watchedCasesDetails
}) => {
if (hasState) {
return {
"caseDetailsSource": isEditState
? myRepresentations
: searchDetailsObj,
"caseResultsSource": isEditState
? myRepresentations
: searchResultsObj
};
}
return {
"caseDetailsSource":
currentType === "watchedCases"
? watchedCasesDetails
: searchDetailsObj,
"caseResultsSource":
currentType === "watchedCases"
? watchedCasesDetails
: searchResultsObj
};
};
export const findResultsObjForCurrentReference = ({
currentReference,
caseResultsSource
}) => {
return jsonpath({
path: `$..[?(@ && @.ticketnumber=="${currentReference}")]`,
"json": caseResultsSource,
"eval": true
})[0];
};
export const findDetailsObjForView = ({
hasState,
isEditState,
queryCreated,
ticketnumber,
caseDetailsSource
}) => {
if (hasState && isEditState) {
return jsonpath({
path: `$..[?(@ && @.repfile_name=="${queryCreated}")]`,
"json": caseDetailsSource,
"eval": true
})[0];
}
return jsonpath({
path: `$..[?(@ && @.ticketnumber=="${ticketnumber}")]`,
"json": caseDetailsSource,
"eval": true
})[0];
};
export const resolveSubmitCaseDetailsSource = ({
isEditState,
currentType,
myRepresentations,
watchedCasesDetails,
searchDetailsObj
}) => {
if (isEditState) {
return myRepresentations;
}
return currentType === "watchedCases"
? watchedCasesDetails
: searchDetailsObj;
};
export const findDetailsObjForSubmit = ({
ticketnumber,
caseDetailsSource
}) => {
return jsonpath({
path: `$..[?(@ && @.ticketnumber=="${ticketnumber}")]`,
"json": caseDetailsSource,
"eval": true
})[0];
};
@@ -0,0 +1,126 @@
export const buildRepresentationUpdateBody = ({
formattedValues,
setRepFileName
}) => {
let updateBody = setRepFileName(formattedValues);
const sanitizedBody = Object.entries(updateBody).reduce(
(acc, [key, val]) => {
if (val !== null && val !== undefined && !key.startsWith("_")) {
acc[key] = val;
}
return acc;
},
{}
);
const stringified = JSON.stringify(sanitizedBody)
.replace(/:"Yes"/g, ":true")
.replace(/:"No"/g, ":false");
updateBody = JSON.parse(stringified);
return updateBody;
};
export const buildSubmitEnrichedValues = ({
formValues,
accountDetails,
appealType,
caseReferenceObj,
detailsObj,
resultsObj,
representationCapacity,
locale
}) => {
const buildSiteAddress = () => {
const {
pinswg_siteaddressline1,
pinswg_siteaddressline2,
pinswg_siteaddresstown,
pinswg_siteaddresspostcode
} = detailsObj;
return [
pinswg_siteaddressline1,
pinswg_siteaddressline2,
pinswg_siteaddresstown,
pinswg_siteaddresspostcode
]
.filter(Boolean)
.join(" ");
};
return {
...formValues,
containerID: accountDetails.containerID,
appealType: appealType || caseReferenceObj.repDetails.appealType,
incidentID:
caseReferenceObj.incidentid ||
caseReferenceObj.repDetails.incidentid,
caseRef: caseReferenceObj.currentReference,
casereference: caseReferenceObj.currentReference,
pinswg_questionnaireduedate: detailsObj.pinswg_questionnaireduedate,
pinswg_endofrepresentationperiod:
detailsObj.pinswg_endofrepresentationperiod,
pinswg_applicationacceptedasvalid:
detailsObj.pinswg_applicationacceptedasvalid,
pinswg_statementduedate: detailsObj.pinswg_statementduedate,
pinswg_statementsduedate: detailsObj.pinswg_statementsduedate,
pinswg_finalcommentsduedate: detailsObj.pinswg_finalcommentsduedate,
pinswg_name: caseReferenceObj.currentReference,
firstname: accountDetails.firstname,
lastname: accountDetails.lastname,
emailAddress: accountDetails.emailaddress1,
siteAddress: buildSiteAddress(),
pinswg_siteaddressline1: detailsObj.pinswg_siteaddressline1,
pinswg_siteaddressline2: detailsObj.pinswg_siteaddressline2,
pinswg_siteaddresstown: detailsObj.pinswg_siteaddresstown,
pinswg_siteaddresspostcode: detailsObj.pinswg_siteaddresspostcode,
pinswg_lpareference: resultsObj?.pinswg_lpareference,
_pinswg_appellant_value: detailsObj._pinswg_appellant_value,
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue":
detailsObj[
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
],
_pinswg_localplanningauthority_value:
detailsObj._pinswg_localplanningauthority_value ||
detailsObj._pinswg_associatedlpa_value,
"_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue":
detailsObj[
"_pinswg_localplanningauthority_value@OData.Community.Display.V1.FormattedValue"
] ||
detailsObj[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
],
representationCapacity,
locale
};
};
export const runFinalisationSequence = ({
values,
containerID,
currentReference,
setFinaliseAppealProcess,
generateRepPDF,
updateLinks,
uploadRepresentationFiles,
setRepresentationSubmit,
setRepresentationSubmitConfirmation
}) => {
setFinaliseAppealProcess(true);
return generateRepPDF(values, containerID, currentReference).then(
(data) => {
if (data.status === "success") {
const updatedValues = updateLinks(values);
uploadRepresentationFiles(updatedValues);
setRepresentationSubmit(true);
setRepresentationSubmitConfirmation(true);
}
return data;
}
);
};
@@ -0,0 +1,73 @@
export const resolveJourneyStageFlags = ({
representationSubmit,
representationSubmitConfirmation
}) => {
const showCheckStage =
representationSubmit === true || representationSubmit === "true";
const showCompletionStage =
representationSubmitConfirmation === true ||
representationSubmitConfirmation === "true";
return {
showFormStage: !showCheckStage,
showCheckStage,
showCompletionStage
};
};
export const resolveRepresentationControlKey = ({
isLPA,
representationCapacity,
repDetailsCapacity,
appealType,
normalizeCapacity
}) => {
if (isLPA && (!representationCapacity || representationCapacity === "")) {
return {
controlKey: "lpa-capacity-selection",
normalizedCapacity: ""
};
}
const rawCapacity = representationCapacity || repDetailsCapacity || "false";
const normalizedCapacity = normalizeCapacity(rawCapacity.toLowerCase());
return {
controlKey: `capacity-${normalizedCapacity}`,
normalizedCapacity
};
};
export const resolveSubmitTransition = ({
representationType,
showQuestionnaireSection,
questionnaireCount
}) => {
if (representationType !== "Questionnaire") {
return "goToCheck";
}
if (
questionnaireCount !== 0 &&
showQuestionnaireSection < questionnaireCount
) {
return "advanceQuestionnaire";
}
return "goToCheck";
};
export const getQuestionnaireNextSection = (
currentSection,
questionnaireCount
) => {
if (questionnaireCount !== 0 && currentSection < questionnaireCount) {
return currentSection + 1;
}
return currentSection;
};
export const getQuestionnairePreviousSection = (currentSection) => {
return currentSection > 1 ? currentSection - 1 : currentSection;
};
+5 -5
View File
@@ -6,7 +6,7 @@ import { useEffect, useState } from "react";
import { connect } from "react-redux";
import transLookup from "../../data/lookuptranslations.json";
import { getRepresentations } from "../../actions";
import { getRepresentations } from "../../actions/services/portalService";
import { setRepresentations } from "../../store/searchOutput/action";
import { formatDates } from "../utils";
@@ -63,7 +63,7 @@ const RepresentationList = (props) => {
incidentid +
"')]",
json: item,
eval: true,
eval: true
});
detailsObj = item;
@@ -97,7 +97,7 @@ const RepresentationList = (props) => {
] +
'")].value_cy',
json: transLookup,
sanbox: {},
sanbox: {}
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
@@ -216,7 +216,7 @@ const RepresentationList = (props) => {
const mapStateToProps = (state) => {
return {
...state,
...state
};
};
@@ -224,7 +224,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setRepresentations: (representationObj) => {
dispatch(setRepresentations(representationObj));
},
}
};
};
+1354 -615
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More