From 886d8785bcd3dfc927a6af66225d48a9c4859efc Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Mon, 30 Sep 2024 17:39:58 +0200 Subject: [PATCH 1/2] split up presentational component --- .../PlanningConstraints.stories.tsx | 14 +- .../PlanningConstraints/Presentational.tsx | 167 ++++++++++ .../components/PlanningConstraints/Public.tsx | 310 +++++------------- 3 files changed, 247 insertions(+), 244 deletions(-) create mode 100644 editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/PlanningConstraints.stories.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/PlanningConstraints.stories.tsx index a461789de2..3a298fe51d 100644 --- a/editor.planx.uk/src/@planx/components/PlanningConstraints/PlanningConstraints.stories.tsx +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/PlanningConstraints.stories.tsx @@ -7,10 +7,8 @@ import classifiedRoadsNegativeResponseMock from "./mocks/classifiedRoadsNegative import classifiedRoadsResponseMock from "./mocks/classifiedRoadsResponseMock"; import digitalLandNegativeResponseMock from "./mocks/digitalLandNegativeResponseMock"; import digitalLandResponseMock from "./mocks/digitalLandResponseMock"; -import Public, { - PlanningConstraintsContent, - PlanningConstraintsContentProps, -} from "./Public"; +import { Presentational, PresentationalProps } from "./Presentational"; +import Public from "./Public"; /** * PlanningConstraints fetches data about constraints from DLUHC's planning.data.gov.uk & data about classified roads from the Ordnance Survey Features API. @@ -27,7 +25,7 @@ export default { component: Public, } satisfies Meta; -const propsWithIntersections: PlanningConstraintsContentProps = { +const propsWithIntersections: PresentationalProps = { title: "Planning constraints", description: "Planning constraints might limit how you can develop or use the property", @@ -48,7 +46,7 @@ const propsWithIntersections: PlanningConstraintsContentProps = { setInaccurateConstraints: () => {}, }; -const propsWithoutIntersections: PlanningConstraintsContentProps = { +const propsWithoutIntersections: PresentationalProps = { title: "Planning constraints", description: "Planning constraints might limit how you can develop or use the property", @@ -70,11 +68,11 @@ const propsWithoutIntersections: PlanningConstraintsContentProps = { }; export const WithIntersections = { - render: () => , + render: () => , }; export const WithoutIntersections = { - render: () => , + render: () => , }; export const WithEditor = () => ; diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx new file mode 100644 index 0000000000..dced398c46 --- /dev/null +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx @@ -0,0 +1,167 @@ +import ErrorOutline from "@mui/icons-material/ErrorOutline"; +import Typography from "@mui/material/Typography"; +import type { Constraint, GISResponse } from "@opensystemslab/planx-core/types"; +import Card from "@planx/components/shared/Preview/Card"; +import CardHeader from "@planx/components/shared/Preview/CardHeader"; +import capitalize from "lodash/capitalize"; +import { HandleSubmit } from "pages/Preview/Node"; +import React from "react"; +import ReactMarkdownOrHtml from "ui/shared/ReactMarkdownOrHtml"; + +import { ErrorSummaryContainer } from "../shared/Preview/ErrorSummaryContainer"; +import SimpleExpand from "../shared/Preview/SimpleExpand"; +import { WarningContainer } from "../shared/Preview/WarningContainer"; +import ConstraintsList from "./List"; +import { DEFAULT_PLANNING_CONDITIONS_DISCLAIMER } from "./model"; +import { InaccurateConstraints } from "./Public"; + +export type PresentationalProps = { + title: string; + description: string; + fn: string; + disclaimer: string; + constraints: GISResponse["constraints"]; + metadata: GISResponse["metadata"]; + handleSubmit: () => void; + refreshConstraints: () => void; + inaccurateConstraints: InaccurateConstraints; + setInaccurateConstraints: ( + value: React.SetStateAction, + ) => void; +}; + +export function Presentational(props: PresentationalProps) { + const { + title, + description, + constraints, + metadata, + disclaimer, + inaccurateConstraints, + setInaccurateConstraints, + } = props; + + const error = constraints.error || undefined; + const showError = error || !Object.values(constraints)?.length; + if (showError) return ; + + const positiveConstraints = Object.values(constraints).filter( + (v: Constraint, _i) => v.text && v.value, + ); + const negativeConstraints = Object.values(constraints).filter( + (v: Constraint, _i) => v.text && !v.value, + ); + + return ( + + + {positiveConstraints.length > 0 && ( + <> + + These are the planning constraints we think apply to this property + + + {negativeConstraints.length > 0 && ( + + + + )} + + + )} + {positiveConstraints.length === 0 && negativeConstraints.length > 0 && ( + <> + + It looks like there are no constraints on this property + + + Based on the information you've given it looks like there are no + planning constraints on your property that might limit what you can + do. + + + Continue with your application to tell us more about your project. + + + + + + + )} + + ); +} + +const Disclaimer = (props: { text: string }) => ( + + + + + + +); + +interface ConstraintsFetchErrorProps { + error: any; + title: string; + description: string; + refreshConstraints: () => void; + handleSubmit?: HandleSubmit; +} + +const ConstraintsFetchError = (props: ConstraintsFetchErrorProps) => ( + + + + + No information available + + {props.error && + typeof props.error === "string" && + props.error.endsWith("local authority") ? ( + {capitalize(props.error)} + ) : ( + <> + + We couldn't find any information about your property. Click search + again to try again. You can continue your application without this + information but it might mean we ask additional questions about your + project. + + + + )} + + +); diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx index 2cfcf8fbbd..3289f8a22d 100644 --- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx @@ -1,7 +1,4 @@ -import ErrorOutline from "@mui/icons-material/ErrorOutline"; -import Typography from "@mui/material/Typography"; import type { - Constraint, EnhancedGISResponse, GISResponse, } from "@opensystemslab/planx-core/types"; @@ -10,24 +7,17 @@ import CardHeader from "@planx/components/shared/Preview/CardHeader"; import type { PublicProps } from "@planx/components/ui"; import DelayedLoadingIndicator from "components/DelayedLoadingIndicator"; import { GraphError } from "components/Error/GraphError"; -import capitalize from "lodash/capitalize"; import { useStore } from "pages/FlowEditor/lib/store"; -import { HandleSubmit } from "pages/Preview/Node"; import React, { useState } from "react"; import useSWR, { Fetcher } from "swr"; -import ReactMarkdownOrHtml from "ui/shared/ReactMarkdownOrHtml"; import { stringify } from "wkt"; import { SiteAddress } from "../FindProperty/model"; -import { ErrorSummaryContainer } from "../shared/Preview/ErrorSummaryContainer"; -import SimpleExpand from "../shared/Preview/SimpleExpand"; -import { WarningContainer } from "../shared/Preview/WarningContainer"; -import ConstraintsList from "./List"; import { - DEFAULT_PLANNING_CONDITIONS_DISCLAIMER, type IntersectingConstraints, type PlanningConstraints, } from "./model"; +import { Presentational } from "./Presentational"; import { handleOverrides } from "./utils"; type Props = PublicProps; @@ -148,6 +138,77 @@ function Component(props: Props) { ...roads?.metadata, }; + const handleSubmit = () => { + // `_constraints` & `_overrides` are responsible for auditing + const _constraints: Array< + EnhancedGISResponse | GISResponse["constraints"] + > = []; + if (hasPlanningData) { + if (data && !dataError) + _constraints.push({ + ...data, + planxRequest: teamGisEndpoint, + } as EnhancedGISResponse); + if (roads && !roadsError) + _constraints.push({ + ...roads, + planxRequest: classifiedRoadsEndpoint, + } as EnhancedGISResponse); + } else { + if (data) _constraints.push(data as GISResponse["constraints"]); + } + + const hasInaccurateConstraints = + inaccurateConstraints && Object.keys(inaccurateConstraints).length > 0; + const _overrides = hasInaccurateConstraints + ? { ...priorOverrides, [props.fn]: inaccurateConstraints } + : undefined; + + // `planningConstraints.action` is for analytics + const userAction = hasInaccurateConstraints + ? "Reported at least one inaccurate planning constraint" + : "Accepted all planning constraints"; + + // `[props.fn]` & `_nots[props.fn]` are responsible for future service automations + const _nots: IntersectingConstraints = {}; + const intersectingConstraints: IntersectingConstraints = {}; + Object.entries(constraints).forEach(([key, data]) => { + if (data.value) { + intersectingConstraints[props.fn] ||= []; + intersectingConstraints[props.fn].push(key); + } else { + _nots[props.fn] ||= []; + _nots[props.fn].push(key); + } + }); + + // If the user reported inaccurate constraints, ensure they are correctly reflected in `[props.fn]` & `_nots[props.fn]` + const { + nots: notsAfterOverrides, + intersectingConstraints: intersectingConstraintsAfterOverrides, + } = handleOverrides( + props.fn, + constraints, + inaccurateConstraints, + intersectingConstraints, + _nots, + ); + + const passportData = { + _constraints, + _overrides, + "planningConstraints.action": userAction, + _nots: notsAfterOverrides, + ...(intersectingConstraintsAfterOverrides[props.fn]?.length === 0 + ? undefined + : intersectingConstraintsAfterOverrides), + }; + + props.handleSubmit?.({ + data: passportData, + }); + }; + const isLoading = isValidating || isValidatingRoads; if (isLoading) return ( @@ -161,240 +222,17 @@ function Component(props: Props) { ); return ( - { - // `_constraints` & `_overrides` are responsible for auditing - const _constraints: Array< - EnhancedGISResponse | GISResponse["constraints"] - > = []; - if (hasPlanningData) { - if (data && !dataError) - _constraints.push({ - ...data, - planxRequest: teamGisEndpoint, - } as EnhancedGISResponse); - if (roads && !roadsError) - _constraints.push({ - ...roads, - planxRequest: classifiedRoadsEndpoint, - } as EnhancedGISResponse); - } else { - if (data) _constraints.push(data as GISResponse["constraints"]); - } - - const hasInaccurateConstraints = - inaccurateConstraints && - Object.keys(inaccurateConstraints).length > 0; - const _overrides = hasInaccurateConstraints - ? { ...priorOverrides, [props.fn]: inaccurateConstraints } - : undefined; - - // `planningConstraints.action` is for analytics - const userAction = hasInaccurateConstraints - ? "Reported at least one inaccurate planning constraint" - : "Accepted all planning constraints"; - - // `[props.fn]` & `_nots[props.fn]` are responsible for future service automations - const _nots: IntersectingConstraints = {}; - const intersectingConstraints: IntersectingConstraints = {}; - Object.entries(constraints).forEach(([key, data]) => { - if (data.value) { - intersectingConstraints[props.fn] ||= []; - intersectingConstraints[props.fn].push(key); - } else { - _nots[props.fn] ||= []; - _nots[props.fn].push(key); - } - }); - - // If the user reported inaccurate constraints, ensure they are correctly reflected in `[props.fn]` & `_nots[props.fn]` - const { - nots: notsAfterOverrides, - intersectingConstraints: intersectingConstraintsAfterOverrides, - } = handleOverrides( - props.fn, - constraints, - inaccurateConstraints, - intersectingConstraints, - _nots, - ); - - const passportData = { - _constraints, - _overrides, - "planningConstraints.action": userAction, - _nots: notsAfterOverrides, - ...(intersectingConstraintsAfterOverrides[props.fn]?.length === 0 - ? undefined - : intersectingConstraintsAfterOverrides), - }; - - props.handleSubmit?.({ - data: passportData, - }); - }} + handleSubmit={handleSubmit} refreshConstraints={() => mutate()} inaccurateConstraints={inaccurateConstraints} setInaccurateConstraints={setInaccurateConstraints} /> ); } - -export type PlanningConstraintsContentProps = { - title: string; - description: string; - fn: string; - disclaimer: string; - constraints: GISResponse["constraints"]; - metadata: GISResponse["metadata"]; - handleSubmit: () => void; - refreshConstraints: () => void; - inaccurateConstraints: InaccurateConstraints; - setInaccurateConstraints: ( - value: React.SetStateAction, - ) => void; -}; - -export function PlanningConstraintsContent( - props: PlanningConstraintsContentProps, -) { - const { - title, - description, - constraints, - metadata, - refreshConstraints, - disclaimer, - inaccurateConstraints, - setInaccurateConstraints, - } = props; - const error = constraints.error || undefined; - const showError = error || !Object.values(constraints)?.length; - if (showError) return ; - - const positiveConstraints = Object.values(constraints).filter( - (v: Constraint, _i) => v.text && v.value, - ); - const negativeConstraints = Object.values(constraints).filter( - (v: Constraint, _i) => v.text && !v.value, - ); - - return ( - - - {positiveConstraints.length > 0 && ( - <> - - These are the planning constraints we think apply to this property - - - {negativeConstraints.length > 0 && ( - - - - )} - - - )} - {positiveConstraints.length === 0 && negativeConstraints.length > 0 && ( - <> - - It looks like there are no constraints on this property - - - Based on the information you've given it looks like there are no - planning constraints on your property that might limit what you can - do. - - - Continue with your application to tell us more about your project. - - - - - - - )} - - ); -} - -const Disclaimer = (props: { text: string }) => ( - - - - - - -); - -interface ConstraintsFetchErrorProps { - error: any; - title: string; - description: string; - refreshConstraints: () => void; - handleSubmit?: HandleSubmit; -} - -const ConstraintsFetchError = (props: ConstraintsFetchErrorProps) => ( - - - - - No information available - - {props.error && - typeof props.error === "string" && - props.error.endsWith("local authority") ? ( - {capitalize(props.error)} - ) : ( - <> - - We couldn't find any information about your property. Click search - again to try again. You can continue your application without this - information but it might mean we ask additional questions about your - project. - - - - )} - - -); From b4228d10cf73a30282f51b6425262f0aab25f7df Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Wed, 2 Oct 2024 15:14:57 +0200 Subject: [PATCH 2/2] remove unused iterator --- .../@planx/components/PlanningConstraints/Presentational.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx index dced398c46..a1d44fbe62 100644 --- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Presentational.tsx @@ -46,10 +46,10 @@ export function Presentational(props: PresentationalProps) { if (showError) return ; const positiveConstraints = Object.values(constraints).filter( - (v: Constraint, _i) => v.text && v.value, + (v: Constraint) => v.text && v.value, ); const negativeConstraints = Object.values(constraints).filter( - (v: Constraint, _i) => v.text && !v.value, + (v: Constraint) => v.text && !v.value, ); return (