Author SHA1 Message Date
Robert Bond fdeff5b898 Merged PR 2605: updated query to include consultationclosed check
updated query to include consultationclosed check

Related work items: #24836
2026-09-07 10:46:32 +00:00
Robert Bond 4e9d585566 Merged PR 2596: fix build error
fix build error

Related work items: #24648
2026-09-02 12:37:32 +00:00
Robert Bond b95a20a980 Merged PR 2595: updated regex to exclude bulk annon mailers
updated regex to exclude bulk annon mailers

Related work items: #24648
2026-09-02 08:02:38 +00:00
Robert Bond c9e7743782 Merged PR 2586: update text for reps showing
update text for reps showing

Related work items: #24650
2026-08-25 15:20:56 +00:00
Robert Bond 6bf5918387 Merged PR 2585: added text before make reps button
added text before make reps button

Related work items: #24650
2026-08-25 09:01:11 +00:00
Robert Bond c2d79cf83f Merged PR 2580: fixed the links
fixed the links

Related work items: #24648
2026-08-20 12:54:44 +00:00
Robert Bond 6ba1e16f9a Merged PR 2573: added link to footer
added link to footer

Related work items: #24649
2026-08-18 15:17:25 +00:00
Robert Bond 6cce7d05f1 Merged PR 2572: overlay link to guidance
overlay link to guidance

Related work items: #24650
2026-08-18 15:02:36 +00:00
Robert Bond ac4834a2a4 Merged PR 2570: updated copyright links
updated copyright links

Related work items: #24648
2026-08-18 14:21:54 +00:00
Robert Bond 850006d917 Merged PR 2559: added package lock
added package lock

Related work items: #24389
2026-08-12 14:03:23 +00:00
Robert Bond 478fa91004 Merged PR 2558: updated link on awaiting submission view allin welsh
updated link on awaiting submission view allin welsh

Related work items: #24389
2026-08-12 13:58:07 +00:00
Robert Bond 5813d409bc Merged PR 2557: removed field
removed field

Related work items: #24554
2026-08-11 09:26:07 +00:00
Robert Bond 71035a054f Merged PR 2556: updated welsh translation
updated welsh translation

Related work items: #24389
2026-08-11 09:13:22 +00:00
Robert Bond c2c4690b9b Merged PR 2555: missing slash in welsh url for case link
missing slash in welsh url for case link

Related work items: #24389
2026-08-10 13:11:18 +00:00
Robert Bond 9e2461305f Merged PR 2546: upodated copy
upodated copy

Related work items: #24335
2026-08-05 11:40:56 +00:00
Robert Bond db07425788 Merged PR 2541: update description for case closed
update description for case closed

Related work items: #24335
2026-08-05 08:31:17 +00:00
Robert Bond 90e8cacf67 Merged PR 2537: adjusted back button on first page of reopend rep
adjusted back button on first page of reopend rep

Related work items: #24488
2026-08-04 10:42:07 +00:00
Robert Bond 6462bd5163 Merged PR 2536: updated path to wrapper to fix build error
updated path to wrapper to fix build error

Related work items: #24215
2026-08-04 08:30:13 +00:00
Robert Bond d190fa8ff7 Merged PR 2534: adjusted breadcrumb for viewall return from a case
adjusted breadcrumb for viewall return from a case

Related work items: #24389
2026-08-04 08:09:04 +00:00
Robert Bond 460dfadd1e Merged PR 2533: made petemail from server side var for logged in
made petemail from server side var for logged in

Related work items: #24215
2026-08-04 07:44:08 +00:00
Robert Bond d74e3fd668 Merged PR 2532: made petemail from serverside env var
made petemail from serverside env var

Related work items: #24215
2026-08-04 07:36:57 +00:00
Robert Bond 75e9cf820d Merged PR 2513: updated translation
updated translation

Related work items: #24396
2026-07-24 12:01:36 +00:00
Robert Bond 9a461dee5e Merged PR 2512: adjsuted breadcrumb for dashboard links to cases to return to my portal not s...
adjsuted breadcrumb for dashboard links to cases to return to my portal not search results

Related work items: #24389
2026-07-24 11:38:48 +00:00
Robert Bond 1258985882 Merged PR 2504: amend paths for reps to exclude long titles
amend paths for reps to exclude long titles

Related work items: #24279
2026-07-22 15:51:27 +00:00
Robert Bond 02507f298c Merged PR 2501: welsh updates for ministerial decision
welsh updates for ministerial decision

Related work items: #24335
2026-07-22 12:35:30 +00:00
Robert Bond 11cdb43da4 Merged PR 2500: additional welsh ammends
additional welsh ammends

Related work items: #24314
2026-07-22 12:24:54 +00:00
Robert Bond f5bdf6a7b0 Merged PR 2493: updated ministerial decision and removed inspectors decision for sips
updated ministerial decision and removed inspectors decision for sips

Related work items: #24335
2026-07-21 11:36:22 +00:00
Robert Bond f80752ab6a Merged PR 2491: delimit event name for welsh >>>
delimit event name for welsh >>>

Related work items: #24302
2026-07-20 16:03:48 +00:00
Robert Bond b01d789bb8 Merged PR 2490: eia to aea for welsh in tab and subheadings
eia to aea for welsh in tab and subheadings

Related work items: #24269
2026-07-20 15:10:18 +00:00
Robert Bond bbf205a45f Merged PR 2489: added in additonal dates
added in additonal dates

Related work items: #24318
2026-07-20 10:05:14 +00:00
Robert Bond ec046944c0 Merged PR 2488: fixed welsh breadcrumb link
fixed welsh breadcrumb link

Related work items: #24328
2026-07-20 06:51:48 +00:00
Robert Bond 579026b6cc Merged PR 2486: adjsuted references to pinswg_eiarequired fields to pinswg_sipseiarequired
adjsuted references to pinswg_eiarequired fields to pinswg_sipseiarequired

Related work items: #24261
2026-07-17 11:52:55 +00:00
Robert Bond 2e9f90211c Merged PR 2485: updated welsh translations
Related work items: #24314
2026-07-16 16:17:02 +00:00
Robert Bond b7a644d078 Merged PR 2484: updated link for SIPS overview
updated link for SIPS overview

Related work items: #24315
2026-07-16 15:04:04 +00:00
Robert Bond 7a54f3df71 Merged PR 2482: updated submission examination display logic
updated submission examination display logic

Related work items: #24306
2026-07-16 05:05:57 +00:00
Robert Bond a850399cb1 Merged PR 2481: updated labels and added welsh language description
updated labels and added welsh language description

Related work items: #24302
2026-07-15 17:15:55 +00:00
Robert Bond e2897e511f Merged PR 2480: update status from page load not state
update status from page load not state

Related work items: #24297
2026-07-15 16:43:43 +00:00
Robert Bond 95486f0164 Merged PR 2476: added check for inactive case deadlines
added check for inactive case deadlines

Related work items: #24297
2026-07-15 12:41:10 +00:00
Robert Bond b00a2c0733 Merged PR 2475: updated file location for consultations to be correct ticket number not title
updated file location for consultations to be correct ticket number not title

Related work items: #24279
2026-07-14 16:13:11 +00:00
Robert Bond d7a73b7b0b Merged PR 2473: added in the dates on summary tabs
added in the dates on summary tabs

Related work items: #24258, #24259, #24263, #24269
2026-07-14 10:40:54 +00:00
Robert Bond bab0c26515 Merged PR 2468: tweaked button link to use caseref
tweaked button link to use caseref

Related work items: #24199
2026-07-09 11:59:31 +00:00
Robert Bond 9ac88bbbde Merged PR 2452: updated csp and params
updated csp and params

Related work items: #23879
2026-07-03 04:50:47 +00:00
Robert Bond 79e6dfade9 Merged PR 2451: included dynamic legend on enlarged map
included dynamic legend on enlarged map

Related work items: #23879
2026-07-02 16:12:23 +00:00
Robert Bond e864049bec Merged PR 2449: language helper
Related work items: #23754
2026-07-01 11:29:15 +00:00
Robert Bond 49aa258fd9 Merged PR 2443: added docs
added docs

Related work items: #23754
2026-06-29 17:58:10 +00:00
Robert Bond ff699735c7 Merged PR 2442: remove lodash dependecies on _.has
Related work items: #23754
2026-06-29 13:08:06 +00:00
Robert Bond 7b6af58065 Merged PR 2441: electricity shouldnt be showing reps available
electricity shouldnt be showing reps available

Related work items: #24008
2026-06-29 08:39:39 +00:00
Robert Bond 001c7ad6fa Merged PR 2436: adjsuted condition to include pinswg_startdate in showrepbutton
adjsuted condition to include  pinswg_startdate in showrepbutton

Related work items: #23998
2026-06-25 14:47:05 +00:00
Robert Bond b87c522137 Merged PR 2435: Add workflow orchestration documentation validation
Add workflow orchestration documentation validation

Related work items: #23754
2026-06-25 11:46:36 +00:00
Robert Bond 3960a357dc Merged PR 2433: assessment api grouping
assessment api grouping

Related work items: #23754
2026-06-25 09:52:15 +00:00
Robert Bond 94b19f9697 Merged PR 2432: Add myportal API grouping facade read slice
Add myportal API grouping facade read slice

Related work items: #23754
2026-06-25 09:37:01 +00:00
Robert Bond a1b89d19b8 Merged PR 2431: Add case details API grouping facade
Add case details API grouping facade

Related work items: #23754
2026-06-25 09:22:12 +00:00
Robert Bond f9f78548b0 Merged PR 2430: Add API grouping facade rollout checkpoint
Add API grouping facade rollout checkpoint

Related work items: #23754
2026-06-25 09:05:59 +00:00
Robert Bond a266fa5428 Merged PR 2429: Add public search API grouping facade slice
Add public search API grouping facade slice

Related work items: #23754
2026-06-25 08:48:43 +00:00
Robert Bond 33dcb6b863 Merged PR 2428: Add documents API grouping facade slice
Add documents API grouping facade slice

Related work items: #23754
2026-06-25 08:24:58 +00:00
Robert Bond 153f0d679e Merged PR 2427: update api facade
Related work items: #23754
2026-06-25 08:11:14 +00:00
Robert Bond 5483c4cdf2 Merged PR 2426: added var to summary map and welsh translatioins
added var to summary map and welsh translatioins

Related work items: #23879
2026-06-24 17:10:22 +00:00
Robert Bond c98bbfc321 Merged PR 2424: varibale for dmw mapping version
varibale for dmw mapping version

Related work items: #23879
2026-06-24 14:43:08 +00:00
Robert Bond 6d0bc35612 Merged PR 2420: added welsh translations of api endpoints
added welsh translations of api endpoints

Related work items: #23879
2026-06-23 10:38:24 +00:00
Robert Bond d7bb1acb10 Merged PR 2414: welsh version of contact us and heading for rep heading for IP rep type
welsh version of contact us and heading for rep heading for IP rep type

Related work items: #23484, #23910
2026-06-22 12:44:36 +00:00
Robert Bond 27fceffbc9 Merged PR 2413: updated docs
updated docs

Related work items: #23754
2026-06-22 05:36:30 +00:00
Robert Bond 0150e243ed Merged PR 2408: updated context for current state at 19062026
updated context for current state at 19062026

Related work items: #23754
2026-06-19 12:39:40 +00:00
Robert Bond e195e09429 Merged PR 2407: Extract representation entry specialist rules to domain policy
Extract representation entry specialist rules to domain policy

Related work items: #23754
2026-06-19 11:21:17 +00:00
Robert Bond be68b987a1 Merged PR 2405: Representation Entry Policy discovery and wrapper
Related work items: #23754
2026-06-19 08:19:56 +00:00
Robert Bond 60a2ff4594 Merged PR 2404: case stage analysis
Related work items: #23754
2026-06-18 16:02:40 +00:00
Robert Bond 81a6276f44 Merged PR 2401: docs: document representation participation availability model
docs: document representation participation availability model

Related work items: #23754
2026-06-18 10:27:53 +00:00
Robert Bond c84ff3fff9 Merged PR 2399: Adds characterization coverage and documentation for CRM query field profile
Related work items: #23754
2026-06-18 08:04:36 +00:00
Robert Bond a6941a8a06 Merged PR 2398: amends to appeal policy
Related work items: #23754
2026-06-18 05:17:10 +00:00
Robert Bond 475aa965b4 Merged PR 2395: helpers for dashboard domain layer
Related work items: #23754
2026-06-17 16:06:09 +00:00
Robert Bond 0b66a0e1d4 Merged PR 2394: adjusting reps domain logic
Related work items: #23754
2026-06-17 10:55:32 +00:00
Robert Bond ca244e5de1 Merged PR 2392: updated project types for new sub grid
updated project types for new sub grid

Related work items: #23752
2026-06-16 17:44:04 +00:00
Robert Bond 3efaafdb72 Merged PR 2388: added updated copy for contact us
added updated copy for contact us

Related work items: #23482, #23483
2026-06-16 08:25:05 +00:00
Robert Bond 1e14fbc709 Merged PR 2387: update welsh translations for new appeal pdf
update welsh translations for new appeal pdf

Related work items: #23562
2026-06-15 12:03:28 +00:00
Robert Bond 628b2b71fd Merged PR 2385: update unsubscribe
update unsubscribe

Related work items: #23796
2026-06-11 13:04:21 +00:00
Robert Bond 3297fe4f16 Merged PR 2384: updated pdf to show correct lang prefs on appeal pdf
updated pdf to show correct lang prefs on appeal pdf

Related work items: #23562
2026-06-11 10:51:38 +00:00
Robert Bond 7b1faf0bac Merged PR 2382: missing import
missing import

Related work items: #23780
2026-06-10 12:41:04 +00:00
Robert Bond c6e42fc314 Merged PR 2381: ROW case stage updates
Related work items: #23564, #23779, #23780
2026-06-10 10:12:11 +00:00
Robert Bond 7e47340326 Merged PR 2377: added enlarge map view on dnsapplication list
added enlarge map view on dnsapplication list

Related work items: #23774
2026-06-09 10:04:32 +00:00
Robert Bond c39e4bcc9a Merged PR 2375: update representation policy
## Summary

Introduces a new `representation-policy` domain boundary and incrementally extracts low-risk representation entry policy logic while preserving existing behaviour.

This PR intentionally stops before extracting ROW and Advert entry rules because characterization uncovered behavioural differences between consumers that require a separate business decision.

## What Changed

Added:

```text
lib/domain/representation-policy/
```

Including:

- `resolveRepresentationWindow(...)`
- `isRepresentationWindowOpen(...)`
- `isRepresentationWindowClosed(...)`
- `canShowRepresentationButtonForAppealType(...)`
- `canStartHouseholderRepresentation(...)`
- `canStartCpoRepresentation(...)`

Updated consumers:

```text
components/case/summary/utils/representationEntry.js
components/search/repsonresults.js
```

## Extracted Behaviour

### Representation Window Calculation
Centralised shared representation window logic and adopted it in both consumers.

### Appeal Type Entry Gating
Centralised excluded appeal-type logic while preserving existing behaviour.

### Householder Rule
Centralised Householder (`846040004`) entry rule.

Preserved behaviour:

```text
Householder representations can only be started by LPAs.
```

### CPO Rule
Centralised CPO (`846040019`) entry rule.

Preserved behaviour using:

```text
pinswg_startdate
pinswg_statementduedate
```

No fallback date broadening introduced.

## Characterization Added

### ROW (`846040015`)
Documented:
- hearing vs non-hearing behaviour
- specialist-process field behaviour
- date gating
- appeal-type coercion behaviour

### Advert (`846040018`)
Documented:
- LPA/non-LPA behaviour
- specialist-process behaviour
- appeal-type coercion behaviour

## Important Findings

### ROW Divergence
Summary and Search consumers currently behave differently when only:

```text
pinswg_speacialistcaseprocess
```

exists.

### Advert Divergence
Summary and Search consumers currently use different specialist-process resolution paths.

### CRM Compatibility
Both fields remain in production use and must be preserved:

```text
pinswg_specialistcaseprocess
pinswg_speacialistcaseprocess
```

## Documentation

Added:

```text
lib/domain/representation-policy/README.md
```

Documenting:
- ownership
- non-goals
- CRM compatibility requirements
- ROW divergence
- Advert divergence
- future extraction constraints

## Validation

Executed during the slice series:

```bash
node tests/phase22/representation-window.test.cjs
node tests/phase22/representation-appeal-type-entry-gating.test.cjs
node tests/phase22/representation-householder-entry-rule.test.cjs
node tests/phase22/representation-cpo-entry-rule.test.cjs
node tests/phase22/representation-row-entry-rule.test.cjs
node tests/phase22/representation-advert-entry-rule.test.cjs
npm run lint
```

All passing.

## Out of Scope

No changes to:

- submission/finalisation
- uploads
- dashboards
- CRM/OData queries
- API routes
- Redux state
- translations
- blocked-message rendering
- CTA l...
2026-06-08 10:51:02 +00:00
Robert Bond 9595ad86df Merged PR 2374: addeding domain layer extrraction
# Summary

This PR introduces a **Case Lifecycle Domain Boundary** to centralize lifecycle decision logic and reduce coupling within the appeals application.

The work is **behaviour-preserving** and introduces no intentional changes to business rules, CRM integrations, translations, dashboards, API routes, or user-facing functionality.

## What was added

New lifecycle boundary:

```text
lib/domain/case-lifecycle/
```

Key responsibilities extracted:

- Specialist process normalization
- Appeal type mapping
- Specialist process stage override mapping
- Stage case-type resolution
- Stage catalogue lookup
- Closed-case status recognition
- Lifecycle stage index resolution
- Lifecycle stage status assignment

## Behaviour preserved

Characterization tests were added before each extraction to preserve:

- Appeal type mapping and aliases
- Specialist process handling
- Lifecycle stage progression
- Closed-case handling
- Status assignment (`complete`, `in-progress`, `not-started`)
- Existing ROW behaviour
- Existing `statuscode` lifecycle semantics

Closed-case recognition remains unchanged for:

```text
1000
5
6
846040013
846040059
846040060
```

## Documentation

Added:

```text
lib/domain/case-lifecycle/README.md
```

Documenting:

- Boundary ownership
- Non-goals
- Lifecycle invariants
- Known architectural constraints
- Future extraction roadmap

## Testing

Added lifecycle characterization coverage for:

- Stage wrapper behaviour
- Specialist process normalization
- Appeal type mapping
- Specialist process stage mapping
- Stage case-type resolution
- Stage catalogue lookup
- Progress behaviour
- Closed-case status handling
- Stage index resolution
- Stage status assignment

## Validation

- Lifecycle characterization tests passed
- `npm run lint` passed with no errors

## Out of Scope

No changes to:

- Stage catalogue ownership
- Representation eligibility
- Dashboard calculations
- CRM/OData queries
- API routes
- Redux state
- EN/CY translations
- Event visibility logic

## Risk

**Low risk**

The refactor was delivered through small, characterization-first slices with no functional changes intended.

Related work items: #23527
2026-06-08 09:34:30 +00:00
Robert Bond 2262f3ab74 Merged PR 2369: added dns sips case close message and in welsh
added dns sips case close message and in welsh

Related work items: #23555
2026-06-03 14:48:58 +00:00
Robert Bond d0cae0fff1 Merged PR 2368: case stage ROW updates and case closed variants and adv search fix
Related work items: #23551, #23555, #23559
2026-06-03 12:44:59 +00:00
Robert Bond a9be19b901 Merged PR 2367: updated description and welsh
updated description and welsh

Related work items: #23551
2026-06-02 14:46:55 +00:00
Robert Bond da07b4873e Merged PR 2365: update welsh case stage
update welsh case stage

Related work items: #23359
2026-06-02 12:40:08 +00:00
Robert Bond 4ced5d0f42 Merged PR 2363: updated status and welsh
updated status and welsh

Related work items: #23319, #23359
2026-06-02 08:34:51 +00:00
Robert Bond 18a05d4be8 Merged PR 2357: dashboards in admin for appeals and lpa
dashboards in admin for appeals and lpa

Related work items: #23527
2026-05-29 16:35:27 +00:00
Robert Bond b71a93076b Merged PR 2352: adjusted query to only show published
adjusted query to only show published

Related work items: #23447
2026-05-28 11:02:18 +00:00
Robert Bond 3f2b8807e7 Merged PR 2351: fix csp by making it server side
Related work items: #23447
2026-05-28 10:41:11 +00:00
Robert Bond 24cd7000f7 Merged PR 2350: added in count on case status dropdown
added in count on case status dropdown

Related work items: #23447
2026-05-28 09:02:30 +00:00
Robert Bond 905c82f598 Merged PR 2349: updated dropdown to show all new case stages
updated dropdown to show all new case stages

Related work items: #23447
2026-05-28 07:05:06 +00:00
Robert Bond 26e8464d46 Merged PR 2348: corrected welsh version of representations period
corrected welsh version of representations period

Related work items: #23319
2026-05-27 14:29:20 +00:00
Robert Bond 1df5573fce Merged PR 2346: updated close status on non val
updated close status on non val

Related work items: #23365
2026-05-26 16:11:48 +00:00
Robert Bond 25485d73ed Merged PR 2345: typo
typo

Related work items: #23365
2026-05-26 14:57:34 +00:00
Robert Bond 6d8d56d745 Merged PR 2343: update to non val
Related work items: #23365
2026-05-26 13:42:09 +00:00
Robert Bond f6bbaa5c1d Merged PR 2342: ammend case status and descriptions per bugs
ammend case status and descriptions per bugs

Related work items: #23319, #23357, #23359, #23365, #23394
2026-05-26 07:50:33 +00:00
Robert Bond 4f3e3f5183 Merged PR 2338: updated welsh translation
updated welsh translation

Related work items: #23319
2026-05-21 12:28:36 +00:00
Robert Bond a6cc030612 Merged PR 2333: updated welsh link on events tab
updated welsh link on events tab

Related work items: #22423
2026-05-19 15:26:33 +00:00
Robert Bond 2e98be3b65 Merged PR 2331: updated welsh text
updated welsh text

Related work items: #23094
2026-05-19 05:23:14 +00:00
Robert Bond 149238dd4e Merged PR 2330: welsh translation update and reps and site visit fix
welsh translation update and reps and site visit fix

Related work items: #23087, #23088
2026-05-18 12:18:03 +00:00
Robert Bond b6bd543527 Merged PR 2329: welsh link for events
welsh link for events

Related work items: #23054
2026-05-18 08:50:58 +00:00
Robert Bond 5967665138 Merged PR 2325: missing welsh translations on search results
missing welsh translations on search results

Related work items: #23044
2026-05-15 15:08:53 +00:00
Robert Bond 9860f47cbe Merged PR 2324: updated status message for case started/case in progrsss
updated status message for case started/case in progrsss

Related work items: #23064
2026-05-15 13:14:23 +00:00
Robert Bond 5bf1393020 Merged PR 2322: removed the ? on event
removed the ? on event

Related work items: #23063
2026-05-15 10:33:01 +00:00
Robert Bond 5a8fd73bfd Merged PR 2321: updated welsh status on search results and status tab descriptions
updated welsh status on search results and status tab descriptions

Related work items: #23044, #23045
2026-05-14 15:03:36 +00:00
Robert Bond c2ae6964f9 Merged PR 2318: auth stabilisation: extract shared myportal auth guard helper
auth stabilisation: extract shared myportal auth guard helper

ntroduces a small shared SSR helper (resolveMyPortalAuthContext) to standardize common myportal auth/session guards (session presence, session user identity, and pinsUser cookie) with preserved reason-coded diagnostics and signin redirect behavior. Migrates exactly two loaders (pages/myportal/searchresults.js, pages/myportal/addresssearchresults.js) to use the helper while keeping loader-specific UPSTREAM_FAILURE and CONTACT_LOOKUP_FAILED logic unchanged.

Related work items: #23020
2026-05-14 10:38:45 +00:00
Robert Bond 4fb62a6773 Merged PR 2317: Auth stabilisation: add reason-coded guards to MyPortal loaders
Adds incremental auth/session hardening across myportal loader paths (loadMyPortalAppealPage, searchresults, addresssearchresults) with explicit guard ordering and reason-coded diagnostics for missing session, missing session identity, missing cookie identity, contact/account lookup failures, and upstream dependency failures. Includes targeted Phase22 loader guard tests and keeps redirect behaviour policy unchanged.

Related work items: #23020
2026-05-14 10:27:12 +00:00
Robert Bond d0b2fd077a Merged PR 2315: Auth stabilistatiion and hardening
Related work items: #23020
2026-05-14 08:55:49 +00:00
Robert Bond d295a507ac Merged PR 2303: updated company name to pinswg_companyname
updated company name to pinswg_companyname

Related work items: #22774
2026-05-11 10:47:39 +00:00
Robert Bond 4f90c9a058 Merged PR 2302: updated text as per vickis request
updated text as per vickis request

Related work items: #22987
2026-05-11 09:52:19 +00:00
Robert Bond 0b02acf035 Merged PR 2300: updated final comments matching string
updated  final comments matching string

Related work items: #22842
2026-05-08 10:31:27 +00:00
Robert Bond 12a28a4be0 Merged PR 2296: added in statuscode and summary description
added in statuscode and summary description

Related work items: #22612, #22616
2026-05-07 10:20:28 +00:00
Robert Bond 1a2c3ede3b Merged PR 2295: resolved lint warnings
resolved lint warnings

Related work items: #22873
2026-05-05 17:13:53 +00:00
Robert Bond 46a0e0b185 Merged PR 2291: reps array refactor and update and tests
reps array refactor and update and tests

Related work items: #22873
2026-05-05 10:16:27 +00:00
Robert Bond 0ff2d69ba6 Merged PR 2290: add guard rail to check for locale and ensure verify request is language spec...
add guard rail to check for locale and ensure verify request is language specific

Related work items: #22303
2026-05-05 08:25:17 +00:00
Robert Bond d2e88b57d8 Merged PR 2288: add in company name for appellant and autofill agent details in new appeal
add in company name for appellant and autofill agent details in new appeal

Related work items: #22842
2026-04-30 16:25:03 +00:00
Robert Bond b596374c3c Merged PR 2286: allowed LPAs to make statements
allowed LPAs to make statements

Related work items: #22848
2026-04-30 09:15:53 +00:00
Robert Bond 9140476a2a Merged PR 2285: tweak to live link
tweak to live link

Related work items: #22423
2026-04-29 17:03:50 +00:00
Robert Bond baf5b1838d Merged PR 2283: Data driven status pages from CRM
Related work items: #22805
2026-04-29 15:29:54 +00:00
Robert Bond 1fd192b870 Merged PR 2282: replaced deadlineextensiuondate
replaced deadlineextensiuondate

Related work items: #22783
2026-04-29 08:02:29 +00:00
Robert Bond 8b187742af Merged PR 2281: update logic for reps arr take isAppellant from capacity selection not type o...
update logic for reps arr take isAppellant from capacity selection not type of involvement

Related work items: #22835
2026-04-28 16:07:35 +00:00
Robert Bond 421adafca3 Merged PR 2280: fix in place for app error
fix in place for app error

Related work items: #22834
2026-04-28 15:48:32 +00:00
Robert Bond a25ab433a6 Merged PR 2279: passed in case customer email in search and update rep logic to stop non owne...
passed in case customer email in search and update rep logic to stop non owners rasing reps on other owners cases

Related work items: #22095
2026-04-28 12:00:12 +00:00
Robert Bond 843e6adee6 Merged PR 2278: mocked design for status tab
mocked design for status tab

Related work items: #22805
2026-04-28 09:36:48 +00:00
Robert Bond b2945790b1 Merged PR 2274: case deadline and watch stream on results
case deadline and wat

Related work items: #22423, #22783
2026-04-27 13:07:46 +00:00
Robert Bond 138f03912b Merged PR 2273: check for existing record and not insert if exists
check for existing record and not insert if exists

Related work items: #22441
2026-04-24 15:56:54 +00:00
Robert Bond d406d97ddf Merged PR 2272: final comments added and update _IP_ precendence to be lower
final comments added and update _IP_ precendence to be lower

Related work items: #22573, #22661
2026-04-24 14:44:24 +00:00
Robert Bond f367f3342c Merged PR 2271: typo on videos and update portal sumamry fro removed fields
Related work items: #22698, #22813
2026-04-24 13:10:20 +00:00
Robert Bond d604d4f264 Merged PR 2269: added link for streaming events
added link for streaming events

Related work items: #22423
2026-04-23 14:34:08 +00:00
Robert Bond 43e37dc844 Merged PR 2268: adjusted styling tto center vertically dynamically
adjusted styling tto center vertically dynamically

Related work items: #22659
2026-04-23 04:59:06 +00:00
Robert Bond 7a14b7e8c9 Merged PR 2267: added ai guidance overlay
added ai guidance overlay

Related work items: #21744
2026-04-22 13:28:05 +00:00
Robert Bond b0004381ed Merged PR 2266: update media tabs display and query
update media tabs display and query

Related work items: #22698
2026-04-22 11:38:04 +00:00
Robert Bond 20782db916 Merged PR 2264: updatd createinvolvent call
updatd createinvolvent call

Related work items: #22441
2026-04-22 08:15:59 +00:00
Robert Bond f34ac3ba11 Merged PR 2263: createinvolvement updates contactinvolvement table
createinvolvement updates contactinvolvement table

Related work items: #22441
2026-04-21 16:16:31 +00:00
Robert Bond 5bf885933d Merged PR 2262: shows primary lpa and then lists additional lps below on the portal summary tab
shows primary lpa and then lists additional lps below on the portal summary tab

Related work items: #22699
2026-04-21 08:39:37 +00:00
Robert Bond 05d83822f2 Merged PR 2261: added in text and ui redesign for rep type selection
added in text and ui redesign for rep type selection

Related work items: #22574
2026-04-21 04:59:01 +00:00
Robert Bond 37a81522d5 Merged PR 2260: refactor(representations): complete slice-based refactor of representations flow
## Representations Refactor — Behaviour-Preserving Structural Improvements

This PR delivers a full refactor of the representations flow, improving structure, readability, and maintainability while preserving all existing behaviour.

The work was completed using a controlled, slice-based approach with strict guardrails and regression validation at each step. No changes have been made to user journeys, payloads, routing, or EN/CY behaviour.

The result is a cleaner, more maintainable codebase with reduced coupling and clearer separation of concerns, ready for future enhancements without increased risk.

---

## What Was Done

The refactor was delivered incrementally across the following slices:

- **R1** — Representation entry logic extraction
- **R2** — Page loader separation (SSR/data orchestration)
- **R3** — Journey step resolution extraction
- **R4** — Flow shell decomposition
- **R5** — Representation elements normalisation
- **R6** — Data/service layer cleanup
- **R7** — Summary rendering proof slice
- **R8** — Submission/finalisation boundary isolation
- **R9** — Summary rollout (Batch 1)

Each slice:
- was isolated to a single concern
- followed strict guardrails
- was validated before merge

Full detail is available in:
`context/representations-refactor-tracker.md`

---

## Key Improvements

- Reduced coupling across the representations journey
- Separated data loading, orchestration, and rendering concerns
- Simplified complex conditional logic into testable helpers
- Standardised summary rendering using shared primitives (`SummaryCard`, `SummaryRow`)
- Isolated submission/finalisation sequencing into explicit boundaries
- Improved overall readability and maintainability

---

## Behaviour Preservation

This refactor does **not** change:

- User journeys (APP / IP / Agent / LPA)
- Route and query behaviour
- Payload contracts and API interactions
- Redux state shape and usage
- Validation rules and messaging
- EN/CY behaviour
- File upload / PDF / email sequencing
- Linked-case logic

All changes are structural only.

---

## Validation

### Automated

- `npm run lint` — passed (warnings only, no new errors)
- `npm run test:reps` — passed (7/7)

### Manual

Validated end-to-end across:

- APP
- IP
- Agent
- LPA

Including:

- representation creation
- editing/resuming representations
- submission flow
- confirmation/completion behaviour
- summary rendering across case types
- EN/CY parity

---

## Risk Management

The refactor targeted several high-risk areas:

- Case summary entry logic
- Representation submission/finalisation sequencing
- Dual-mode entry (new vs existing representation)

Risk was controlled through:

- small, incremental slices
- one branch per slice
- regression validation per slice
- strict behaviour-preservation guardrails
- controlled rollout for summary rendering changes

---

## Reviewer Guidance

Suggested areas to focus on:

- End-to-end representation journey (create → submit → complete)
- S...
2026-04-20 13:09:07 +00: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
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 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
633 changed files with 112664 additions and 32515 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
+72 -65
View File
@@ -18,7 +18,8 @@ CLIENT_SECRET = .Jf8Q~tKMObCJjhh_hy3J5AIctFrBalU4FS~tdxk
RELAY_ROOT = https://ar-ns-lz-pedw-dev-uks-01.servicebus.windows.net/ar-hc-lz-pedw-dev-uks-01/
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 = "dv-pedw.crm11.dynamics.com"
CRMURL_VERSION = "v9.2"
# //New Test
CLIENT_ID = d40b9373-96c2-4b04-8bd8-c2a2fe6444c1
@@ -26,18 +27,19 @@ 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
# CLIENT_ID = ab6c4678-b31a-4eb3-b429-e039120e488a
# CLIENT_SECRET = V288Q~P1lFMvvX9Xnkx2pLixK~GOREH983Owqc1E
# 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"
# //New PreProd
CLIENT_ID = ab6c4678-b31a-4eb3-b429-e039120e488a
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"
CRMURL = "wg-pp-pedw.crm11.dynamics.com"
CRMURL_VERSION = "v9.2"
# //Prod Oauth WGO
@@ -49,28 +51,27 @@ 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"
# CRMURL = "crm2016.llc.gov.wales/PEDW"
# CRMURL_VERSION = "v8.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
@@ -79,94 +80,95 @@ NEXTAUTH_URL = $API_ROOT
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
# 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
// 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
# dev test sql server
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
# 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
// 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
// Prod sql server
# Prod sql server
# 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
# 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
SECRET = SuperSecret
//Azure Storage Account
# Azure Storage Account
AZURE_CLIENT_ID = $CLIENT_ID
AZURE_TENANT_ID = $TENANT
AZURE_CLIENT_SECRET = $CLIENT_SECRET
// CNR key
# CNR key
AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwcnruks"
AZURE_STORAGE_ACCOUNT_KEY = hRffHq4IwctpaTKdS/U33Xq6nyTDF1t++WJgIwsw7gzstktZin/rRwbTIAL9KKqesVsK7EHrTTG6+ASt4+7skw==
AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwcnruks.blob.core.windows.net
// new dev key
# new dev key
AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwdevuks"
AZURE_STORAGE_ACCOUNT_KEY = MmrsrCjBfop9pI2oImsP/+lJUWU2DrXsAB8nq5bOVROTmPovSGxjCjKw9+TAFr00k8WDUK9r6mVx+AStcMLyAA==
AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwdevuks.blob.core.windows.net
// New test key
# 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
// New preprod key
# AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwppeuks"
# AZURE_STORAGE_ACCOUNT_KEY = B4GRKr+cA9b9mNM/GHFivzhzflJIZltG8bMmooXlYvGYySPAspTTHistAT30sHipnR+hh6dqUc9q+ASttAjPRQ==
# AZURE_PEDW_STORAGE_ENDPOINT = https://sapltpedwppeuks.blob.core.windows.net
# New preprod key
AZURE_STORAGE_ACCOUNT_NAME = "sapltpedwppeuks"
AZURE_STORAGE_ACCOUNT_KEY = B4GRKr+cA9b9mNM/GHFivzhzflJIZltG8bMmooXlYvGYySPAspTTHistAT30sHipnR+hh6dqUc9q+ASttAjPRQ==
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
AZURE_PEDW_CONTAINER = "pedwapplications"
AZURE_PEDW_QUEUE_ENDPOINT = https://$AZURE_STORAGE_ACCOUNT_NAME.queue.core.windows.net
//gov.notify
//NOTIFY_API_KEY = pedwnextauthkey-90f93710-9dae-423e-aea1-11eccd0dc132-0c9469d9-d671-444d-9e9d-3e5a62001043
NOTIFY_API_KEY = pedwnextauthlivekey-90f93710-9dae-423e-aea1-11eccd0dc132-5c0ed187-754a-4b04-8c99-978e5710c2c1
# gov.notify
# NOTIFY_API_KEY = pedwnextauthkey-90f93710-9dae-423e-aea1-11eccd0dc132-0c9469d9-d671-444d-9e9d-3e5a62001043
# NOTIFY_API_KEY = pedwnextauthlivekey-90f93710-9dae-423e-aea1-11eccd0dc132-5c0ed187-754a-4b04-8c99-978e5710c2c1
NOTIFY_API_KEY = pedwnextauthlive01key-90f93710-9dae-423e-aea1-11eccd0dc132-72bfb1e1-c0b7-47cb-9887-3d47a4b11ad4
SHOW_DEBUG = true
// DEV Connection string
//NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=4b7568b3-70bd-4591-9dc2-267c0a6501d1;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=447d38b5-5d40-4238-8fb3-0ef4741b3db1
# DEV Connection string
NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=4b7568b3-70bd-4591-9dc2-267c0a6501d1;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=447d38b5-5d40-4238-8fb3-0ef4741b3db1
APPLICATIONINSIGHTS_CONNECTIONSTRING = InstrumentationKey=7a06e24a-793c-47bc-8987-ce80f180e035;IngestionEndpoint=https://uksouth-1.in.applicationinsights.azure.com/;LiveEndpoint=https://uksouth.livediagnostics.monitor.azure.com/;ApplicationId=9906b6d0-b791-4add-8d53-c827a534e1d4
# 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
// 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
# 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
// 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
# NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=a41b91bf-f7dc-4f44-bb66-a67df4eee435;IngestionEndpoint=https://uksouth-1.in.applicationinsights.azure.com/;LiveEndpoint=https://uksouth.livediagnostics.monitor.azure.com/;ApplicationId=473d0632-16f1-4065-8984-d6bac62402c0
NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=a41b91bf-f7dc-4f44-bb66-a67df4eee435;IngestionEndpoint=https://uksouth-1.in.applicationinsights.azure.com/;LiveEndpoint=https://uksouth.livediagnostics.monitor.azure.com/;ApplicationId=473d0632-16f1-4065-8984-d6bac62402c0
// Prod Connection string
//NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=61f358c2-8075-4164-b897-17bc6dd31fee;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=0c297349-396b-4dc6-8b56-85f5c680adc6
# Prod Connection string
# NEXT_PUBLIC_APP_INSIGHT = InstrumentationKey=61f358c2-8075-4164-b897-17bc6dd31fee;IngestionEndpoint=https://ukwest-0.in.applicationinsights.azure.com/;LiveEndpoint=https://ukwest.livediagnostics.monitor.azure.com/;ApplicationId=0c297349-396b-4dc6-8b56-85f5c680adc6
HIDEERRORLOGS = false
@@ -174,3 +176,8 @@ HIDEERRORLOGS = false
SHOWSIPS = true
ALLOWED_IPS=::1,203.0.113.42,198.51.100.17
UPLOAD_BATCH_COUNT = 5
NRWDOMAIN = rdbmedia.co.uk
MAP_TARGET = pedw-dev
+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"]
+85
View File
@@ -0,0 +1,85 @@
# 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/implementation-governance.md`
- `context/maintainability-adoption-programme.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
'
"""
}
}
}
}
}
+297 -262
View File
@@ -1,7 +1,7 @@
import { DefaultAzureCredential } from "@azure/identity";
import _ from "lodash";
import { hashAPIPath, consoleLogger } from ".";
import { getDocumentTypeFromFilename } from "../components/utils";
import { getDocumentTypeFromFilename, hasOwn } from "../components/utils";
const {
ContainerClient,
@@ -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);
@@ -257,7 +316,7 @@ export const createBlob = async (formContent, containerName, caseref) => {
formContent = JSON.parse(formContent);
let caseID = "";
caseID = _.has(formContent, "pinswg_name")
caseID = hasOwn(formContent, "pinswg_name")
? formContent.pinswg_name
: caseref;
@@ -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,33 +466,36 @@ 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
? "846040005"
: repName.indexOf("_Statement_") > 0
? "846040002"
: repName.indexOf("_Questionnaire_") > 0
? "846040001"
: repName.indexOf("_Comments") > 0
? "846040003"
: repName.indexOf("_Impact") > 0
? "846040001"
: "846040002";
const locationMap = [
{ key: "_IP_-_Comments_", value: "846040038" }, // most specific first
{ key: "_IP_", value: "846040005" },
{ key: "_Statement_", value: "846040002" },
{ key: "_Questionnaire_", value: "846040001" },
{ key: "_Comments", value: "846040003" },
{ key: "_Impact", value: "846040029" },
{ key: "_Consultation_Response_", value: "846040036" }
];
const whichLocation =
locationMap.find((item) => repName.includes(item.key))?.value ||
"846040002";
const tags = {
containerid: containerName,
caseID: caseID,
blobType: "Representation PDF",
ishareLocation: whichLocation,
documentLocationId: whichLocation,
documentLocationId: whichLocation
};
//console.log("the tags:", tags);
@@ -445,7 +509,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 +529,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 +541,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 +559,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 +569,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);
@@ -553,64 +617,46 @@ export const uploadFile = async (formContent, containerName, foldername) => {
);
console.log(data);
let whichLocation =
files[prop][0].fieldName.indexOf("_Statement_of_Case") >
0 ||
files[prop][0].fieldName.indexOf("_-_Statement_of_Case") >
0 ||
files[prop][0].fieldName.indexOf("_-_Application_Form") >
0 ||
files[prop][0].fieldName.indexOf(
"_-_Site_Ownership_Certificate"
) > 0 ||
files[prop][0].fieldName.indexOf("_-_Decision_Notice") >
0 ||
files[prop][0].fieldName.indexOf("_-_Site_Location_Plan") >
0 ||
files[prop][0].fieldName.indexOf(
"_-_Plans_Drawing_Documents"
) > 0 ||
files[prop][0].fieldName.indexOf(
"_-_Additional_Plans_Drawings_Documents"
) > 0 ||
files[prop][0].fieldName.indexOf(
"_-_Design_and_Access_Statement"
) > 0 ||
files[prop][0].fieldName.indexOf(
"_-_NSB_LPA_Additional_Documents"
) > 0 ||
files[prop][0].fieldName.indexOf("_-_LPA_Correspondence") >
0 ||
files[prop][0].fieldName.indexOf(
"_-_LPA_Original_Permission"
) > 0 ||
files[prop][0].fieldName.indexOf(
"_-_LPA's_Registration_Letter"
) > 0 ||
files[prop][0].fieldName.indexOf(
+"_-_Environmental_Statement"
) > 0 ||
files[prop][0].fieldName.indexOf("_-_Cost_of_Application") >
0 ||
files[prop][0].fieldName.indexOf(
"_-_Other_Relevant_Material"
) > 0 ||
files[prop][0].fieldName.indexOf(
"_-_S106_Agreement_or_Unilateral_Undertaking" > 0
)
? "846040000"
: files[prop][0].fieldName.indexOf("_IP_") > 0
? "846040005"
: files[prop][0].fieldName.indexOf("_Statement_") > 0
? "846040002"
: files[prop][0].fieldName.indexOf("_Questionnaire_") >
0
? "846040001"
: files[prop][0].fieldName.indexOf("_Comments_") > 0
? "846040003"
: files[prop][0].fieldName.indexOf("_Impact_") > 0
? "846040001"
: "846040000";
const fieldName = files[prop][0].fieldName;
const whichLocation =
[
// 1️⃣ LPA docs (grouped)
{
match: [
"_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"
},
{ match: ["_IP_-_Comments_"], value: "846040038" },
{
match: ["_Consultation_Response_"],
value: "846040036"
},
{ match: ["_Questionnaire_"], value: "846040001" },
{ match: ["_Statement_"], value: "846040002" },
{ match: ["_Comments_"], value: "846040003" },
{ match: ["_Impact_"], value: "846040001" },
{ match: ["_IP_"], value: "846040005" }
].find((rule) =>
rule.match.some((key) => fieldName.includes(key))
)?.value || "846040000";
const tags = {
containerid: containerName,
@@ -618,7 +664,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 +775,16 @@ 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_-_Comments_/, value: "846040038" },
{ 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 +805,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 +842,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 +887,7 @@ export const uploadPDFRepFiles = async (
containerid: containerName,
caseID: foldername.split("/")[0],
documentType: repType,
blobType: "RepresentationPDF",
blobType: "RepresentationPDF"
};
//console.log("the tags:", tags);
@@ -891,7 +940,7 @@ export const uploadPDFAppealFiles = async (
const tags = {
containerid: containerName,
caseID: foldername.split("/")[0],
blobType: "AppealPDF",
blobType: "AppealPDF"
};
//console.log("the tags:", tags);
@@ -930,7 +979,7 @@ export const uploadPDFCaseFiles = async (
const tags = {
containerid: containerName,
caseID: foldername.split("/")[0],
blobType: "AppealPDF",
blobType: "AppealPDF"
};
//console.log("the tags:", tags);
@@ -1136,7 +1185,7 @@ export const downloadAllRepsFiles = async (containerName, repsBlobObj) => {
return {
"@odata.count": blobCount,
"value": downloaded,
"value": downloaded
};
};
@@ -1175,7 +1224,7 @@ export const getCaseBlob = async (
const tags = {
containerid: containerName,
caseID: caseReference,
blobType: "Case",
blobType: "Case"
};
//console.log("the tags:", tags);
@@ -1206,7 +1255,7 @@ export const getProgressBlobs = async (containerName, caseReference) => {
let blobCount = 0;
for await (const blob of containerClient.listBlobsFlat({
prefix: caseReference,
prefix: caseReference
})) {
blobCount++;
}
@@ -1219,47 +1268,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,22 +1314,38 @@ 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 &&
blobObj.push({
"name": blob.name.split("/")[1],
"path": blob.name,
//"versionId": blob.versionId,
"caseObj":
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[0] +
"_case.json",
// "isCurrentVersion": blob.isCurrentVersion,
// "contentLength": blob.properties.contentLength,
// "contentType": blob.contentType,
// "lastModified": blob.properties.lastModified,
});
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,
//"versionId": blob.versionId,
"caseObj":
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[0] +
"_case.json"
// "isCurrentVersion": blob.isCurrentVersion,
// "contentLength": blob.properties.contentLength,
// "contentType": blob.contentType,
// "lastModified": blob.properties.lastModified,
});
}
// console.log(
@@ -1345,7 +1397,7 @@ export const getRepsBlobs = async (containerName) => {
includeMetadata: true,
includeSnapshots: false,
includeTags: true,
includeVersions: true,
includeVersions: true
};
let blobObj = [];
@@ -1354,14 +1406,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 +1449,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 +1525,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 +1569,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 +1630,7 @@ export const createRepCompleteMessage = async (
caseReference +
"/" +
filename +
"/files",
"/files"
};
console.log("======================", message, "=====================");
@@ -1621,7 +1656,7 @@ export const listContainers = async (
includeDeleted: false,
includeMetadata: true,
includeSystem: true,
prefix: containerNamePrefix,
prefix: containerNamePrefix
};
console.log(
"\n\n\n=================================================\n",
@@ -1664,7 +1699,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 +1785,7 @@ export async function listBlobHierarchicalForUser(containerClient) {
? lastModifiedDate.toISOString()
: null,
createdOn: createdOnDate ? createdOnDate.toISOString() : null,
displayDate,
displayDate
});
}
return blobNames;
@@ -1765,7 +1800,7 @@ export async function listContainersForUser(
includeDeleted: false,
includeMetadata: true,
includeSystem: true,
prefix: containerNamePrefix,
prefix: containerNamePrefix
};
const results = [];
@@ -1783,7 +1818,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/v9.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
}
};
};
+100
View File
@@ -0,0 +1,100 @@
import { hasOwn } from "../../components/utils";
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"
);
const errStr =
"\n\n/////////////////////////////////////////////////\nServer Error\n" +
(hasOwn(err, "name") ? `Name: ${err.name}\n` : "") +
(hasOwn(err, "code") ? `Code: ${err.code}\n` : "") +
(hasOwn(err, "query") ? `Query: ${err.query}\n` : "") +
(hasOwn(err, "message") ? `Message: ${err.message}\n` : "") +
(err?.request?.url ? `request.url: ${err.request.url}\n` : "") +
(err?.response?.status ? `Status: ${err.response.status}\n` : "") +
(err?.response?.statusText
? `StatusText: ${err.response.statusText}\n`
: "") +
(err?.response?.request?.socket?.remoteAddress
? `Request IP: ${err.response.request.socket.remoteAddress}\n`
: "") +
(err?.response?.data?.error?.message
? `Message: ${redactSensitive(err.response.data.error.message)}\n`
: "") +
(err?.response?.config?.url
? `Request URL: ${redactSensitive(err.response.config.url)}\n`
: "") +
(err?.response?.headers?.date
? `Request Time: ${err.response.headers.date}\n`
: "") +
(err?.config?.url
? `Axios config url: ${redactSensitive(err.config.url)}\n`
: "") +
(err?.message
? `Axios message: ${redactSensitive(err.message)}\n`
: "") +
(err?.config?.data
? `Request 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_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_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_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_dnsids_value,pinswg_startdateofevent,pinswg_typeofevent";
return "&$select=pinswg_casedeadline,pinswg_sipseiarequired,pinswg_anticipatedgridrefeasting,pinswg_anticipatedgridrefnorthing,_pinswg_appellant_value,pinswg_name,_pinswg_associatedlpa_value,pinswg_choiceofprocedure,pinswg_decision,pinswg_projectdescription,pinswg_projectlocation,pinswg_recommendation,statuscode,_pinswg_sipscase_value,pinswg_writtenacceptanceofnotification,pinswg_validnoticeofintentiontosubmit,pinswg_deadlineforsubmissionofapplication,pinswg_screeningstartdate,pinswg_eiatargetdate,pinswg_targetextended,pinswg_directionissueddate,pinswg_scopingstartdate,pinswg_scopingtargetdate,pinswg_scopingtargetextended,pinswg_scopingoutcomeissueddate,pinswg_essubmitted,pinswg_deemedmarinelicenseappliedfor,pinswg_validationdeadlineextension,pinswg_consultationopen,pinswg_consultationclose,pinswg_reportissuedtowelshministers,pinswg_decisionissuedtoapplicant,pinswg_decisionoutcome,pinswg_redetermination,pinswg_redeterminationconsultationdeadline,pinswg_reportdeadline,pinswg_casesubmissiondate,pinswg_preexaminationmeetingneeded,pinswg_consultaionpublicitydeadline,pinswg_endofexaminationstartofreporting,pinswg_compulsoryacquisitionvariationsubmitted,pinswg_compulsoryacquisitionvariationaccepted,pinswg_schemeorderamendmentrequestaccepted,pinswg_schemeorderamendmentrequestsubmitted";
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
};
+87
View File
@@ -0,0 +1,87 @@
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);
}
};
export const getStatusByAppeal = async () => {
try {
return await getJson(
BASE_URL + "/api/admin/getStatusCountsByAppeal_api"
);
} catch (error) {
return logAndReturnResponse(error);
}
};
export const getStatusByAppealLPA = async () => {
try {
return await getJson(
BASE_URL + "/api/admin/getStatusCountsByAppealAndLPA_api"
);
} catch (error) {
return logAndReturnResponse(error);
}
};
+15
View File
@@ -0,0 +1,15 @@
import {
getNewAppeals,
getNewAppealsPage,
getNewDocumentsPaged,
getStatusByAppeal,
getStatusByAppealLPA
} from "./adminDirectService";
export {
getNewAppeals,
getNewAppealsPage,
getNewDocumentsPaged,
getStatusByAppeal,
getStatusByAppealLPA
};
+284
View File
@@ -0,0 +1,284 @@
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/cases/get-case-message", {
id: searchString
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getIncidentbyID = (searchString) => {
const route = buildFileQuery("/api/cases/get-incident-by-id", {
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/cases/get-sips-events", {
caseid
});
return getJson(withBaseUrl(BASE_URL, route)).catch(logAndReturnResponse);
};
export const getSIPSMedia = async (caseid) => {
const route = buildFileQuery("/api/cases/get-sips-media", {
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/myportal/get-portal-module-details", {
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);
});
};
export const getCaseStatus = async (appealType, caseReference) => {
const route = buildFileQuery("/api/endpoint/getStatusCounts_api", {});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
+43
View File
@@ -0,0 +1,43 @@
import {
getCaseMessage,
getIncidentbyID,
getSIPSEvents,
getSIPSMedia,
getAppealID,
createNewCase,
createNewCaseBlob,
updateCase,
updateCaseBlob,
patchCase,
getCase,
getCaseByID,
getAppealPDFDocs,
getAppealPDFDocument,
getPartSavedAppeal,
getIsPublishedbyID,
getPortalModuleDetails,
getPortalModuleDetailsProxy,
getCaseStatus
} from "./caseDirectService";
export {
getCaseMessage,
getIncidentbyID,
getSIPSEvents,
getSIPSMedia,
getAppealID,
createNewCase,
createNewCaseBlob,
updateCase,
updateCaseBlob,
patchCase,
getCase,
getCaseByID,
getAppealPDFDocs,
getAppealPDFDocument,
getPartSavedAppeal,
getIsPublishedbyID,
getPortalModuleDetails,
getPortalModuleDetailsProxy,
getCaseStatus
};
+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 };
+313
View File
@@ -0,0 +1,313 @@
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/myportal/get-my-cases", {
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/myportal/get-my-lpa-cases", {
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/subscriptions/get-watched-cases", {
loggedInUserId
});
return getJson(withBaseUrl(BASE_URL, route)).catch((error) => {
consoleLogger(error);
});
};
export const getWatchedCasesProxy = (loggedInUserId) => {
const route = buildFileQuery("/api/subscriptions/get-watched-cases-proxy", {
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/subscriptions/create-watched-case");
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/subscriptions/delete-watched-case", {
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, involvement) => {
var queryUrl = buildFileQuery("/api/file/createrepinvolvement_api");
var data = {
"incidentid": caseid,
"contactid": contactid,
"involvement": involvement
};
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/search/basic-paged?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/search/advanced?searchstring=" +
JSON.stringify(searchString)
).catch(logAndReturnEmptyValueErrorResponse);
};
export const getAdvancedSearchPaged = (
searchString,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords
) => {
return getJson(
"/api/search/advanced-paged?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 groupedUrl = `/api/search/basic-details-paged?appealTypeName=${appealTypeName}&primaryIdAttribute=${primaryIdAttribute}&incidentID=${encodeURIComponent(
filter
)}`;
try {
const data = await getJson(groupedUrl);
return data.value || [];
} catch (error) {
consoleLogger(error);
return [];
}
};
export const getSearchDocumentDetails = (incidentID) => {
return getJson(
"/api/documents/get-search-document-details?incidentid=" + incidentID
).catch((error) => {
consoleLogger(error);
throw error;
});
};
export const getSearchDocumentTypes = (incidentID) => {
return getJson(
"/api/documents/get-search-document-types?incidentid=" + incidentID
).catch((error) => {
consoleLogger(error);
});
};
export const getSearchDocumentDetailsPaged = (
incidentID,
pageNumber,
orderBy,
fieldSort,
showNumberOfRecords,
documentType
) => {
return getJson(
"/api/documents/get-search-document-details-paged?incidentid=" +
incidentID +
"&pageNumber=" +
pageNumber +
"&orderby=" +
orderBy +
"&fieldSort=" +
fieldSort +
"&showNumberOfRecords=" +
showNumberOfRecords +
"&documentType=" +
documentType
).catch((error) => {
consoleLogger(error);
});
};
export const getLinkedCases = (parentIncidentid) => {
return getJson(
"/api/cases/get-linked-cases?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
+104
View File
@@ -0,0 +1,104 @@
import useTranslation from "next-translate/useTranslation";
import { parseCookies, setCookie } from "nookies";
import { useEffect, useRef, useState } from "react";
const COOKIE_NAME = "pedwAiPolicyNoticeHidden";
const COOKIE_DAYS = 7;
export default function AIPolicyNotice() {
let { t } = useTranslation();
const [hasCheckedCookie, setHasCheckedCookie] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const dismissButtonRef = useRef(null);
useEffect(() => {
const cookies = parseCookies();
const isHidden = cookies[COOKIE_NAME] === "true";
setIsOpen(!isHidden);
setHasCheckedCookie(true);
}, []);
useEffect(() => {
if (isOpen && dismissButtonRef.current) {
dismissButtonRef.current.focus();
}
}, [isOpen]);
const dismissNotice = () => {
const now = new Date();
const expDate = new Date(now);
expDate.setDate(now.getDate() + COOKIE_DAYS);
setCookie(null, COOKIE_NAME, "true", {
path: "/",
expires: expDate
});
setIsOpen(false);
};
if (!hasCheckedCookie || !isOpen) {
return null;
}
return (
<div
className="modal aiModal fade show"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "1rem"
}}
aria-hidden={false}
>
<div className="modal-dialog" style={{ margin: 0 }}>
<div
className="modal-content govuk-!-padding-6"
role="dialog"
aria-modal="true"
aria-labelledby="ai-policy-notice-title"
>
<h2
id="ai-policy-notice-title"
className="govuk-heading-m govuk-!-margin-bottom-3"
>
{t("home:ai-policy-notice-title")}
</h2>
<p className="govuk-body">
{t("home:ai-policy-notice-paragraph-one")}
</p>
<p className="govuk-body govuk-!-margin-bottom-4">
{t("home:ai-policy-notice-paragraph-two")}{" "}
<a
target="_blank"
rel="noopener noreferrer"
href={t(
"home:ai-policy-notice-paragraph-two-a-link"
)}
>
{t("home:ai-policy-notice-paragraph-two-a-label")}
</a>{" "}
{t("home:ai-policy-notice-paragraph-two-b")}
</p>
<button
ref={dismissButtonRef}
type="button"
className="govuk-button govuk-!-margin-bottom-0"
data-module="govuk-button"
onClick={dismissNotice}
>
{t("home:ai-policy-notice-dismiss-button")}
</button>
</div>
</div>
<div
className="modal-backdrop fade show"
style={{ opacity: 0.2 }}
></div>
</div>
);
}
+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)
);
+190 -179
View File
@@ -140,12 +140,12 @@ const RenderPickList = ({
input,
optionsArr,
meta: { touched, error },
errorMsg,
errorMsg
}) => {
let { t, lang } = useTranslation();
optionsArr = [
t("account:account-preferred-language-english") + ":846040001",
t("account:account-preferred-language-welsh") + ":846040000",
t("account:account-preferred-language-welsh") + ":846040000"
];
return (
<>
@@ -202,7 +202,7 @@ let PersonalDetails = (props) => {
load,
pristine,
reset,
submitting,
submitting
} = props;
let { t, lang } = useTranslation();
@@ -246,183 +246,194 @@ let PersonalDetails = (props) => {
return (
<>
{props.accountUpdatedComplete == false ? (
props.personalDetailsComplete == false ? (
<form onSubmit={handleSubmit(onHandleSubmit)}>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
{t("account:account-about-you-label")}
</h2>
</legend>
<div className="govuk-form-group ">
<span className="govuk-label ">
{t("account:account-role-label")}:{" "}
{
<div className="govuk-form-group">
{props.accountUpdatedComplete == false ? (
props.personalDetailsComplete == false ? (
<form onSubmit={handleSubmit(onHandleSubmit)}>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
{t("account:account-about-you-label")}
</h2>
</legend>
<div className="govuk-form-group ">
<span className="govuk-label ">
{t("account:account-role-label")}:{" "}
{
props.props.accountDetails
.accountDetails[
"pinswg_typeofinvolvement@OData.Community.Display.V1.FormattedValue"
]
}
</span>
</div>
<Field
name="pinswg_preferredlanguage"
component={RenderPickList}
datafieldname={"pinswg_preferredlanguage"}
optionsArr={["English", "Welsh"]}
className="govuk-input govuk-input--width-20 govuk-radios--inline"
validate={[required]}
label={t(
"account:account-preferred-language-label"
)}
errorMsg={t("account:is-required-label")}
/>
<Field
name="firstname"
id="firstname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t(
"account:account-first-name-label"
)}
errorMsg={t("account:is-required-label")}
/>
<Field
name="lastname"
id="lastname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t("account:account-last-name-label")}
errorMsg={t("account:is-required-label")}
/>
<Field
name="emailaddress1"
id="emailaddress1"
component={RenderTextfield}
type="email"
className="govuk-input govuk-input--width-20"
validate={[required, email]}
label={t(
"account:account-email-address-label"
)}
disabled
/>
<Field
name="telephone1"
id="telephone1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required, phoneNumber]}
label={t("account:account-telephone-label")}
/>
<Field
name="pinswg_companyname"
id="pinswg_companyname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
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"
)}
</h2>
</legend>
<Field
name="address1_line1"
id="address1_line1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:account-address-line-1-label"
)}
validate={[required]}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_line2"
id="address1_line2"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:account-address-line-2-label"
)}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_city"
id="address1_city"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t("account:account-town-city-label")}
validate={[required]}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_county"
id="address1_county"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t("account:account-county-label")}
validate={[required]}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_postalcode"
id="address1_postalcode"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-5"
label={t("account:account-postcode-label")}
validate={[required, postcode]}
/>
</fieldset>
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
<Link
href={
"/myportal?q=" +
props.props.accountDetails
.accountDetails[
"pinswg_typeofinvolvement@OData.Community.Display.V1.FormattedValue"
]
.loggedinUserId
}
</span>
className="govuk-link"
>
{t("common:cancel-link")}
</Link>
</div>
<Field
name="pinswg_preferredlanguage"
component={RenderPickList}
datafieldname={"pinswg_preferredlanguage"}
optionsArr={["English", "Welsh"]}
className="govuk-input govuk-input--width-20 govuk-radios--inline"
validate={[required]}
label={t(
"account:account-preferred-language-label"
)}
errorMsg={t("account:is-required-label")}
/>
<Field
name="firstname"
id="firstname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t("account:account-first-name-label")}
errorMsg={t("account:is-required-label")}
/>
<Field
name="lastname"
id="lastname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t("account:account-last-name-label")}
errorMsg={t("account:is-required-label")}
/>
<Field
name="emailaddress1"
id="emailaddress1"
component={RenderTextfield}
type="email"
className="govuk-input govuk-input--width-20"
validate={[required, email]}
label={t("account:account-email-address-label")}
disabled
/>
<Field
name="telephone1"
id="telephone1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required, phoneNumber]}
label={t("account:account-telephone-label")}
/>
<Field
name="company"
id="company"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
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")}
</h2>
</legend>
<Field
name="address1_line1"
id="address1_line1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:account-address-line-1-label"
)}
validate={[required]}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_line2"
id="address1_line2"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:account-address-line-2-label"
)}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_city"
id="address1_city"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t("account:account-town-city-label")}
validate={[required]}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_county"
id="address1_county"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t("account:account-county-label")}
validate={[required]}
errorMsg={t("account:is-required-label")}
/>
<Field
name="address1_postalcode"
id="address1_postalcode"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-5"
label={t("account:account-postcode-label")}
validate={[required, postcode]}
/>
</fieldset>
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
<Link
href={
"/myportal?q=" +
props.props.accountDetails.loggedinUserId
</form>
) : (
<>
<BuildCheckPersonalDetails
props={props}
setPersonalDetailsComplete={
setPersonalDetailsComplete
}
className="govuk-link"
>
{t("common:cancel-link")}
</Link>
</div>
</form>
setAccountUpdatedComplete={
setAccountUpdatedComplete
}
/>
</>
)
) : (
<>
<BuildCheckPersonalDetails
props={props}
setPersonalDetailsComplete={
setPersonalDetailsComplete
}
setAccountUpdatedComplete={
setAccountUpdatedComplete
}
/>
</>
)
) : (
<PersonalDetailsComplete props={props} />
)}
<PersonalDetailsComplete props={props} />
)}
</div>
</>
);
};
@@ -433,7 +444,7 @@ const mapStateToProps = (state) => {
searchResultsObj: state.searchResultsObj,
formData: state.formData,
appealType: state.appealType,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -442,7 +453,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentSection: (currentSection) => {
dispatch(setCurrentSection(currentSection));
},
}
};
};
@@ -473,12 +484,12 @@ const selector = formValueSelector("personalDetailsForm"); // <-- same as form n
PersonalDetails = reduxForm({
form: "personalDetailsForm",
enableReinitialize: true, // this is needed!!
destroyOnUnmount: false,
destroyOnUnmount: false
})(PersonalDetails);
// You have to connect() to any reducers that you wish to connect to yourself
PersonalDetails = connect((state) => ({
initialValues: state.accountDetails.accountDetails, // pull initial values from account reducer
initialValues: state.accountDetails.accountDetails // pull initial values from account reducer
}))(PersonalDetails);
export default PersonalDetails;
+4 -4
View File
@@ -11,7 +11,7 @@ const BuildCheckPersonalDetails = (props) => {
handleSubmit,
value,
setPersonalDetailsComplete,
setAccountUpdatedComplete,
setAccountUpdatedComplete
} = props;
let { t, lang } = useTranslation();
@@ -142,7 +142,7 @@ const BuildCheckPersonalDetails = (props) => {
<dd className="govuk-summary-list__value">
{
props.props.props.form.personalDetailsForm.values
.company
.pinswg_companyname
}
</dd>
<dd className="govuk-summary-list__actions">
@@ -354,7 +354,7 @@ const mapStateToProps = (state) => {
searchResultsObj: state.searchResultsObj,
formData: state.formData,
appealType: state.appealType,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -363,7 +363,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentSection: (currentSection) => {
dispatch(setCurrentSection(currentSection));
},
}
};
};
+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, userLang]);
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());
},
}
};
};
+161 -146
View File
@@ -66,7 +66,7 @@ const RegisterForm = (props) => {
setRegisterFormComplete,
setAccountCreatedComplete,
loggedInUserEmail,
emailaddress1,
emailaddress1
} = props;
let { t, lang } = useTranslation();
@@ -137,149 +137,164 @@ const RegisterForm = (props) => {
{props.accountCreatedComplete != true ? (
props.registerFormComplete == false ? (
<form onSubmit={handleSubmit(onHandleSubmit)}>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
{t(
"account:register-new-account-sub-heading-1"
)}{" "}
{loggedInUserEmail}
</h2>
</legend>
<Field
name="firstname"
id="firstname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t(
"account:register-new-account-first-name-label"
)}
errorMsg={t(
"account:register-new-account-first-name-label-errormsg"
)}
/>
<Field
name="lastname"
id="lastname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t(
"account:register-new-account-last-name-label"
)}
errorMsg={t(
"account:register-new-account-last-name-label-errormsg"
)}
/>
<Field
name="emailaddress1"
id="emailaddress1"
component={RenderTextfield}
type="email"
className="govuk-input govuk-input--width-20"
validate={[required, email]}
label={t(
"account:register-new-account-email-address-label"
)}
disabled
/>
<Field
name="telephone1"
id="telephone1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required, phoneNumber]}
label={t(
"account:register-new-account-phone-label"
)}
/>
</fieldset>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
{t(
"account:register-new-account-sub-heading-2"
)}
</h2>
</legend>
<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">
{t(
"account:register-new-account-sub-heading-1"
)}{" "}
{loggedInUserEmail}
</h2>
</legend>
<Field
name="firstname"
id="firstname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t(
"account:register-new-account-first-name-label"
)}
errorMsg={t(
"account:register-new-account-first-name-label-errormsg"
)}
/>
<Field
name="lastname"
id="lastname"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t(
"account:register-new-account-last-name-label"
)}
errorMsg={t(
"account:register-new-account-last-name-label-errormsg"
)}
/>
<Field
name="emailaddress1"
id="emailaddress1"
component={RenderTextfield}
type="email"
className="govuk-input govuk-input--width-20"
validate={[required, email]}
label={t(
"account:register-new-account-email-address-label"
)}
disabled
/>
<Field
name="telephone1"
id="telephone1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required, phoneNumber]}
label={t(
"account:register-new-account-phone-label"
)}
/>
<Field
name="company"
id="company"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
validate={[required]}
label={t(
"account:register-new-account-company-label"
)}
/>
</fieldset>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
{t(
"account:register-new-account-sub-heading-2"
)}
</h2>
</legend>
<Field
name="address1_line1"
id="address1_line1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:register-new-account-address-line-1-label"
)}
validate={[required]}
errorMsg={t(
"account:register-new-account-address-line-1-label-errormsg"
)}
/>
<Field
name="address1_line2"
id="address1_line2"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:register-new-account-address-line-2-label"
)}
/>
<Field
name="address1_city"
id="address1_city"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t(
"account:register-new-account-town-label"
)}
validate={[required]}
errorMsg={t(
"account:register-new-account-town-label-errormsg"
)}
/>
<Field
name="address1_county"
id="address1_county"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t(
"account:register-new-account-county-label"
)}
validate={[required]}
errorMsg={t(
"account:register-new-account-county-label-errormsg"
)}
/>
<Field
name="address1_postalcode"
id="address1_postalcode"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-5"
label={t(
"account:register-new-account-postcode-label"
)}
validate={[required, postcode]}
/>
</fieldset>
<Field
name="address1_line1"
id="address1_line1"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:register-new-account-address-line-1-label"
)}
validate={[required]}
errorMsg={t(
"account:register-new-account-address-line-1-label-errormsg"
)}
/>
<Field
name="address1_line2"
id="address1_line2"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-20"
label={t(
"account:register-new-account-address-line-2-label"
)}
/>
<Field
name="address1_city"
id="address1_city"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t(
"account:register-new-account-town-label"
)}
validate={[required]}
errorMsg={t(
"account:register-new-account-town-label-errormsg"
)}
/>
<Field
name="address1_county"
id="address1_county"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-10"
label={t(
"account:register-new-account-county-label"
)}
validate={[required]}
errorMsg={t(
"account:register-new-account-county-label-errormsg"
)}
/>
<Field
name="address1_postalcode"
id="address1_postalcode"
component={RenderTextfield}
type="text"
className="govuk-input govuk-input--width-5"
label={t(
"account:register-new-account-postcode-label"
)}
validate={[required, postcode]}
/>
</fieldset>
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
>
{t("common:continue-button")}
</button>
</div>
</div>
</div>
</form>
) : (
@@ -308,8 +323,8 @@ const mapStateToProps = (state, props) => {
appealType: state.appealType,
//form: state.form,
initialValues: {
emailaddress1: props.loggedInUserEmail,
},
emailaddress1: props.loggedInUserEmail
}
};
};
@@ -317,7 +332,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentSection: (currentSection) => {
dispatch(setCurrentSection(currentSection));
},
}
};
};
@@ -330,6 +345,6 @@ export default connect(
reduxForm({
form: "accountRegisterForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RegisterForm)
);
+3 -2
View File
@@ -3,6 +3,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import CRMError from "./crmError";
import SearchResults from "./search/addresssearchresults";
import { hasOwn } from "./utils";
export default function Search(props) {
const {
@@ -12,14 +13,14 @@ export default function Search(props) {
myportal,
watchedCases,
isLinkedCase,
showLoginCheck,
showLoginCheck
} = props;
let { t } = useTranslation();
const router = useRouter();
const { locale } = router;
var hasError = _.has(searchResultsObj.searchResultsObj, "errorCode")
var hasError = hasOwn(searchResultsObj.searchResultsObj, "errorCode")
? true
: false;
+79 -92
View File
@@ -6,17 +6,20 @@ import Link from "next/link";
import { useRouter } from "next/router";
import PaginationControl from "../../../components/search/pagination";
import { connect } from "react-redux";
import transLookup from "../../../data/lookuptranslations.json";
import {
setSearchResults,
setSearchDetails,
setSearchDetails
} from "../../../store/searchOutput/action";
import { getDetailsProxy, getSearchDetailsPaged } from "../../utils";
import { getAdvancedSearchPaged, getNewAppealsPage } from "../../../actions";
import { getDetailsProxy, getSearchDetailsPaged, hasOwn } from "../../utils";
import { getAdvancedSearchPaged } from "../../../actions/services/searchService";
import { getNewAppealsPage } from "../../../actions/services/adminService";
import resolveCrmDisplayValue from "../../../lib/i18n/crmDisplay/resolveCrmDisplayValue";
import {
setCurrentPage,
setCurrentReference,
setCurrentReference
} from "../../../store/currentView/action";
function AppealsTab(props) {
@@ -27,9 +30,15 @@ function AppealsTab(props) {
const [fieldSortState, setfieldSortState] = useState("desc");
const [loginToWatch, setLoginToWatch] = useState("true");
const searchDetailsObj = props.searchResultsObj.searchDetailsObj || {};
const resultsArr = props.searchResultsObj.searchResultsObj.value || [];
const searchResultsObj = props.searchResultsObj.searchResultsObj;
const {
searchResultsObj: searchResultsState,
setSearchResults,
setSearchDetails
} = props;
const searchDetailsObj = searchResultsState.searchDetailsObj || {};
const resultsArr = searchResultsState.searchResultsObj.value || [];
const searchResultsObj = searchResultsState.searchResultsObj;
const { t } = useTranslation();
const router = useRouter();
@@ -74,20 +83,14 @@ function AppealsTab(props) {
fieldSort,
selectedOption
).then((data) => {
props.setSearchResults(data); // ✅ use props, not import
setSearchResults(data);
return getSearchDetailsPaged(data).then((details) => {
props.setSearchDetails(details); // ✅ use props, not import
setSearchDetails(details);
setShowSpinnerState(false);
});
});
},
[
advancedSearch,
searchString,
selectedOption,
props.setSearchDetails,
props.setSearchResults,
]
[selectedOption, setSearchDetails, setSearchResults]
);
useEffect(() => {
@@ -105,7 +108,7 @@ function AppealsTab(props) {
orderByState,
searchLoaded,
getSearchPageResults,
router.query,
router.query
]);
const ResultsView = (resultsArr) => {
@@ -225,7 +228,7 @@ function AppealsTab(props) {
item.ticketnumber +
'")]',
json: searchDetailsObj,
eval: true,
eval: true
});
detailsObj = detailsObj[0] || {};
@@ -246,14 +249,16 @@ function AppealsTab(props) {
? "/fymhorth/achos"
: "/achos"
: myportal == true
? "/myportal/case"
: "/case") +
? "/myportal/case"
: "/case") +
"/" +
item.ticketnumber
}
className="govuk-link--no-underline"
onClick={() => {
setCurrentReference({
"statuscode":
item.statuscode,
"ticketnumber":
item.ticketnumber,
"currentReference":
@@ -272,10 +277,10 @@ function AppealsTab(props) {
)
? detailsObj.pinswg_speacialistcaseprocess
: detailsObj.hasOwnProperty(
"pinswg_specialistcaseprocess"
)
? detailsObj.pinswg_specialistcaseprocess
: "",
"pinswg_specialistcaseprocess"
)
? detailsObj.pinswg_specialistcaseprocess
: ""
});
}}
>
@@ -303,7 +308,7 @@ function AppealsTab(props) {
)}
:
</span>
{_.has(
{hasOwn(
detailsObj,
"pinswg_projectlocation"
)
@@ -314,15 +319,15 @@ function AppealsTab(props) {
) < 0
? detailsObj.pinswg_projectlocation
: router.locale == "cy"
? detailsObj.pinswg_projectlocation.split(
";"
)[1]
: detailsObj.pinswg_projectlocation.split(
";"
)[0]
? detailsObj.pinswg_projectlocation.split(
";"
)[1]
: detailsObj.pinswg_projectlocation.split(
";"
)[0]
: ""
: ""}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddressline1"
)
@@ -332,52 +337,52 @@ function AppealsTab(props) {
{/* {detailsObj.pinswg_siteaddressline1 !=
null && <br />} */}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddressline1"
) &&
detailsObj.pinswg_siteaddressline1 !=
null && <br />}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddressline1"
)
? detailsObj.pinswg_siteaddressline2
: ""}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddressline2"
) &&
detailsObj.pinswg_siteaddressline2 !=
null && <br />}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresstown"
)
? detailsObj.pinswg_siteaddresstown
: ""}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresstown"
) &&
detailsObj.pinswg_siteaddresstown !=
null && <br />}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresscounty"
)
? detailsObj.pinswg_siteaddresscounty
: ""}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresscounty"
) &&
detailsObj.pinswg_siteaddresscounty !=
null && <br />}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresspostcode"
)
@@ -395,13 +400,13 @@ function AppealsTab(props) {
? item.pinswg_appellantfirstname +
" " +
item.pinswg_appellantlastname
: _.has(detailsObj, [
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue",
])
? detailsObj[
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
]
: ""}
: hasOwn(detailsObj, [
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
])
? detailsObj[
"_pinswg_appellant_value@OData.Community.Display.V1.FormattedValue"
]
: ""}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
@@ -410,23 +415,17 @@ function AppealsTab(props) {
)}
:
</span>
{_.has(item, [
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue",
{hasOwn(item, [
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
])
? router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
] +
'")].value_cy',
json: transLookup,
eval: true,
})
: item[
? resolveCrmDisplayValue({
value: item[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
] || "N/A"
],
locale: router.locale,
translations: transLookup,
fallbackValue: "N/A"
})
: ""}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
@@ -436,20 +435,14 @@ function AppealsTab(props) {
)}
:
</span>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item[
"pinswg_appealcasetype@OData.Community.Display.V1.FormattedValue"
] +
'")].value_cy',
json: transLookup,
eval: true,
})
: item[
"pinswg_appealcasetype@OData.Community.Display.V1.FormattedValue"
] || "N/A"}
{resolveCrmDisplayValue({
value: item[
"pinswg_appealcasetype@OData.Community.Display.V1.FormattedValue"
],
locale: router.locale,
translations: transLookup,
fallbackValue: "N/A"
})}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
@@ -459,20 +452,14 @@ function AppealsTab(props) {
:
</span>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item[
"statuscode@OData.Community.Display.V1.FormattedValue"
] +
'")].value_cy',
json: transLookup,
eval: true,
})
: item[
"statuscode@OData.Community.Display.V1.FormattedValue"
] || "N/A"}
{resolveCrmDisplayValue({
value: item[
"statuscode@OData.Community.Display.V1.FormattedValue"
],
locale: router.locale,
translations: transLookup,
fallbackValue: "N/A"
})}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
{item[
@@ -555,13 +542,13 @@ const mapDispatchToProps = (dispatch) => {
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
}
};
};
const mapStateToProps = (state) => {
return {
searchResultsObj: state.searchResultsObj,
searchResultsObj: state.searchResultsObj
};
};
+273
View File
@@ -0,0 +1,273 @@
import { useMemo, useState } from "react";
import Link from "next/link";
export default function AppealStatusDashboard({ statusObj = [] }) {
const [openSections, setOpenSections] = useState({});
const sortedAppeals = useMemo(
() =>
[...statusObj].sort(
(a, b) => (b.caseCount || 0) - (a.caseCount || 0)
),
[statusObj]
);
const percentageOf = (value, total) =>
total ? `${(((value || 0) / total) * 100).toFixed(1)}%` : "0.0%";
const totalCases = sortedAppeals.reduce(
(sum, appeal) => sum + (appeal.caseCount || 0),
0
);
const largestAppeal = sortedAppeals[0];
const allStatuses = sortedAppeals.flatMap((appeal) =>
(appeal.statuses || []).map((status) => ({
...status,
appealType: appeal.value
}))
);
const statusTotals = allStatuses.reduce((acc, status) => {
if (!acc[status.statuscode]) {
acc[status.statuscode] = {
value: status.value,
statuscode: status.statuscode,
caseCount: 0
};
}
acc[status.statuscode].caseCount += status.caseCount || 0;
return acc;
}, {});
const mostCommonStatus = Object.values(statusTotals).sort(
(a, b) => b.caseCount - a.caseCount
)[0];
const maxAppealCount = largestAppeal?.caseCount || 1;
const allExpanded =
sortedAppeals.length > 0 &&
sortedAppeals.every((_, index) => openSections[index]);
const toggleSection = (index) => {
setOpenSections((current) => ({
...current,
[index]: !current[index]
}));
};
const showAll = () => {
const allOpen = {};
sortedAppeals.forEach((_, index) => {
allOpen[index] = true;
});
setOpenSections(allOpen);
};
const hideAll = () => {
setOpenSections({});
};
return (
<div className="govuk-grid-row">
<div className="govuk-grid-column-full">
<h1>Appeal case status dashboard</h1>
<div className="govuk-grid-row">
<DashboardCard title="Total cases" value={totalCases} />
<DashboardCard
title="Appeal types"
value={sortedAppeals.length}
/>
<DashboardCard
title="Largest appeal type"
value={largestAppeal?.caseCount || 0}
caption={largestAppeal?.value}
/>
<DashboardCard
title="Most common status"
value={mostCommonStatus?.caseCount || 0}
caption={mostCommonStatus?.value}
/>
</div>
<section className="govuk-!-margin-top-8">
<h2 className="govuk-heading-m">
Case counts and stage breakdown by appeal type
</h2>
<p className="govuk-body-s">
Select an appeal type row to show the case stage
breakdown.
</p>
<div className="govuk-accordion__controls">
<button
type="button"
className="govuk-accordion__show-all"
onClick={allExpanded ? hideAll : showAll}
>
<span className="govuk-accordion__show-all-text">
{allExpanded
? "Hide all sections"
: "Show all sections"}
</span>
</button>
</div>
<div
className="govuk-accordion appeal-stage-chart"
id="appeals-with-status-accordion"
>
{sortedAppeals.map((appeal, index) => {
const isOpen = !!openSections[index];
const barWidth =
((appeal.caseCount || 0) / maxAppealCount) *
100;
const sortedStatuses = [...(appeal.statuses || [])]
.filter((status) => (status.caseCount || 0) > 0)
.sort(
(a, b) =>
(b.caseCount || 0) - (a.caseCount || 0)
);
return (
<div
className={
isOpen
? "govuk-accordion__section govuk-accordion__section--expanded appeal-stage-chart__accordion-section"
: "govuk-accordion__section appeal-stage-chart__accordion-section"
}
key={appeal.appealType || index}
>
<div className="govuk-accordion__section-header">
<h2 className="govuk-accordion__section-heading">
<button
type="button"
className="govuk-accordion__section-button appeal-stage-chart__button"
id={`appeal-status-heading-${index}`}
aria-expanded={isOpen}
aria-controls={`appeal-status-content-${index}`}
onClick={() =>
toggleSection(index)
}
>
<span className="appeal-stage-chart__row">
<span className="appeal-stage-chart__label">
{appeal.value} (
{percentageOf(
appeal.caseCount,
totalCases
)}
)
</span>
<span className="appeal-stage-chart__bar-wrapper">
<span
className="appeal-stage-chart__bar"
style={{
width: `${barWidth}%`
}}
aria-hidden="true"
>
{sortedStatuses.map(
(
status,
statusIndex
) => (
<span
key={`${status.statuscode}-${statusIndex}`}
className={`appeal-stage-chart__segment appeal-stage-chart__segment--${
statusIndex %
6
}`}
style={{
width: `${
((status.caseCount ||
0) /
(appeal.caseCount ||
1)) *
100
}%`
}}
title={`${status.value}: ${status.caseCount}`}
/>
)
)}
</span>
<span className="appeal-stage-chart__count">
{appeal.caseCount ||
0}{" "}
cases
</span>
</span>
</span>
</button>
</h2>
</div>
<div
id={`appeal-status-content-${index}`}
className="govuk-accordion__section-content"
aria-labelledby={`appeal-status-heading-${index}`}
hidden={!isOpen}
>
<dl className="govuk-summary-list">
{sortedStatuses.map((status) => (
<div
className="govuk-summary-list__row"
key={status.statuscode}
>
<dt className="govuk-summary-list__key">
<Link
target="_blank"
href={`/advancedsearchresults?apt=${appeal.appealType}&statuscode=${status.statuscode}`}
>
{status.value}
</Link>
</dt>
<dd className="govuk-summary-list__value">
{status.caseCount || 0}{" "}
cases (
{percentageOf(
status.caseCount,
appeal.caseCount
)}{" "}
of this appeal type)
</dd>
</div>
))}
</dl>
</div>
</div>
);
})}
</div>
</section>
</div>
</div>
);
}
function DashboardCard({ title, value, caption }) {
return (
<div className="govuk-grid-column-one-quarter">
<div className="dashboard-card">
<p className="govuk-body-s dashboard-card__title">{title}</p>
<p className="govuk-heading-l dashboard-card__value">{value}</p>
{caption && (
<p className="govuk-body-s dashboard-card__caption">
{caption}
</p>
)}
</div>
</div>
);
}
@@ -0,0 +1,571 @@
import { useMemo, useState } from "react";
import Link from "next/link";
export default function AppealLpaStatusDashboard({ statusObj = [] }) {
const [openAppeals, setOpenAppeals] = useState({});
const [openLpaSections, setOpenLpaSections] = useState({});
const [openAppealLpas, setOpenAppealLpas] = useState({});
const [openLpaAppeals, setOpenLpaAppeals] = useState({});
const percentageOf = (value, total) =>
total ? `${(((value || 0) / total) * 100).toFixed(1)}%` : "0.0%";
const totalCases = useMemo(
() =>
statusObj.reduce((total, item) => total + (item.caseCount || 0), 0),
[statusObj]
);
const groupedAppeals = useMemo(() => {
const grouped = statusObj.reduce((acc, item) => {
const appealKey = item.appealType;
if (!acc[appealKey]) {
acc[appealKey] = {
appealType: item.appealType,
appealTypeName: item.appealTypeName,
caseCount: 0,
lpas: {}
};
}
acc[appealKey].caseCount += item.caseCount || 0;
const lpaKey = item.lpaId || "unknown-lpa";
if (!acc[appealKey].lpas[lpaKey]) {
acc[appealKey].lpas[lpaKey] = {
lpaId: item.lpaId,
lpaName: item.lpaName || "Unknown LPA",
caseCount: 0,
statuses: []
};
}
acc[appealKey].lpas[lpaKey].caseCount += item.caseCount || 0;
acc[appealKey].lpas[lpaKey].statuses.push({
statuscode: item.statuscode,
status: item.status,
caseCount: item.caseCount || 0
});
return acc;
}, {});
return Object.values(grouped)
.map((appeal) => ({
...appeal,
lpas: Object.values(appeal.lpas)
.map((lpa) => ({
...lpa,
statuses: lpa.statuses.sort(
(a, b) => (b.caseCount || 0) - (a.caseCount || 0)
)
}))
.sort((a, b) => (b.caseCount || 0) - (a.caseCount || 0))
}))
.sort((a, b) => (b.caseCount || 0) - (a.caseCount || 0));
}, [statusObj]);
const groupedLpas = useMemo(() => {
const grouped = statusObj.reduce((acc, item) => {
const lpaKey = item.lpaId || "unknown-lpa";
if (!acc[lpaKey]) {
acc[lpaKey] = {
lpaId: item.lpaId,
lpaName: item.lpaName || "Unknown LPA",
caseCount: 0,
appeals: {}
};
}
acc[lpaKey].caseCount += item.caseCount || 0;
const appealKey = item.appealType;
if (!acc[lpaKey].appeals[appealKey]) {
acc[lpaKey].appeals[appealKey] = {
appealType: item.appealType,
appealTypeName: item.appealTypeName,
caseCount: 0,
statuses: []
};
}
acc[lpaKey].appeals[appealKey].caseCount += item.caseCount || 0;
acc[lpaKey].appeals[appealKey].statuses.push({
statuscode: item.statuscode,
status: item.status,
caseCount: item.caseCount || 0
});
return acc;
}, {});
return Object.values(grouped)
.map((lpa) => ({
...lpa,
appeals: Object.values(lpa.appeals)
.map((appeal) => ({
...appeal,
statuses: appeal.statuses.sort(
(a, b) => (b.caseCount || 0) - (a.caseCount || 0)
)
}))
.sort((a, b) => (b.caseCount || 0) - (a.caseCount || 0))
}))
.sort((a, b) => (b.caseCount || 0) - (a.caseCount || 0));
}, [statusObj]);
const toggleAppeal = (appealType) => {
setOpenAppeals((current) => ({
...current,
[appealType]: !current[appealType]
}));
};
const toggleLpaSection = (lpaId) => {
setOpenLpaSections((current) => ({
...current,
[lpaId]: !current[lpaId]
}));
};
const toggleAppealLpa = (key) => {
setOpenAppealLpas((current) => ({
...current,
[key]: !current[key]
}));
};
const toggleLpaAppeal = (key) => {
setOpenLpaAppeals((current) => ({
...current,
[key]: !current[key]
}));
};
const sortedAppeals = useMemo(
() => [...statusObj].sort((a, b) => (b.lpaId || 0) - (a.lpaId || 0)),
[statusObj]
);
const allStatuses = sortedAppeals.flatMap((appeal) =>
(appeal.statuses || []).map((status) => ({
...status,
lpaId: appeal.lpaId,
lpaName: appeal.lpaName
}))
);
const statusTotals = allStatuses.reduce((acc, status) => {
if (!acc[status.statuscode]) {
acc[status.statuscode] = {
value: status.value,
statuscode: status.statuscode,
caseCount: 0
};
}
acc[status.statuscode].caseCount += status.caseCount || 0;
return acc;
}, {});
const largestAppeal = groupedLpas[0];
const mostCommonStatus = Object.values(groupedAppeals).sort(
(a, b) => b.caseCount - a.caseCount
)[0];
const maxAppealCount = largestAppeal?.caseCount || 1;
const maxGroupedAppealCount = groupedAppeals[0]?.caseCount || 1;
const allAppealsExpanded =
groupedAppeals.length > 0 &&
groupedAppeals.every((appeal) => openAppeals[appeal.appealType]);
const allLpasExpanded =
groupedLpas.length > 0 &&
groupedLpas.every((lpa) => openLpaSections[lpa.lpaId]);
const showAllAppeals = () => {
const allOpen = {};
groupedAppeals.forEach((appeal) => {
allOpen[appeal.appealType] = true;
});
setOpenAppeals(allOpen);
};
const showAllLpas = () => {
const allOpen = {};
groupedLpas.forEach((lpa) => {
allOpen[lpa.lpaId] = true;
});
setOpenLpaSections(allOpen);
};
const hideAllAppeals = () => {
setOpenAppeals({});
setOpenAppealLpas({});
};
const hideAllLpas = () => {
setOpenLpaSections({});
setOpenLpaAppeals({});
};
return (
<div className="govuk-grid-row">
<div className="govuk-grid-column-full">
<h1 className="">LPA Appeal case status dashboard</h1>
<div className="govuk-grid-row">
<DashboardCard title="Total cases" value={totalCases} />
<DashboardCard title="LPAs" value={groupedLpas.length} />
<DashboardCard
title="Largest number of appeals"
value={largestAppeal?.caseCount || 0}
caption={largestAppeal?.lpaName}
/>
<DashboardCard
title="Most common appeal"
value={mostCommonStatus?.caseCount || 0}
caption={mostCommonStatus?.appealTypeName}
/>
</div>
</div>
<section className="govuk-!-margin-top-8">
<h2 className="govuk-heading-m">
Appeal types and case stages by LPA
</h2>
<div className="govuk-accordion__controls">
<button
type="button"
className="govuk-accordion__show-all"
onClick={allLpasExpanded ? hideAllLpas : showAllLpas}
>
<span className="govuk-accordion__show-all-text">
{allLpasExpanded
? "Hide all sections"
: "Show all sections"}
</span>
</button>
</div>
<div
className="govuk-accordion"
id="lpa-appeal-status-accordion"
>
{groupedLpas.map((lpa) => {
const lpaOpen = !!openLpaSections[lpa.lpaId];
const width =
((lpa.caseCount || 0) / maxAppealCount) * 100;
return (
<div
className={
lpaOpen
? "govuk-accordion__section govuk-accordion__section--expanded"
: "govuk-accordion__section"
}
key={lpa.lpaId}
>
<div className="govuk-accordion__section-header">
<h3 className="govuk-accordion__section-heading">
<button
type="button"
className="govuk-accordion__section-button"
aria-expanded={lpaOpen}
onClick={() =>
toggleLpaSection(lpa.lpaId)
}
>
<div className="appeal-count-chart__row">
<div className="appeal-count-chart__label">
{lpa.lpaName} (
{percentageOf(
lpa.caseCount,
totalCases
)}
)
</div>
<div className="appeal-count-chart__bar-wrapper">
<span
className="appeal-count-chart__bar"
style={{
width: `${width}%`
}}
/>
<span className="appeal-count-chart__count">
{lpa.caseCount}
</span>
</div>
</div>
</button>
</h3>
</div>
<div
className="govuk-accordion__section-content"
hidden={!lpaOpen}
>
{lpa.appeals.map((appeal) => {
const key = `${lpa.lpaId}-${appeal.appealType}`;
const appealOpen =
!!openLpaAppeals[key];
return (
<div
className="lpa-status-block"
key={key}
>
<button
type="button"
className="lpa-status-block__button"
aria-expanded={appealOpen}
onClick={() =>
toggleLpaAppeal(key)
}
>
<span className="govuk-body-m">
{appeal.appealTypeName}
</span>
<span className="govuk-body-m">
{appeal.caseCount} cases
(
{percentageOf(
appeal.caseCount,
lpa.caseCount
)}
)
</span>
</button>
<div hidden={!appealOpen}>
<dl className="govuk-summary-list govuk-!-margin-bottom-4">
{appeal.statuses.map(
(status) => (
<div
className="govuk-summary-list__row"
key={
status.statuscode
}
>
<dt className="govuk-summary-list__key">
<Link
target="_blank"
href={`/advancedsearchresults?apt=${appeal.appealType}&lpa=${lpa.lpaId}&statuscode=${status.statuscode}`}
>
{
status.status
}
</Link>
</dt>
<dd className="govuk-summary-list__value">
{
status.caseCount
}{" "}
(
{percentageOf(
status.caseCount,
appeal.caseCount
)}
)
</dd>
</div>
)
)}
</dl>
</div>
</div>
);
})}
</div>
</div>
);
})}
</div>
</section>
<section className="govuk-!-margin-top-8">
<h2 className="govuk-heading-m">
Case stage by LPA and Appeal Type
</h2>
<p className="govuk-body-s">
Select an appeal type row to show the LPA and then case
stage breakdown.
</p>
<div className="govuk-accordion__controls">
<button
type="button"
className="govuk-accordion__show-all"
onClick={
allAppealsExpanded ? hideAllAppeals : showAllAppeals
}
>
<span className="govuk-accordion__show-all-text">
{allAppealsExpanded
? "Hide all sections"
: "Show all sections"}
</span>
</button>
</div>
<div
className="govuk-accordion"
id="appeal-lpa-status-accordion"
>
{groupedAppeals.map((appeal) => {
const appealOpen = !!openAppeals[appeal.appealType];
const width =
((appeal.caseCount || 0) / maxGroupedAppealCount) *
100;
return (
<div
className={
appealOpen
? "govuk-accordion__section govuk-accordion__section--expanded"
: "govuk-accordion__section"
}
key={appeal.appealType}
>
<div className="govuk-accordion__section-header">
<h2 className="govuk-accordion__section-heading">
<button
type="button"
className="govuk-accordion__section-button"
aria-expanded={appealOpen}
onClick={() =>
toggleAppeal(appeal.appealType)
}
>
<div className="appeal-count-chart__row">
<div className="appeal-count-chart__label">
{appeal.appealTypeName}{" "}
{/* {appeal.caseCount} cases */}
(
{percentageOf(
appeal.caseCount,
totalCases
)}
)
</div>
<div className="appeal-count-chart__bar-wrapper">
<span
className="appeal-count-chart__bar"
style={{
width: `${width}%`
}}
/>
<span className="appeal-count-chart__count">
{appeal.caseCount}
</span>
</div>
</div>
</button>
</h2>
</div>
<div
className="govuk-accordion__section-content"
hidden={!appealOpen}
>
{appeal.lpas.map((lpa) => {
const lpaKey = `${appeal.appealType}-${lpa.lpaId}`;
const lpaOpen =
!!openAppealLpas[lpaKey];
return (
<div
className="lpa-status-block"
key={lpaKey}
>
<button
type="button"
className="lpa-status-block__button"
aria-expanded={lpaOpen}
onClick={() =>
toggleAppealLpa(lpaKey)
}
>
<span className="govuk-body-m">
{lpa.lpaName}
</span>
<span className="govuk-body-m">
{lpa.caseCount} cases (
{percentageOf(
lpa.caseCount,
appeal.caseCount
)}
)
</span>
</button>
<div hidden={!lpaOpen}>
<dl className="govuk-summary-list govuk-!-margin-bottom-4">
{lpa.statuses.map(
(status) => (
<div
className="govuk-summary-list__row"
key={
status.statuscode
}
>
<dt className="govuk-summary-list__key">
<Link
target="_blank"
href={`/advancedsearchresults?apt=${appeal.appealType}&lpa=${lpa.lpaId}&statuscode=${status.statuscode}`}
>
{
status.status
}
</Link>
</dt>
<dd className="govuk-summary-list__value">
{
status.caseCount
}{" "}
(
{percentageOf(
status.caseCount,
lpa.caseCount
)}
)
</dd>
</div>
)
)}
</dl>
</div>
</div>
);
})}
</div>
</div>
);
})}
</div>
</section>
</div>
);
}
function DashboardCard({ title, value, caption }) {
return (
<div className="govuk-grid-column-one-quarter">
<div className="dashboard-card">
<p className="govuk-body-s dashboard-card__title">{title}</p>
<p className="govuk-heading-l dashboard-card__value">{value}</p>
{caption && (
<p className="govuk-body-s dashboard-card__caption">
{caption}
</p>
)}
</div>
</div>
);
}
+294
View File
@@ -0,0 +1,294 @@
// // components/admin/tabs/AccountsTab.js
// import Link from "next/link";
// export default function AppealsWithStatusTab({ statusObj }) {
// return (
// <div>
// <h1>User Accounts</h1>
// <dl className="govuk-summary-list govuk-!-margin-bottom-9 results">
// <div className="govuk-summary-list__row results-headings">
// <dd className="govuk-summary-list__key govuk-!-font-size-16">
// Name
// </dd>
// <dd className="govuk-summary-list__key govuk-!-font-size-16">
// Last accessed
// </dd>
// </div>
// {statusObj.map((appeal) => (
// <div key={appeal.id} className="govuk-summary-list__row">
// <dd className="govuk-summary-list__value govuk-!-font-size-16 ">
// <Link
// target="_blank"
// href={`/auth/signin2?email=${appeal.value}`}
// >
// {appeal.value}
// </Link>
// <br />
// <dl className="govuk-summary-list govuk-!-margin-bottom-9 govuk-!-margin-left-9 results">
// <div className="govuk-summary-list__row results-headings">
// <dd className="govuk-summary-list__key govuk-!-font-size-16">
// Name
// </dd>
// <dd className="govuk-summary-list__key govuk-!-font-size-16">
// Last accessed
// </dd>
// </div>
// {appeal.statuses.map((statuses) => (
// <div
// key={appeal.id}
// className="govuk-summary-list__row"
// >
// <dd className="govuk-summary-list__value govuk-!-font-size-16 ">
// {statuses.value}
// </dd>
// <dd className="govuk-summary-list__value govuk-!-font-size-16 ">
// Total: {statuses.caseCount}
// </dd>
// </div>
// ))}
// </dl>
// </dd>
// <dd className="govuk-summary-list__value govuk-!-font-size-16 ">
// Total: {appeal.caseCount}
// </dd>
// </div>
// ))}
// </dl>
// </div>
// );
// }
import { useState } from "react";
import Link from "next/link";
export default function AppealsWithStatusTab({ statusObj = [] }) {
const [openSections, setOpenSections] = useState({});
const toggleSection = (index) => {
setOpenSections((current) => ({
...current,
[index]: !current[index]
}));
};
const showAll = () => {
const allOpen = {};
statusObj.forEach((_, index) => {
allOpen[index] = true;
});
setOpenSections(allOpen);
};
const hideAll = () => {
setOpenSections({});
};
const allExpanded =
statusObj.length > 0 &&
statusObj.every((_, index) => openSections[index]);
const sortedAppeals = [...statusObj].sort(
(a, b) => (b.caseCount || 0) - (a.caseCount || 0)
);
const maxCount = sortedAppeals[0]?.caseCount || 1;
return (
<div>
<h1 className="govuk-heading-l">Case status by appeal type</h1>
<div className="govuk-!-margin-bottom-6">
<h2 className="govuk-heading-m">Case counts by appeal type</h2>
<dl className="appeal-count-chart">
{sortedAppeals.map((appeal) => {
const maxCount = sortedAppeals[0]?.caseCount || 1;
const width =
((appeal.caseCount || 0) / maxCount) * 100;
return (
<div
className="appeal-count-chart__row"
key={appeal.appealType}
>
<dt className="appeal-count-chart__label">
{appeal.value}
</dt>
<dd className="appeal-count-chart__bar-wrapper">
<span
className="appeal-count-chart__bar"
style={{ width: `${width}%` }}
/>
<span className="appeal-count-chart__count">
{appeal.caseCount || 0}
</span>
</dd>
</div>
);
})}
</dl>
</div>
<div className="govuk-!-margin-bottom-6">
<h2 className="govuk-heading-m">Case stages by appeal type</h2>
<dl className="appeal-stage-chart">
{sortedAppeals.map((appeal) => (
<div
className="appeal-stage-chart__row"
key={appeal.appealType}
>
<dt className="appeal-stage-chart__label">
{appeal.value}
</dt>
<dd className="appeal-stage-chart__bar-wrapper">
<div
className="appeal-stage-chart__bar"
style={{
width: `${((appeal.caseCount || 0) / maxCount) * 100}%`
}}
aria-label={`${appeal.value}, ${appeal.caseCount || 0} cases`}
>
{(appeal.statuses || [])
.filter(
(status) => status.caseCount > 0
)
.sort(
(a, b) => b.caseCount - a.caseCount
)
.map((status, index) => (
<span
key={status.statuscode}
className={`appeal-stage-chart__segment appeal-stage-chart__segment--${index % 6}`}
style={{
width: `${((status.caseCount || 0) / (appeal.caseCount || 1)) * 100}%`
}}
title={`${status.value}: ${status.caseCount}`}
/>
))}
</div>
<span className="appeal-stage-chart__count">
{appeal.caseCount || 0}
</span>
</dd>
</div>
))}
</dl>
</div>
<div className="govuk-accordion__controls">
<button
type="button"
className="govuk-accordion__show-all"
onClick={allExpanded ? hideAll : showAll}
>
<span className="govuk-accordion__show-all-text">
{allExpanded
? "Hide all sections"
: "Show all sections"}
</span>
</button>
</div>
<div className="govuk-accordion" id="appeals-with-status-accordion">
{[...statusObj]
.sort((a, b) => (b.caseCount || 0) - (a.caseCount || 0))
.map((appeal, index) => {
const isOpen = !!openSections[index];
return (
<div
className={
isOpen
? "govuk-accordion__section govuk-accordion__section--expanded"
: "govuk-accordion__section"
}
key={appeal.appealType || index}
>
<div className="govuk-accordion__section-header ">
<h2 className="govuk-accordion__section-heading">
<button
type="button"
className="govuk-accordion__section-button "
id={`appeal-status-heading-${index}`}
aria-expanded={isOpen}
aria-controls={`appeal-status-content-${index}`}
onClick={() => toggleSection(index)}
>
<span
style={{
display: "flex",
justifyContent:
"space-between",
alignItems: "center",
width: "100%",
paddingRight: "20px" // leaves room for accordion icon
}}
>
<span className="govuk-heading-s ">
{appeal.value}
</span>
<span
className="govuk-body-s"
style={{
fontWeight: "normal"
}}
>
{appeal.caseCount || 0}{" "}
cases
</span>
</span>
</button>
</h2>
</div>
<div
id={`appeal-status-content-${index}`}
className="govuk-accordion__section-content"
aria-labelledby={`appeal-status-heading-${index}`}
hidden={!isOpen}
>
<dl className="govuk-summary-list govuk-!-margin-left-9">
{(appeal.statuses || [])
.sort(
(a, b) =>
(b.caseCount || 0) -
(a.caseCount || 0)
)
.map((status) => (
<div
className="govuk-summary-list__row"
key={status.statuscode}
>
<dt className="govuk-summary-list__key">
<Link
target="_blank"
href={`/advancedsearchresults?apt=${appeal.appealType}&statuscode=${status.statuscode}`}
>
{status.value}
</Link>
</dt>
<dd className="govuk-summary-list__value">
{status.caseCount || 0}
</dd>
</div>
))}
</dl>
</div>
</div>
);
})}
</div>
</div>
);
}
+88 -144
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, hasOwn } from "../../utils";
import PaginationControl from "../../../components/case/pagination";
import DocumentLink from "../../utils/downloads";
import { useDownloadQueue } from "../../utils/downloadmanager";
@@ -15,177 +15,179 @@ 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";
import resolveCrmDisplayValue from "../../../lib/i18n/crmDisplay/resolveCrmDisplayValue";
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 +202,7 @@ const DocumentsTab = (props) => {
setDocumentDetails,
setCurrentPage,
docsOffline,
showFilteredDocs,
showFilteredDocs
} = props;
const router = useRouter();
@@ -236,7 +238,7 @@ const DocumentsTab = (props) => {
setCheckedItems((prev) => ({
...prev,
[name]: checked,
[name]: checked
}));
setSelectAll(false);
};
@@ -246,7 +248,7 @@ const DocumentsTab = (props) => {
setCheckedOriginItems((prev) => ({
...prev,
[name]: checked,
[name]: checked
}));
setSelectOriginAll(false);
};
@@ -305,37 +307,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;
@@ -427,19 +399,11 @@ const DocumentsTab = (props) => {
className="govuk-label-s govuk-checkboxes__label"
htmlFor={item.Value}
>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item.Label +
'")].value_cy',
json: transLookup,
eval: true,
})
: item.Label ||
t(
"case:summary-no-date-entered-label"
)}{" "}
{resolveCrmDisplayValue({
value: item.Label,
locale,
translations: transLookup
})}
</label>
</div>
);
@@ -500,19 +464,11 @@ const DocumentsTab = (props) => {
className="govuk-label-s govuk-checkboxes__label"
htmlFor={"origin_" + item.Value}
>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item.Label +
'")].value_cy',
json: transLookup,
eval: true,
})
: item.Label ||
t(
"case:summary-no-date-entered-label"
)}{" "}
{resolveCrmDisplayValue({
value: item.Label,
locale,
translations: transLookup
})}
</label>
</div>
);
@@ -526,7 +482,7 @@ const DocumentsTab = (props) => {
const checkedValues = [
...document.querySelectorAll(
".govuk-checkboxes__input:checked"
),
)
].map((e) => e.value);
const origins = checkedValues
@@ -727,7 +683,7 @@ const DocumentsTab = (props) => {
incidentid +
"')]",
json: item,
eval: true,
eval: true
});
detailsObj = item;
return (
@@ -784,7 +740,7 @@ const DocumentsTab = (props) => {
// )}
// :
// </span>
// {_.has(
// {hasOwn(
// detailsObj,
// "pinswg_name"
// )
@@ -819,7 +775,7 @@ const DocumentsTab = (props) => {
)}
:
</span>
{_.has(
{hasOwn(
detailsObj,
"pinswg_name"
)
@@ -835,7 +791,7 @@ const DocumentsTab = (props) => {
)}
:
</span>
{_.has(
{hasOwn(
detailsObj,
"pinswg_name"
)
@@ -852,23 +808,16 @@ const DocumentsTab = (props) => {
:
</span>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
] +
'")].value_cy',
json: transLookup,
eval: true,
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
] ||
t(
"case:summary-no-date-entered-label"
)}
{resolveCrmDisplayValue(
{
value: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
],
locale,
translations:
transLookup
}
)}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-14">
<span className="results-visually-hidden">
@@ -878,7 +827,7 @@ const DocumentsTab = (props) => {
:
</span>
{_.has(
{hasOwn(
detailsObj,
"createdon"
)
@@ -966,17 +915,16 @@ const DocumentsTab = (props) => {
)
.then((data) => data)
.then((data) => {
props.setDocumentDetails(data);
setDocumentDetails(data);
setShowSpinnerState(false);
});
},
[
props.incidentid,
selectedOption,
selectedDocumentType,
selectedWeeks,
selectedDocumentOrigin,
props.setDocumentDetails,
setDocumentDetails
]
);
@@ -987,7 +935,7 @@ const DocumentsTab = (props) => {
orderBy,
fieldSort
).then((data) => {
props.setDocumentDetails(data);
setDocumentDetails(data);
return data;
});
@@ -1090,7 +1038,7 @@ const DocumentsTab = (props) => {
selectedDocumentType,
selectedWeeks
).then((data) => {
props.setDocumentDetails(data);
setDocumentDetails(data);
setDocumentSearchState(true);
return data;
@@ -1102,16 +1050,14 @@ const DocumentsTab = (props) => {
//const searchDocumentResultsObj = docDetailsObj();
}, [
incidentid,
props.setDocumentDetails,
setDocumentDetails,
selectedOption,
selectedDocumentType,
fieldSortState,
orderByState,
getDocumentTypes,
getOrigin,
getDocumentResults,
lang,
selectedWeeks,
selectedWeeks
]);
function countFieldValue(obj, field, targetValue) {
@@ -1137,9 +1083,7 @@ const DocumentsTab = (props) => {
return (
<div className="govuk-grid-row">
<div className="govuk-grid-column-full">
<h2 className="govuk-heading-m ">
{t("case:documents-heading-label")}
</h2>
<h1 className="">{t("case:documents-heading-label")}</h1>
{/* <div className="">
MDU :{" "}
{countFieldValue(
@@ -1164,7 +1108,7 @@ const DocumentsTab = (props) => {
<select
onChange={(e) => {
setSelectedWeeks(e.target.value);
props.setCurrentPage(1);
setCurrentPage(1);
}}
value={selectedWeeks}
className="govuk-select"
@@ -1196,7 +1140,7 @@ const DocumentsTab = (props) => {
const mapStateToProps = (state) => {
return {
...state,
...state
};
};
@@ -1207,7 +1151,7 @@ const mapDispatchToProps = (dispatch) => {
},
setCurrentPage: (currentPage) => {
dispatch(setCurrentPage(currentPage));
},
}
};
};
+40 -10
View File
@@ -5,8 +5,11 @@ import { bytesToSize } from "../../../components/utils";
import {
deleteMyRepresentationsFromBlob,
deleteAwaitingSubmissionsFromBlob,
sendRepCompleteMessage,
} from "../../../actions";
getRepsFromBlobProxy
} from "../../../actions/services/documentService";
import { sendRepCompleteMessage } from "../../../actions/services/portalService";
import { parseCookies } from "nookies";
import { consoleLogger } from "../../../actions/core/logger";
import { formatBlobDates } from "../utils/adminHelper";
@@ -21,6 +24,33 @@ export default function StorageTab({ data, repCompleteCount }) {
setShowTmpFile(newState);
};
const deleteRepItem = (containerID, caseID, repfile) => {
let cookies = parseCookies();
//console.log("sssss", containerID, caseID, repfile);
deleteMyRepresentationsFromBlob(containerID, caseID, repfile)
.then((data) => {
return data;
})
.then(() => {
getRepsFromBlobProxy(containerID)
.then((data) => {
setMyRepresentations(data);
setMyRepresentationsDetails(data);
})
.catch((error) => {
consoleLogger(error);
return null;
})
.then(() => {
setCurrentView({
"viewName": "My Representations",
"viewKey": "myRepresentations"
});
});
});
};
return (
<div>
<h1>Storage Account Contents</h1>
@@ -74,9 +104,9 @@ export default function StorageTab({ data, repCompleteCount }) {
showRepJson && folder.hasRepJson
? { display: "block" }
: showTmpFile &&
folder.hasTmpFile
? { display: "block" }
: { display: "none" }
folder.hasTmpFile
? { display: "block" }
: { display: "none" }
}
>
<h4>{folder.folderPath || "Root"}</h4>
@@ -150,7 +180,7 @@ export default function StorageTab({ data, repCompleteCount }) {
headers:
{
"Content-Type":
"application/json",
"application/json"
},
body: JSON.stringify(
{
@@ -159,18 +189,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(
+107 -99
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,112 +34,120 @@ export async function fetchAdminStorageData() {
const users = await prisma.user.findMany();
const limit = pLimit(5);
let data = await Promise.all(
users.map(async (user) => {
const containers = await listContainersForUser(
blobServiceClient,
`${user.id}`,
user.email
);
return limit(async () => {
const containers = await listContainersForUser(
blobServiceClient,
`${user.id}`,
user.email
);
const containersWithHashes = await Promise.all(
containers.map(async (container) => {
const groupedByFolder = await container.blobs.reduce(
async (accP, blob) => {
const acc = await accP;
const folderPath =
getUpToLastSlash(blob.name.trim()) || "";
const containersWithHashes = await Promise.all(
containers.map(async (container) => {
const groupedByFolder = await container.blobs.reduce(
async (accP, blob) => {
const acc = await accP;
const folderPath =
getUpToLastSlash(blob.name.trim()) || "";
const hashedfilepath = hashAPIPath(
`/api/file/downloadblob?container=${
container.container
}&casefolderID=${folderPath}&blobname=${blob.name
.substring(blob.name.lastIndexOf("/") + 1)
.trim()}`,
process.env.HASHKEY
);
const fileNameOnly = blob.name.substring(
blob.name.lastIndexOf("/") + 1
);
const isTmpFile = fileNameOnly.startsWith("TMP-");
const blobWithHash = {
...blob,
hashedfilepath,
isTmpFile,
};
let folderGroup = acc.find(
(f) => f.folderPath === folderPath
);
if (!folderGroup) {
folderGroup = {
folderPath,
blobs: [],
hasRepJson: false,
repJsonBlob: null,
repComplete: false,
hasTmpFile: false,
};
acc.push(folderGroup);
}
folderGroup.blobs.push(blobWithHash);
if (isTmpFile) {
folderGroup.hasTmpFile = true;
}
// rep.json handling
if (blob.name.toLowerCase().endsWith("rep.json")) {
folderGroup.hasRepJson = true;
folderGroup.repJsonBlob = blobWithHash;
const containerClient =
blobServiceClient.getContainerClient(
const hashedfilepath = hashAPIPath(
`/api/file/downloadblob?container=${
container.container
);
const blockBlobClient =
containerClient.getBlockBlobClient(
blob.name
);
}&casefolderID=${folderPath}&blobname=${blob.name
.substring(
blob.name.lastIndexOf("/") + 1
)
.trim()}`,
process.env.HASHKEY
);
try {
const downloadResponse =
await blockBlobClient.download(0);
const downloaded = await streamToString(
downloadResponse.readableStreamBody
);
const json = JSON.parse(downloaded);
folderGroup.repComplete = Boolean(
json.repComplete
);
} catch (err) {
console.error(
"Error reading rep.json",
err
);
folderGroup.repComplete = false;
const fileNameOnly = blob.name.substring(
blob.name.lastIndexOf("/") + 1
);
const isTmpFile =
fileNameOnly.startsWith("TMP-");
const blobWithHash = {
...blob,
hashedfilepath,
isTmpFile
};
let folderGroup = acc.find(
(f) => f.folderPath === folderPath
);
if (!folderGroup) {
folderGroup = {
folderPath,
blobs: [],
hasRepJson: false,
repJsonBlob: null,
repComplete: false,
hasTmpFile: false
};
acc.push(folderGroup);
}
}
return acc;
},
Promise.resolve([])
);
folderGroup.blobs.push(blobWithHash);
return { ...container, folders: groupedByFolder };
})
);
if (isTmpFile) {
folderGroup.hasTmpFile = true;
}
return containersWithHashes.length > 0
? {
id: user.id,
email: user.email,
containers: containersWithHashes,
}
: null;
// rep.json handling
if (
blob.name.toLowerCase().endsWith("rep.json")
) {
folderGroup.hasRepJson = true;
folderGroup.repJsonBlob = blobWithHash;
const containerClient =
blobServiceClient.getContainerClient(
container.container
);
const blockBlobClient =
containerClient.getBlockBlobClient(
blob.name
);
try {
const downloadResponse =
await blockBlobClient.download(0);
const downloaded = await streamToString(
downloadResponse.readableStreamBody
);
const json = JSON.parse(downloaded);
folderGroup.repComplete = Boolean(
json.repComplete
);
} catch (err) {
console.error(
"Error reading rep.json",
err
);
folderGroup.repComplete = false;
}
}
return acc;
},
Promise.resolve([])
);
return { ...container, folders: groupedByFolder };
})
);
return containersWithHashes.length > 0
? {
id: user.id,
email: user.email,
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;
+276 -1135
View File
File diff suppressed because it is too large Load Diff
+4 -8
View File
@@ -41,15 +41,11 @@ const Case = (props) => {
docsOffline={props.props.docsOffline}
documentDetailsObj={props.documentDetailsObj}
showFilteredDocs={props.showFilteredDocs}
showLoginCheck={props.showLoginCheck}
eventsObj={props.eventsObj}
mediaObj={props.mediaObj}
mapTarget={props.mapTarget}
/>
{showRepresentations == true && (
<RepresentationList
incidentid={props.incidentid}
caseReference={props.caseReference}
representationsObj={props.representationsObj}
documentDetailsObj={props.documentDetailsObj}
/>
)}
</div>
</div>
</main>
+80 -58
View File
@@ -15,67 +15,89 @@ 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}>
<strong className="govuk-warning-text__text">
<span className="govuk-visually-hidden">Warning</span>
</strong>
<div>
<div className=" govuk-body-s">
<h2 className="govuk-heading-s">
{(
message.subject.match(
new RegExp(">>>", "g")
) || []
).length > 0
? router.locale == "cy"
? message.subject
.split("Banner ")[1]
.split(">>>")[1]
: message.subject
.split("Banner ")[1]
.split(">>>")[0]
: (
message.subject.match(
new RegExp(";", "g")
) || []
).length > 0
? router.locale == "cy"
? message.subject
.split("Banner ")[1]
.split(";")[1]
: message.subject
.split("Banner ")[1]
.split(";")[0]
: message.subject.split("Banner ")[1]}
</h2>
</div>
<div className="govuk-body-s">
{(
message.description.match(
new RegExp(">>>", "g")
) || []
).length > 0
? router.locale == "cy"
? 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}
</div>
<div className="govuk-body-xs govuk-!-margin-bottom-0 ">
{t("case:date-raised")}:{" "}
{formatDates(message.createdon)}
</div>
</div>
<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">
{t("case:notice-label")}
</span>
</strong>
<div>
<div className=" govuk-body-s">
<h2 className="govuk-heading-s">
{(
message.subject.match(
new RegExp(">>>", "g")
) || []
).length > 0
? router.locale == "cy"
? message.subject
.split("Banner ")[1]
.split(">>>")[1]
: message.subject
.split("Banner ")[1]
.split(">>>")[0]
: (
message.subject.match(
new RegExp(";", "g")
) || []
).length > 0
? router.locale == "cy"
? message.subject
.split("Banner ")[1]
.split(";")[1]
: message.subject
.split("Banner ")[1]
.split(";")[0]
: message.subject.split(
"Banner "
)[1]}
</h2>
</div>
<div className="govuk-body-s">
{(
message.description.match(
new RegExp(">>>", "g")
) || []
).length > 0
? router.locale == "cy"
? 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}
</div>
<div className="govuk-body-xs govuk-!-margin-bottom-0 ">
{t("case:date-raised")}:{" "}
{formatDates(message.createdon)}
</div>
</div>
</div>
)}
</div>
))}
</>
+37 -426
View File
@@ -5,20 +5,21 @@ 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, hasOwn } 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";
import DocumentLink from "../utils/downloads";
import { useDownloadQueue } from "../utils/downloadmanager";
import resolveCrmDisplayValue from "../../lib/i18n/crmDisplay/resolveCrmDisplayValue";
const DocumentDetails = (props) => {
let { t, lang } = useTranslation();
@@ -32,12 +33,14 @@ const DocumentDetails = (props) => {
setDocumentDetails,
setCurrentPage,
docsOffline,
showFilteredDocs,
showFilteredDocs
} = props;
const router = useRouter();
const { locale } = router;
const appealType = props.currentView?.caseReference?.appealType;
var documentDetailsArr = documentDetailsObj || [];
const { startDownload, getStatus } = useDownloadQueue(3);
const [selectedOption, setSelectedOption] = useState(10);
@@ -57,7 +60,7 @@ const DocumentDetails = (props) => {
setCheckedItems((prev) => ({
...prev,
[name]: checked,
[name]: checked
}));
setSelectAll(false);
};
@@ -90,383 +93,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);
const DocumentView = (documentDetailsArr) => {
documentDetailsArr = documentDetailsArr.value;
return (
<>
<p>
{documentDetailsObj["@odata.count"] > 1
? t("case:documents-count-label-1")
: t("case:documents-count-label-1a")}{" "}
{documentDetailsObj["@odata.count"]}{" "}
{documentDetailsObj["@odata.count"] > 1
? t("case:documents-count-label-2")
: t("case:documents-count-label-2a")}
</p>
<div className="documentFilters govuk-!-margin-top-5">
<div
id="recordDocumentTypeSelect"
className="govuk-grid-row govuk-!-margin-left-0"
>
<div className="govuk-form-group">
<label
className="govuk-label-s "
htmlFor="showDocumentType"
>
{t("case:documents-show-label")}
<select
onChange={(e) => {
setSelectedDocumentType(e.target.value);
setCurrentPage(1);
}}
value={selectedDocumentType}
className="govuk-select"
id="showDocumentType"
name="showDocumentType"
>
<option value="all" key="00">
{router.locale == "cy"
? jsonpath({
path: '$..[?(@ && @.value=="All")].value_cy',
json: transLookup,
eval: true,
})
: "All"}
</option>
{documentTypes.map((item, key) => {
return (
<option
key={key}
value={
item.pinswg_isharedocumentlocations
}
>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item.pinswg_isharedocumentlocationsLabel +
'")].value_cy',
json: transLookup,
eval: true,
})
: item.pinswg_isharedocumentlocationsLabel ||
t(
"case:summary-no-date-entered-label"
)}
</option>
);
})}
</select>
{t("case:documents-count-label-2")}
</label>
</div>
</div>
{documentDetailsObj["@odata.count"] > 5 && (
<div
id="recordCountSelect"
className="govuk-grid-row govuk-!-margin-left-0"
>
<div className="govuk-form-group">
<label
className="govuk-label-s "
htmlFor="showNumberOfRecords"
>
{t(
"search:searchresults-show-records-label-a"
)}
<select
onChange={(e) => {
setSelectedOption(e.target.value);
setCurrentPage(1);
}}
value={selectedOption}
className="govuk-select"
id="showNumberOfRecords"
name="showNumberOfRecords"
>
<option value="5">5</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="50">50</option>
</select>{" "}
{t(
"search:searchresults-show-records-label-b"
)}
</label>
</div>
</div>
)}
</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("case:documents-not-available-label")}
</strong>
</div>
)}
<dl className="documentList govuk-summary-list govuk-!-margin-bottom-9 results">
<div className="govuk-summary-list__row results-headings">
<dd className="govuk-summary-list__key govuk-!-font-size-16 ">
<button
type="button"
onClick={() => sortDataBy("pinswg_name")}
className={
orderByState == "pinswg_name"
? fieldSortState == "asc"
? `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortAsc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortDesc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
}
>
{t("case:documents-name-label")}
</button>
</dd>
<dd className="govuk-summary-list__key govuk-!-font-size-16">
{/* {t("case:documents-doc-type-label")} */}
<button
type="button"
onClick={() =>
sortDataBy("pinswg_isharedocumentlocations")
}
className={
orderByState ==
"pinswg_isharedocumentlocations"
? fieldSortState == "asc"
? `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortAsc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortDesc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
}
>
{t("case:documents-doc-type-label")}
</button>
</dd>
<dd className="govuk-summary-list__key govuk-!-font-size-16">
{/* {t("case:documents-date-published-label")} */}
<button
type="button"
onClick={() => sortDataBy("createdon")}
className={
orderByState == "createdon"
? fieldSortState == "asc"
? `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortAsc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16 sortDesc`
: `govuk-link govuk-link--no-underline govuk-!-font-weight-bold govuk-!-font-size-16`
}
>
{t("case:documents-date-published-label")}
</button>
</dd>
{/* <dd className="govuk-summary-list__key govuk-!-font-size-16">
{t("case:documents-size-label")}
</dd> */}
</div>
{showSpinnerState == true ? (
<div className="govuk-summary-list__row">
<div className="govuk-!-margin-top-4 govuk-!-margin-bottom-4 govuk-!-font-weight-bold govuk-!-font-size-20 ">
{t("common:spinner-fetching-data")}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Fetching data"
}
/>
</div>
</div>
) : (
documentDetailsArr.map((item, key) => {
let detailsObj = jsonpath({
path:
"$..[?(@ && @._pinswg_documentids_value=='" +
incidentid +
"')]",
json: item,
sanbox: {},
});
detailsObj = item;
return (
<div
className="govuk-summary-list__row"
key={key}
>
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
{showDownloadingState === key && (
<div className="coverLoadingLink downloading">
{/* <img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale ==
"cy"
? "Nôl data"
: "Fetching data"
}
/> */}
</div>
)}
{!ShowDocLinks ? (
<Link
scroll={false}
href={
detailsObj.pinswg_hashlink
}
key={key}
onClick={() => {
toggleDownLoadLink(key),
sendGAEvent(
"event",
"DownloadedFile",
{
caseReference:
props.caseReference,
filename:
detailsObj.pinswg_name,
}
);
}}
>
<span className="results-visually-hidden">
{t(
"case:documents-name-label"
)}
:
</span>
{_.has(
detailsObj,
"pinswg_name"
)
? detailsObj.pinswg_name
: ""}
</Link>
) : (
<>
<span className="results-visually-hidden">
{t(
"case:documents-name-label"
)}
:
</span>
{_.has(
detailsObj,
"pinswg_name"
)
? detailsObj.pinswg_name
: ""}
</>
)}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
{t("case:documents-doc-type-label")}
:
</span>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
] +
'")].value_cy',
json: transLookup,
eval: true,
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
] ||
t(
"case:summary-no-date-entered-label"
)}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
{t(
"case:documents-date-published-label"
)}
:
</span>
{_.has(
detailsObj,
"pinswg_latestpublisheddate"
)
? formatDates(
detailsObj.pinswg_latestpublisheddate
)
: ""}
</dd>
{/* <dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
{t("case:documents-size-label")}:
</span>
92
</dd> */}
</div>
);
})
)}
</dl>
<PaginationControl
showNoOfRecords={selectedOption}
currentPage={currentPage}
searchResultsObj={documentDetailsObj}
spinnerState={spinnerState}
orderBy={orderByState}
fieldSort={fieldSortState}
getDocumentResults={getDocumentResults}
/>
</>
);
};
let ShowDocLinks = getDocLink(docsOffline);
const FilteredDocumentView = (documentDetailsArr) => {
documentDetailsArr = documentDetailsArr.value;
@@ -577,19 +204,11 @@ const DocumentDetails = (props) => {
item.pinswg_isharedocumentlocations
}
>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
item.pinswg_isharedocumentlocationsLabel +
'")].value_cy',
json: transLookup,
eval: true,
})
: item.pinswg_isharedocumentlocationsLabel ||
t(
"case:summary-no-date-entered-label"
)}{" "}
{resolveCrmDisplayValue({
value: item.pinswg_isharedocumentlocationsLabel,
locale,
translations: transLookup
})}{" "}
- ({item.count})
</label>
</div>
@@ -604,7 +223,7 @@ const DocumentDetails = (props) => {
const data = [
...document.querySelectorAll(
".govuk-checkboxes__input:checked"
),
)
].map((e) => e.value);
setSelectedDocumentType(data);
setCurrentPage(1);
@@ -740,7 +359,7 @@ const DocumentDetails = (props) => {
incidentid +
"')]",
json: item,
eval: true,
eval: true
});
detailsObj = item;
return (
@@ -815,7 +434,7 @@ const DocumentDetails = (props) => {
)}
:
</span>
{_.has(
{hasOwn(
detailsObj,
"pinswg_name"
)
@@ -831,7 +450,7 @@ const DocumentDetails = (props) => {
)}
:
</span>
{_.has(
{hasOwn(
detailsObj,
"pinswg_name"
)
@@ -847,24 +466,13 @@ const DocumentDetails = (props) => {
)}
:
</span>
{router.locale == "cy"
? jsonpath({
path:
'$..[?(@ && @.value=="' +
detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
] +
'")].value_cy',
json: transLookup,
eval: true,
})
: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
] ||
t(
"case:summary-no-date-entered-label"
)}
{resolveCrmDisplayValue({
value: detailsObj[
"pinswg_isharedocumentlocations@OData.Community.Display.V1.FormattedValue"
],
locale,
translations: transLookup
})}
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16">
<span className="results-visually-hidden">
@@ -874,7 +482,7 @@ const DocumentDetails = (props) => {
:
</span>
{_.has(
{hasOwn(
detailsObj,
"pinswg_latestpublisheddate"
)
@@ -935,7 +543,7 @@ const DocumentDetails = (props) => {
props.incidentid,
selectedOption,
selectedDocumentType,
setDocumentDetails,
setDocumentDetails
]
);
@@ -1059,7 +667,7 @@ const DocumentDetails = (props) => {
orderByState,
getDocumentTypes,
getDocumentResults,
lang,
lang
]);
return (
<div className="govuk-grid-row">
@@ -1073,14 +681,17 @@ const DocumentDetails = (props) => {
<h3>
{showDocumentSearchState == false
? t("case:documents-checking-label")
: t("case:documents-non-available-label")}
: appealType === 846040002 ||
appealType === 846040011
? t(
"case:documents-non-available-app-label"
)
: t("case:documents-non-available-label")}
</h3>
</div>
</div>
) : showFilteredDocs == "true" ? (
FilteredDocumentView(documentDetailsArr)
) : (
DocumentView(documentDetailsArr)
FilteredDocumentView(documentDetailsArr)
)}
</div>
</div>
@@ -1089,7 +700,7 @@ const DocumentDetails = (props) => {
const mapStateToProps = (state) => {
return {
...state,
...state
};
};
@@ -1100,7 +711,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);
+264
View File
@@ -0,0 +1,264 @@
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";
import {
getLivePublishedEvent,
isEventLiveNow
} from "./summary/utils/liveEvent";
import { useRouter } from "next/router";
const EventsDetails = (props) => {
let { t } = useTranslation();
const router = useRouter();
const { locale } = router;
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;
}
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>
)}
{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>
)}
{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_eventaddressline1"
) && (
<>
<div
style={{
display: "flex"
}}
>
<b>
{t(
"case:event-address-label"
)}
</b>
:
<div
style={{
display:
"flex",
marginLeft:
"10px",
flexDirection:
"column"
}}
>
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddressline1"
) && (
<span>
{
item.pinswg_eventaddressline1
}
</span>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddressline2"
) && (
<span>
{
item.pinswg_eventaddressline2
}
</span>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresstown"
) && (
<span>
{
item.pinswg_eventaddresstown
}
</span>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresscounty"
) && (
<span>
{
item.pinswg_eventaddresscounty
}
</span>
)}
{hasOwnPropertyAndNotNull(
item,
"pinswg_eventaddresscounty"
) && (
<span>
{
item.pinswg_eventaddresspostcode
}
</span>
)}
</div>
</div>
</>
)}
</div>
{/* type of event
start date date of event - end date of event
if virtual event no address showNoOfRecords
show event address */}
{hasOwnPropertyAndNotNull(
item,
"pinswg_linktotheevent"
) &&
isEventLiveNow(item) && (
<div className="eventColumn">
<a
href={
locale === "en"
? item.pinswg_linktotheevent
: item.pinswg_linktotheeventstreamwelsh
}
className="govuk-link"
target="_blank"
rel="noopener noreferrer"
>
{t(
"case:live-event-link"
)}
</a>
</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);
+260
View File
@@ -0,0 +1,260 @@
import { useEffect, useMemo, useState } from "react";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { getBilingualText } from "./summary/utils/helpers";
/**
* 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 sourceMedia = 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 (sourceMedia || [])
.filter((x) => x?.pinswg_publishtoweb)
.map((item) => {
const link = isWelsh
? item.pinswg_videoplatformurlwelsh
: item.pinswg_videoplatformurl;
const videoId = extractYouTubeId(link);
if (!videoId) return null;
return {
key:
item.pinswg_documentid ||
item.pinswg_mediaid ||
`${item.pinswg_eventrecordingid}-${videoId}`,
videoId,
name: item.pinswg_eventrecordingname || "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);
}, [sourceMedia, 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-recording-name-label")}</b>:{" "}
{getBilingualText(selected.name, router.locale)}
</div>
{!!selected.description && (
<div className="govuk-body">
<b>
{t(
"case:event-recording-description-label"
)}
</b>
:{" "}
{getBilingualText(
selected.description,
router.locale
)}
</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">
{getBilingualText(
v.name,
router.locale
)}
</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,356 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { useEffect, useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderCondtionalRadioList,
RenderRichMultiline,
shouldDisableButton
} from "./representationElements";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
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,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const persistedRepresentationType =
currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
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");
const selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:consultation-from-an-agent-heading"
)}
kindLabelText={t(
"myrepresentations:kind-of-consultation-label"
)}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={appellantApplicantRepresentationArr}
/>
{isTypeConfirmed && (
<>
<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">
{selectedRepresentationType ==
"Consultation Response"
? t(
"myrepresentations:enter-comment-label"
)
: t(
"myrepresentations:consultation-comment-label"
)}
</p>
<fieldset
className="govuk-fieldset"
aria-describedby="commentBox-hint"
>
{selectedRepresentationType ==
"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.currentView.caseReference
.ticketnumber
}
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>
</>
)}
{isTypeConfirmed && (
<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,332 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { useEffect, useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderRichMultiline,
shouldDisableButton
} from "./representationElements";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
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,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const persistedRepresentationType =
currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
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)
);
const selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:consultation-from-an-appellant-heading"
)}
kindLabelText={t(
"myrepresentations:kind-of-consultation-label"
)}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={appellantApplicantRepresentationArr}
/>
{isTypeConfirmed && (
<div className="govuk-grid-row">
<div className="govuk-form-group">
<p className="govuk-body">
{selectedRepresentationType ==
"Consultation Response"
? t("myrepresentations:enter-comment-label")
: t(
"myrepresentations:consultation-comment-label"
)}
</p>
<fieldset
className="govuk-fieldset"
aria-describedby="commentBox-hint"
>
{selectedRepresentationType ==
"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.currentView.caseReference
.ticketnumber
}
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>
)}
{isTypeConfirmed && (
<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(
"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,369 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { useEffect, useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import {
FileUploadField,
RenderCondtionalRadioList,
RenderRichMultiline,
shouldDisableButton
} from "./representationElements";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
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,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const required = (value) => (value ? undefined : "Required");
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const persistedRepresentationType =
props.currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
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 selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:consultation-from-an-interested-person-heading"
)}
kindLabelText={t(
"myrepresentations:kind-of-consultation-label"
)}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={interestedPersonRepresentationArr}
validate={[required]}
errorMsg={t("myrepresentations:select-an-option-label")}
/>
{isTypeConfirmed && (
<>
<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">
{selectedRepresentationType ==
"Consultation Response"
? t(
"myrepresentations:enter-comment-label"
)
: t(
"myrepresentations:consultation-comment-label"
)}
</p>
<fieldset
className="govuk-fieldset"
aria-describedby="commentBox-hint"
>
{" "}
{selectedRepresentationType ==
"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.currentView.caseReference
.ticketnumber
}
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>
</>
)}
{isTypeConfirmed && (
<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,205 @@
import { Field } from "redux-form";
import router from "next/router";
import Link from "next/link";
const getRepresentationTypeHint = (option, t) => {
const map = {
"Statement": t("myrepresentations:rep-type-statement-hint"),
"Final comments": t("myrepresentations:rep-type-final-comments-hint"),
"Questionnaire": t("myrepresentations:rep-type-questionnaire-hint"),
"Consultation Response": t(
"myrepresentations:rep-type-consultation-hint"
),
"Local Impact Report": t("myrepresentations:rep-type-lir-hint"),
"Marine Impact Report": t("myrepresentations:rep-type-mir-hint")
};
return map[option] || "";
};
const getRepresentationTypeLabel = (option) => {
if (router.locale !== "cy") {
return option;
}
if (option === "Questionnaire") {
return "Holiadur";
}
if (option === "Statement") {
return "Datganiad";
}
if (option === "Final comments") {
return "Sylwadau terfynol";
}
return option;
};
const RenderRepresentationTypeRadioList = ({
name,
optionsArr,
errorMsg,
onRepresentationTypeChange,
input,
meta: { touched, error }
}) => {
return (
<div
className={
touched && error
? "govuk-form-group govuk-form-group--error"
: "govuk-form-group"
}
>
{touched && error && (
<span id={`${name}-error`} className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span>{" "}
{errorMsg}
</span>
)}
<div className="govuk-radios govuk-radios--small">
{optionsArr.map((option, index) => {
const optionId = `${name}_${index}`;
const hintId = `${optionId}-hint`;
return (
<div
key={option.value || optionId}
className="govuk-radios__item representation-type-option"
>
<input
className="govuk-radios__input"
id={optionId}
name={input.name}
type="radio"
value={option.value}
checked={input.value === option.value}
onChange={() => input.onChange(option.value)}
aria-describedby={
option.hint ? hintId : undefined
}
/>
<label
className="govuk-label govuk-radios__label govuk-!-font-weight-bold "
htmlFor={optionId}
>
{option.label}
{option.hint && (
<div
id={hintId}
className="govuk-hint govuk-!-margin-top-2"
>
{option.hint}
</div>
)}
</label>
</div>
);
})}
</div>
</div>
);
};
const RepresentationTypeSelectorBlock = ({
t,
headingText,
kindLabelText,
isTypeSelected,
selectedTypeDisplay,
representationType,
setTypeSelectionConfirmed,
onRepresentationTypeChange,
onTypeSelectionConfirmed,
optionsArr,
validate,
errorMsg
}) => {
const enhancedOptions = (optionsArr || [])
.filter((option) => option && option !== "Select...")
.map((option) => ({
value: option,
label: getRepresentationTypeLabel(option),
hint: getRepresentationTypeHint(option, t)
}));
const hasSelectableOptions = enhancedOptions.length > 0;
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 govuk-!-margin-bottom-2"
htmlFor="representationType"
>
{kindLabelText}
</label>
{hasSelectableOptions ? (
<>
<Field
name="representationType"
component={
RenderRepresentationTypeRadioList
}
onRepresentationTypeChange={
onRepresentationTypeChange
}
validate={validate}
optionsArr={enhancedOptions}
errorMsg={errorMsg}
/>
<button
type="button"
className="govuk-button"
data-module="govuk-button"
disabled={!representationType}
onClick={() => {
if (representationType) {
setTypeSelectionConfirmed(true);
onTypeSelectionConfirmed &&
onTypeSelectionConfirmed(
representationType
);
}
}}
>
{t("common:continue-button")}
</button>
</>
) : (
<>
<p className="govuk-body">
{t(
"myrepresentations:representation-type-unavailable-label"
)}
</p>
<Link className="govuk-link" href="/">
{t(
"myrepresentations:return-home-link-label"
)}
</Link>
</>
)}
</>
) : (
<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
@@ -2,21 +2,23 @@ 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 { useEffect, useState } from "react";
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) => {
@@ -35,9 +37,23 @@ const RepAgent = (props) => {
setSavingStatus,
savingStatus,
setFilesForRepresentations,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const persistedRepresentationType =
currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
if (JSON.stringify(formObj) != "{}") {
setRepFileName(formObj.representationForm.values);
@@ -62,61 +78,49 @@ const RepAgent = (props) => {
));
const required = (value) => (value ? undefined : "Required");
const selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
const handleSaveExit = () => {
let valuesObj = props.props.props.form[props.form].values || {};
delete valuesObj["_pinswg_appellant_value"];
updateRepresentation(valuesObj, false, true);
};
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>
<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"
? "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") && (
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-agent-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={appellantApplicantRepresentationArr}
/>
{isTypeConfirmed && (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
@@ -239,74 +243,17 @@ const RepAgent = (props) => {
</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",
"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>
{isTypeConfirmed && (
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
)}
</>
);
@@ -319,7 +266,7 @@ const mapDispatchToProps = (dispatch) => ({
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
});
export default connect(
@@ -329,6 +276,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RepAgent)
);
@@ -2,20 +2,22 @@ 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 { useEffect, useState } from "react";
import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
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) => {
@@ -34,9 +36,23 @@ const RepAppellant = (props) => {
setSavingStatus,
savingStatus,
setFilesForRepresentations,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const persistedRepresentationType =
currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
console.log(JSON.stringify(formObj) == "{}" ? "empty" : "value");
if (JSON.stringify(formObj) != "{}") {
@@ -61,59 +77,49 @@ const RepAppellant = (props) => {
index === self.findIndex((t) => t.name === value.name)
);
const selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
const handleSaveExit = () => {
let valuesObj = props.props.props.form[props.form].values || {};
delete valuesObj["_pinswg_appellant_value"];
updateRepresentation(valuesObj, false, true);
};
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"
? "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") && (
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-appellant-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={appellantApplicantRepresentationArr}
/>
{isTypeConfirmed && (
<div className="govuk-grid-row">
<div className="govuk-form-group">
<p className="govuk-body">
@@ -228,75 +234,18 @@ 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"
}
/>
</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>
{isTypeConfirmed && (
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
)}
</>
);
@@ -312,7 +261,7 @@ const mapDispatchToProps = (dispatch) => {
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
};
};
@@ -323,6 +272,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(
"myrepresentations:capacity-select-what-capacity-label"
)}
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,12 +40,17 @@ 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"
: "c49785e7-3217-40b6-b33f-dffc4e58d5c1";
? isWelsh
? "7d27b968-6f63-4334-b1c4-2e4a69554212"
: "c3f373ca-df9d-4967-bea0-3ad3a57f8dcc"
: isWelsh
? "6e2f0111-b352-4074-91a2-760403cd185b"
: "c49785e7-3217-40b6-b33f-dffc4e58d5c1";
const emailAddress =
props.props.props.props.accountDetails.accountDetails.emailaddress1;
@@ -55,13 +60,14 @@ 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
props.props.props.props.accountDetails.accountDetails.contactid,
props.props.currentView.representationCapacity
),
sendRepCompleteMessage(
props.props.props.props.accountDetails.containerID,
@@ -89,10 +95,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 +110,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 +137,7 @@ const mapStateToProps = (state) => {
currentView: state.currentView,
myRepresentations: state.myRepresentations,
initialValues: state.currentView.caseReference.repDetails,
accountDetails: state.accountDetails,
accountDetails: state.accountDetails
//form: state.form,
};
};
@@ -1,9 +1,9 @@
import _ from "lodash";
import useTranslation from "next-translate/useTranslation";
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";
@@ -12,17 +12,21 @@ import {
bytesToSize,
getThumbnailIconByExtension,
formatDates,
hasOwn,
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,40 +227,104 @@ const RepCompleteSubmit = (props) => {
arrUniq = removeEmptyObjects(arrUniq);
Object.assign(formObj.representationForm.values, {
"filesList": arrUniq,
"filesList": arrUniq
});
let filterFiles = [];
function showRepsEnded(endDate) {
let date = new Date();
date = new Date(date.toDateString());
let end = new Date(endDate);
end.setUTCHours(23);
end.setUTCMinutes(59);
end.setUTCSeconds(59);
end.toISOString();
if (!endDate) {
return false;
}
return end > date ? false : true;
const end = new Date(endDate);
if (Number.isNaN(end.getTime())) {
return false;
}
const today = new Date();
today.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
return end <= today;
}
const checkRepDatePassed = () => {
if (repFormData.representationType == "Questionnaire") {
if (repFormData.representationType === "Questionnaire") {
return false;
} else {
if (repFormData.representationType == "Final comments") {
return repFormData.hasOwnProperty(
"pinswg_finalcommentsduedate"
) && showRepsEnded(repFormData.pinswg_finalcommentsduedate)
? true
: false;
}
if (repFormData.representationType === "Final comments") {
return showRepsEnded(repFormData.pinswg_finalcommentsduedate);
}
if (repFormData.representationType === "Statement") {
return (
showRepsEnded(repFormData.pinswg_statementduedate) ||
showRepsEnded(repFormData.pinswg_endofrepresentationperiod)
);
}
return false;
};
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;
}
if (repFormData.representationType == "Statement") {
return repFormData.hasOwnProperty("pinswg_statementduedate") &&
showRepsEnded(repFormData.pinswg_statementduedate)
? true
: false;
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);
}
};
@@ -264,21 +343,33 @@ const RepCompleteSubmit = (props) => {
<div className="govuk-grid-row govuk-body">
<h1 className="govuk-heading-m ">
{" "}
{t(
"myrepresentations:rep-check-submit-heading"
)}
{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(
"myrepresentations:rep-check-your-rep-heading"
)}
{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(
"case:representation-representation-type"
)}
{repFormData.appealType == 846040002
? t(
"case:consultation-representation-type"
)
: t(
"case:representation-representation-type"
)}
</dt>
<dd className="govuk-summary-list__value">
@@ -287,12 +378,12 @@ const RepCompleteSubmit = (props) => {
"Questionnaire"
? "Holiadur"
: repFormData.representationType ==
"Statement"
? "Datganiad"
: repFormData.representationType ==
"Final comments"
? "Sylwadau terfynol"
: repFormData.representationType
"Statement"
? "Datganiad"
: repFormData.representationType ==
"Final comments"
? "Sylwadau terfynol"
: repFormData.representationType
: repFormData.representationType}
</dd>
@@ -314,7 +405,7 @@ const RepCompleteSubmit = (props) => {
</a> */}
</dd>
</div>
{_.has(
{hasOwn(
repFormData,
"representationOnBehalfOf"
) && (
@@ -328,7 +419,7 @@ const RepCompleteSubmit = (props) => {
<dd className="govuk-summary-list__value">
{
repFormData.representationOnBehalfOf
representationOnBehalfOfDisplay
}
</dd>
@@ -350,7 +441,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
});
setRepresentationSubmit(
false
@@ -403,7 +494,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
}
);
setRepresentationSubmit(
@@ -427,58 +518,63 @@ const RepCompleteSubmit = (props) => {
</>
)}
{repFormData.representationType !=
"Questionnaire" && (
<div className="govuk-summary-list__row">
<dt className="govuk-summary-list__key">
{t("case:representation-comments")}
</dt>
"Questionnaire" &&
repFormData.appealType != 846040002 && (
<div className="govuk-summary-list__row">
<dt className="govuk-summary-list__key">
{t(
"case:representation-comments"
)}
</dt>
<dd className="govuk-summary-list__value">
<span
className="multiline-elipsis"
dangerouslySetInnerHTML={{
__html: repFormData.representationComments,
}}
/>
</dd>
<dd className="govuk-summary-list__value">
<span
className="multiline-elipsis"
dangerouslySetInnerHTML={{
__html: repFormData.representationComments
}}
/>
</dd>
<dd className="govuk-summary-list__actions">
<a
className="govuk-link"
href="#"
onClick={() => {
// setSubmitBack();
<dd className="govuk-summary-list__actions">
<a
className="govuk-link"
href="#"
onClick={() => {
// setSubmitBack();
setCurrentReference({
"ticketnumber":
repFormData.ticketnumber,
"currentReference":
repFormData.caseRef,
"currentType":
"myRepresentations",
"incidentid":
repFormData.incidentID,
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
});
setRepresentationSubmit(
false
);
}}
>
{t("common:change-link-label")}
<span className="govuk-visually-hidden">
{" "}
{/* {FieldsTranslations(
setCurrentReference({
"ticketnumber":
repFormData.ticketnumber,
"currentReference":
repFormData.caseRef,
"currentType":
"myRepresentations",
"incidentid":
repFormData.incidentID,
"appealType":
repFormData.appealType,
"repDetails":
repFormData
});
setRepresentationSubmit(
false
);
}}
>
{t(
"common:change-link-label"
)}
<span className="govuk-visually-hidden">
{" "}
{/* {FieldsTranslations(
rows[0].value
)} */}
</span>
</a>
</dd>
</div>
)}
</span>
</a>
</dd>
</div>
)}
<div className="govuk-summary-list__row">
<dt className="govuk-summary-list__key">
{t("case:representation-files")}
@@ -539,7 +635,7 @@ const RepCompleteSubmit = (props) => {
"appealType":
repFormData.appealType,
"repDetails":
repFormData,
repFormData
}),
setRepresentationSubmit(
false
@@ -586,6 +682,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 +722,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 +817,11 @@ const RepCompleteSubmit = (props) => {
type="submit"
className="govuk-button"
data-module="govuk-button"
onClick={() => {
onClick={async () => {
// setRepresentationSubmitConfirmation(
// true
// );
await triggerQuestionnaireDownload();
setRepresentationSubmit(
true
);
@@ -704,7 +858,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 +867,7 @@ const mapDispatchToProps = (dispatch) => {
return {
setCurrentReference: (currentReference) => {
dispatch(setCurrentReference(currentReference));
},
}
};
};
@@ -1,6 +1,7 @@
import { JSONPath as jsonpath } from "jsonpath-plus";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { hasOwn } from "../../utils";
const RepDetails = (props) => {
let { t } = useTranslation();
@@ -8,7 +9,7 @@ const RepDetails = (props) => {
const router = useRouter();
const { locale } = router;
const { currentType, casesObj } = props;
const { currentType, casesObj, appealType } = props;
let { caseReference, ticketnumber } = props;
@@ -17,8 +18,8 @@ const RepDetails = (props) => {
? props.myRepresentations.myRepresentations
: props.searchResultsObj.searchDetailsObj
: props.currentType == "watchedCases"
? props.props.props.props.watchedCases.watchedCasesDetails
: props.props.props.searchResultsObj.searchDetailsObj;
? props.props.props.props.watchedCases.watchedCasesDetails
: props.props.props.searchResultsObj.searchDetailsObj;
var detailsObj = {};
@@ -31,7 +32,7 @@ const RepDetails = (props) => {
detailsObj = jsonpath({
path: '$..[?(@ && @.ticketnumber=="' + ticketnumber + '")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
detailsObj = detailsObj[0];
@@ -61,34 +62,43 @@ const RepDetails = (props) => {
{t("case:summary-site-address-label")}
</dt>
<dd className="govuk-summary-list__value">
{_.has(detailsObj, "pinswg_siteaddressline1")
{hasOwn(detailsObj, "pinswg_siteaddressline1")
? detailsObj.pinswg_siteaddressline1
: ""}
{_.has(detailsObj, "pinswg_siteaddressline1") &&
{hasOwn(
detailsObj,
"pinswg_siteaddressline1"
) &&
detailsObj.pinswg_siteaddressline1 !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddressline2")
{hasOwn(detailsObj, "pinswg_siteaddressline2")
? detailsObj.pinswg_siteaddressline2
: ""}
{_.has(detailsObj, "pinswg_siteaddressline2") &&
{hasOwn(
detailsObj,
"pinswg_siteaddressline2"
) &&
detailsObj.pinswg_siteaddressline2 !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddresstown")
{hasOwn(detailsObj, "pinswg_siteaddresstown")
? detailsObj.pinswg_siteaddresstown
: ""}
{_.has(detailsObj, "pinswg_siteaddresstown") &&
{hasOwn(detailsObj, "pinswg_siteaddresstown") &&
detailsObj.pinswg_siteaddresstown !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddresscounty")
{hasOwn(detailsObj, "pinswg_siteaddresscounty")
? detailsObj.pinswg_siteaddresscounty
: ""}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresscounty"
) &&
detailsObj.pinswg_siteaddresscounty !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddresspostcode")
{hasOwn(
detailsObj,
"pinswg_siteaddresspostcode"
)
? detailsObj.pinswg_siteaddresspostcode
: ""}
</dd>
@@ -97,10 +107,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,68 +324,84 @@ 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
className="govuk-radios__item"
data-children-count={key}
key={key}
className="govuk-!-margin-bottom-3"
>
<Field
id={id + "_" + index}
name={id}
component="input"
type="radio"
className="govuk-radios__input"
value={options[key]}
validate={[required]}
/>
<label
className="govuk-label govuk-radios__label"
htmlFor={id + "_" + index}
>
{options[key] == "Yes"
? t(
"myrepresentations:questionnaire-yes"
)
: t(
"myrepresentations:questionnaire-no"
)}
</label>
</div>
{value == "Yes" && options[key] == "Yes" && (
<div
className="govuk-radios__conditional "
id={"conditional-" + id}
className="govuk-radios__item"
data-children-count={key}
key={key}
>
<div
className="govuk-form-group"
key={"yes_group_" + key}
<Field
id={id + "_" + index}
name={id}
component="input"
type="radio"
className="govuk-radios__input"
value={normalizedOptionValue}
validate={[required]}
/>
<label
className="govuk-label govuk-radios__label"
htmlFor={id + "_" + index}
>
<label
className="govuk-label"
htmlFor={id + "_details"}
>
{t(
"myrepresentations:representation-onbehalfof-label"
)}
</label>
<Field
className="govuk-input govuk-!-width-one-half"
id={id + "_details"}
name={id + "_details"}
component={RenderTextfield}
type="text"
validate={[required]}
errorMsg={t(
"myrepresentations:is-required-label"
)}
/>
</div>
{options[key] == "Yes"
? t(
"myrepresentations:questionnaire-yes"
)
: t(
"myrepresentations:questionnaire-no"
)}
</label>
</div>
)}
</div>
))}
{isYesSelected &&
normalizedOptionValue === "Yes" && (
<div
className="govuk-radios__conditional "
id={"conditional-" + id}
>
<div
className="govuk-form-group"
key={"yes_group_" + key}
>
<label
className="govuk-label"
htmlFor={
id + "_details"
}
>
{t(
"myrepresentations:representation-onbehalfof-label"
)}
</label>
<Field
className="govuk-input govuk-!-width-one-half"
id={id + "_details"}
name={id + "_details"}
component={
RenderTextfield
}
type="text"
validate={[
requiredIfYes
]}
errorMsg={t(
"myrepresentations:is-required-label"
)}
/>
</div>
</div>
)}
</div>
);
})}
</div>
</fieldset>
</div>
@@ -537,18 +573,19 @@ export const RenderPickList = ({
? optionsArr[key] == "Questionnaire"
? "Holiadur"
: optionsArr[key] == "Statement"
? "Datganiad"
: optionsArr[key] ==
"Final comments"
? "Sylwadau terfynol"
: optionsArr[key] ==
"Written representations"
? "Sylwadau ysgrifenedig"
: optionsArr[key] == "Inquiry"
? "Ymchwiliad"
: optionsArr[key] == "Hearing"
? "Gwrandawiad"
: optionsArr[key]
? "Datganiad"
: optionsArr[key] ==
"Final comments"
? "Sylwadau terfynol"
: optionsArr[key] ==
"Written representations"
? "Sylwadau ysgrifenedig"
: optionsArr[key] == "Inquiry"
? "Ymchwiliad"
: optionsArr[key] ==
"Hearing"
? "Gwrandawiad"
: optionsArr[key]
: optionsArr[key]}
</option>
);
@@ -627,31 +664,31 @@ export const RenderDatePicker = ({
startDateArr == 0
? new Date()
: startDateArr[0] == "-"
? startDateArr[2] == "y"
? subYears(new Date(), startDateArr[1])
? startDateArr[2] == "y"
? subYears(new Date(), startDateArr[1])
: startDateArr[2] == "m"
? subMonths(new Date(), startDateArr[1])
: subDays(new Date(), startDateArr[1])
: startDateArr[2] == "y"
? addYears(new Date(), startDateArr[1])
: startDateArr[2] == "m"
? subMonths(new Date(), startDateArr[1])
: subDays(new Date(), startDateArr[1])
: startDateArr[2] == "y"
? addYears(new Date(), startDateArr[1])
: startDateArr[2] == "m"
? addMonths(new Date(), startDateArr[1])
: addDays(new Date(), startDateArr[1]);
? addMonths(new Date(), startDateArr[1])
: addDays(new Date(), startDateArr[1]);
var maxDate =
endDateArr == 0
? new Date()
: startDateArr[0] == "-"
? startDateArr[2] == "y"
? subYears(new Date(), startDateArr[1])
? startDateArr[2] == "y"
? subYears(new Date(), startDateArr[1])
: startDateArr[2] == "m"
? subMonths(new Date(), startDateArr[1])
: subDays(new Date(), startDateArr[1])
: startDateArr[2] == "y"
? addYears(new Date(), startDateArr[1])
: startDateArr[2] == "m"
? subMonths(new Date(), startDateArr[1])
: subDays(new Date(), startDateArr[1])
: startDateArr[2] == "y"
? addYears(new Date(), startDateArr[1])
: startDateArr[2] == "m"
? addMonths(new Date(), startDateArr[1])
: addDays(new Date(), startDateArr[1]);
? addMonths(new Date(), startDateArr[1])
: addDays(new Date(), startDateArr[1]);
return (
<>
@@ -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>
)}
@@ -1375,9 +1510,9 @@ export function FileUploadField(props) {
? props.filenamePrefix.representationForm.values
.repfile_name
: props.filenamePrefix.hasOwnProperty("formObj")
? props.filenamePrefix.formObj["representationForm"]
.values.repfile_name
: ""
? props.filenamePrefix.formObj["representationForm"]
.values.repfile_name
: ""
}
props={props}
/>
@@ -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();
@@ -2,21 +2,23 @@ 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 { useEffect, useState } from "react";
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) => {
@@ -34,11 +36,25 @@ const RepInterestedPartyPerson = (props) => {
setSavingStatus,
savingStatus,
setFilesForRepresentations,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const required = (value) => (value ? undefined : "Required");
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const persistedRepresentationType =
props.currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
if (JSON.stringify(formObj) != "{}") {
setRepFileName(formObj.representationForm.values);
@@ -61,65 +77,52 @@ const RepInterestedPartyPerson = (props) => {
{file.path} - {file.size} bytes
</li>
));
const selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
const handleSaveExit = () => {
let valuesObj = props.props.props.form[props.form].values || {};
delete valuesObj["_pinswg_appellant_value"];
updateRepresentation(valuesObj, false, true);
};
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>
<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") && (
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-interested-person-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={interestedPersonRepresentationArr}
validate={[required]}
errorMsg={t("myrepresentations:select-an-option-label")}
/>
{isTypeConfirmed && (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
@@ -127,12 +130,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(
@@ -231,78 +229,17 @@ const RepInterestedPartyPerson = (props) => {
</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>
{/*
<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>
{isTypeConfirmed && (
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
)}
</>
);
@@ -316,7 +253,7 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(change(form, field, newValue)),
setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList));
},
}
});
export default connect(
@@ -326,6 +263,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RepInterestedPartyPerson)
);
@@ -10,8 +10,9 @@ import { useState, useEffect } from "react";
import {
FileUploadField,
RenderMultiline,
RenderPickList,
shouldDisableButton
} from "./representationElements";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import RepresentationProgress_enforcement from "./representationProgress/representationProgress_enforcement";
import RepresentationProgress_s78 from "./representationProgress/representationProgress_s78";
import Enforcement_Questionnaire from "./representationQuestionnaires/representationLPAQuestionnaire_enforcement";
@@ -22,7 +23,7 @@ import {
setRepresentationSubmit,
setFilesForRepresentations,
setCurrentPage,
setCurrentReference,
setCurrentReference
} from "../../../store/currentView/action";
let RepLPA = (props) => {
@@ -48,6 +49,8 @@ let RepLPA = (props) => {
setSavingStatus,
savingStatus,
setFilesForRepresentations,
onRepresentationTypeChange,
onTypeSelectionConfirmed
} = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
@@ -58,6 +61,36 @@ let RepLPA = (props) => {
));
const required = (value) => (value ? undefined : "Required");
const persistedRepresentationType =
currentView?.caseReference?.repDetails?.representationType;
const [typeSelectionConfirmed, setTypeSelectionConfirmed] = useState(
!!persistedRepresentationType
);
useEffect(() => {
if (persistedRepresentationType) {
setTypeSelectionConfirmed(true);
}
}, [persistedRepresentationType]);
const selectedRepresentationType =
formObj?.representationForm?.values?.representationType ||
props.representationType;
const isTypeConfirmed =
!!selectedRepresentationType && typeSelectionConfirmed;
const selectedRepresentationTypeDisplay =
router.locale == "cy"
? selectedRepresentationType == "Questionnaire"
? "Holiadur"
: selectedRepresentationType == "Statement"
? "Datganiad"
: selectedRepresentationType == "Final comments"
? "Sylwadau terfynol"
: selectedRepresentationType
: selectedRepresentationType;
const whichQuestionnaire = () => {
{
switch (currentView.caseReference.appealType) {
@@ -182,7 +215,7 @@ let RepLPA = (props) => {
currentView.caseReference.currentReference +
'")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
let setCaseResultssObj = router.query.hasOwnProperty("state")
@@ -201,7 +234,7 @@ let RepLPA = (props) => {
currentView.caseReference.currentReference +
'")]',
json: setCaseResultssObj,
eval: true,
eval: true
});
resultsObj = resultsObj[0];
@@ -224,62 +257,26 @@ let RepLPA = (props) => {
return (
<>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-lpa-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={isTypeConfirmed}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
representationType={selectedRepresentationType}
setTypeSelectionConfirmed={setTypeSelectionConfirmed}
onRepresentationTypeChange={onRepresentationTypeChange}
onTypeSelectionConfirmed={onTypeSelectionConfirmed}
optionsArr={appellantApplicantRepresentationArr}
validate={[required]}
errorMsg={t("myrepresentations:select-an-option-label")}
/>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{!_.has(
formObj["representationForm"],
["values", "representationType"] ||
typeof props.currentView.caseReference.repDetails !=
"undefined"
) ? (
<>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t(
"myrepresentations:representation-from-an-lpa-heading"
)}
</label>
<Field
id="representationType"
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>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{(_.has(formObj["representationForm"], [
"values",
"representationType",
]) ||
typeof props.representationType != "undefined") &&
(props.representationType != "Questionnaire" &&
props.representationType != "Select..." ? (
{isTypeConfirmed &&
(selectedRepresentationType != "Questionnaire" ? (
<>
<p className="govuk-body">
{t("myrepresentations:enter-comment-label")}
@@ -353,7 +350,8 @@ let RepLPA = (props) => {
"myrepresentations:add-files-label"
)}
ticketnumber={
props.currentView.caseReference.ticketnumber
props.currentView.caseReference
.ticketnumber
}
hint={"sdsd"}
fileList={
@@ -400,9 +398,8 @@ let RepLPA = (props) => {
</div>
<div className="govuk-grid-row">
<div className="govuk-button-group">
{(typeof props.representationType != "undefined" ||
_.has(formObj, "values", "representationType")) &&
props.representationType == "Questionnaire" && (
{isTypeConfirmed &&
selectedRepresentationType == "Questionnaire" && (
<>
{showQuestionnaireSection <
questionnaireCount && (
@@ -442,14 +439,14 @@ let RepLPA = (props) => {
// top: 0,
// behavior: "smooth",
// });
setRepFileName(
(setRepFileName(
props.formObj[
"representationForm"
].values
),
setShowQuestionnaireSection(
showQuestionnaireSection - 1
);
));
}}
className="govuk-link"
>
@@ -502,14 +499,17 @@ let RepLPA = (props) => {
)}
</>
)}{" "}
{(typeof props.representationType != "undefined" ||
_.has(formObj, "values", "representationType")) &&
props.representationType != "Questionnaire" && (
{isTypeConfirmed &&
selectedRepresentationType != "Questionnaire" && (
<>
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj
})}
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
@@ -607,7 +607,7 @@ RepLPA = connect((state) => {
return {
procedureType,
representationType,
representationType
};
})(RepLPA);
@@ -620,7 +620,7 @@ const mapDispatchToProps = (dispatch) => ({
},
setCurrentReference: (currentReference) => {
dispatch(setCurrentReference(currentReference));
},
}
});
const mapStateToProps = (state, props) => {
@@ -636,8 +636,7 @@ const mapStateToProps = (state, props) => {
],
"firstname":
props.props.props.accountDetails.accountDetails.firstname,
"lastname":
props.props.props.accountDetails.accountDetails.lastname,
"lastname": props.props.props.accountDetails.accountDetails.lastname
}
);
return {
@@ -648,7 +647,7 @@ const mapStateToProps = (state, props) => {
currentView: state.currentView,
myRepresentations: state.myRepresentations,
watchedCases: state.watchedCases,
initialValues: initialValuesTemp,
initialValues: initialValuesTemp
};
};
@@ -659,6 +658,6 @@ export default connect(
reduxForm({
form: "representationForm",
enableReinitialize: true,
destroyOnUnmount: false,
destroyOnUnmount: false
})(RepLPA)
);
@@ -1,6 +1,7 @@
import { JSONPath as jsonpath } from "jsonpath-plus";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { hasOwn } from "../../utils";
const RepLPADetails = (props) => {
let { t } = useTranslation();
@@ -15,15 +16,15 @@ const RepLPADetails = (props) => {
? props.props.myRepresentations.myRepresentations
: props.searchResultsObj.searchDetailsObj
: props.currentType == "watchedCases"
? props.props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj;
? props.props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj;
var detailsObj = {};
detailsObj = jsonpath({
path: '$..[?(@ && @.ticketnumber=="' + ticketnumber + '")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
detailsObj = detailsObj[0];
@@ -51,34 +52,43 @@ const RepLPADetails = (props) => {
{t("case:summary-site-address-label")}
</dt>
<dd className="govuk-summary-list__value">
{_.has(detailsObj, "pinswg_siteaddressline1")
{hasOwn(detailsObj, "pinswg_siteaddressline1")
? detailsObj.pinswg_siteaddressline1
: ""}
{_.has(detailsObj, "pinswg_siteaddressline1") &&
{hasOwn(
detailsObj,
"pinswg_siteaddressline1"
) &&
detailsObj.pinswg_siteaddressline1 !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddressline2")
{hasOwn(detailsObj, "pinswg_siteaddressline2")
? detailsObj.pinswg_siteaddressline2
: ""}
{_.has(detailsObj, "pinswg_siteaddressline2") &&
{hasOwn(
detailsObj,
"pinswg_siteaddressline2"
) &&
detailsObj.pinswg_siteaddressline2 !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddresstown")
{hasOwn(detailsObj, "pinswg_siteaddresstown")
? detailsObj.pinswg_siteaddresstown
: ""}
{_.has(detailsObj, "pinswg_siteaddresstown") &&
{hasOwn(detailsObj, "pinswg_siteaddresstown") &&
detailsObj.pinswg_siteaddresstown !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddresscounty")
{hasOwn(detailsObj, "pinswg_siteaddresscounty")
? detailsObj.pinswg_siteaddresscounty
: ""}
{_.has(
{hasOwn(
detailsObj,
"pinswg_siteaddresscounty"
) &&
detailsObj.pinswg_siteaddresscounty !=
null && <br />}
{_.has(detailsObj, "pinswg_siteaddresspostcode")
{hasOwn(
detailsObj,
"pinswg_siteaddresspostcode"
)
? detailsObj.pinswg_siteaddresspostcode
: ""}
</dd>
@@ -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) => (
@@ -50,32 +50,32 @@ let Enforcement_Questionnaire = (props) => {
? router.query.state == "edit"
? props.props.myRepresentations.myRepresentations
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj;
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj;
var detailsObj = {};
detailsObj = jsonpath({
path:
'$..[?(@ && @.pinswg_name=="' +
currentView.caseReference.currentReference +
currentView.caseReference.ticketnumber +
'")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
let setCaseResultssObj = router.query.hasOwnProperty("state")
? router.query.state == "edit"
? props.props.myRepresentations.myRepresentations
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj;
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj;
detailsObj = detailsObj[0];
@@ -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) => (
@@ -52,32 +53,32 @@ let S78_Questionnaire = (props) => {
? router.query.state == "edit"
? props.props.myRepresentations.myRepresentations
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj;
? props.props.watchedCases.watchedCasesDetails
: props.props.searchResultsObj.searchDetailsObj;
var detailsObj = {};
detailsObj = jsonpath({
path:
'$..[?(@ && @.pinswg_name=="' +
currentView.caseReference.currentReference +
'$..[?(@ && @.ticketnumber=="' +
currentView.caseReference.ticketnumber +
'")]',
json: setCaseDetailsObj,
eval: true,
eval: true
});
let setCaseResultssObj = router.query.hasOwnProperty("state")
? router.query.state == "edit"
? props.props.myRepresentations.myRepresentations
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj
: currentView.caseReference.currentType == "watchedCases"
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj;
? props.props.watchedCases.watchedCases
: props.props.searchResultsObj.searchResultsObj;
detailsObj = detailsObj[0];
@@ -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)
);

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