From 3d48ee8746e2931bc1059b574930e3c97c8093c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gonz=C3=A1lez=20Mu=C3=B1oz?= Date: Mon, 16 Dec 2024 17:52:00 +0100 Subject: [PATCH] adds restoration panel validations --- .../cost-inputs-overrides/capex/index.tsx | 6 +- .../form/cost-inputs-overrides/opex/index.tsx | 6 +- .../cost-inputs-overrides/other/index.tsx | 6 +- .../form/restoration-plan/columns.tsx | 9 +- .../projects/form/restoration-plan/index.tsx | 63 +++++-- .../loss-rate.tsx | 34 ++-- .../t1-global-emission-factor.tsx | 23 +-- .../t2-national-emission-factors.tsx | 23 +-- .../restoration-project-detail/index.tsx | 166 +++++++++--------- client/src/containers/projects/new/header.tsx | 2 +- client/src/containers/projects/new/index.tsx | 42 ++++- .../create-custom-project.schema.ts | 4 +- 12 files changed, 247 insertions(+), 137 deletions(-) diff --git a/client/src/containers/projects/form/cost-inputs-overrides/capex/index.tsx b/client/src/containers/projects/form/cost-inputs-overrides/capex/index.tsx index f2b2b778..3b46777c 100644 --- a/client/src/containers/projects/form/cost-inputs-overrides/capex/index.tsx +++ b/client/src/containers/projects/form/cost-inputs-overrides/capex/index.tsx @@ -1,5 +1,6 @@ import { useFormContext } from "react-hook-form"; +import { ACTIVITY } from "@shared/entities/activity.enum"; import { COSTS_DTO_TO_NAME_MAP } from "@shared/schemas/assumptions/assumptions.enums"; import { flexRender, @@ -38,7 +39,10 @@ export default function CapexCostInputsTable() { ecosystem, countryCode, activity, - parameters: { restorationActivity }, + parameters: { + // @ts-expect-error fix later + restorationActivity, + }, } = form.getValues(); const { queryKey } = queryKeys.customProjects.defaultCosts({ diff --git a/client/src/containers/projects/form/cost-inputs-overrides/opex/index.tsx b/client/src/containers/projects/form/cost-inputs-overrides/opex/index.tsx index 746ccd0c..5426596e 100644 --- a/client/src/containers/projects/form/cost-inputs-overrides/opex/index.tsx +++ b/client/src/containers/projects/form/cost-inputs-overrides/opex/index.tsx @@ -1,5 +1,6 @@ import { useFormContext } from "react-hook-form"; +import { ACTIVITY } from "@shared/entities/activity.enum"; import { COSTS_DTO_TO_NAME_MAP } from "@shared/schemas/assumptions/assumptions.enums"; import { flexRender, @@ -38,7 +39,10 @@ export default function OpexCostInputsTable() { ecosystem, countryCode, activity, - parameters: { restorationActivity }, + parameters: { + // @ts-expect-error fix later + restorationActivity, + }, } = form.getValues(); const { queryKey } = queryKeys.customProjects.defaultCosts({ diff --git a/client/src/containers/projects/form/cost-inputs-overrides/other/index.tsx b/client/src/containers/projects/form/cost-inputs-overrides/other/index.tsx index be174738..a195af2e 100644 --- a/client/src/containers/projects/form/cost-inputs-overrides/other/index.tsx +++ b/client/src/containers/projects/form/cost-inputs-overrides/other/index.tsx @@ -1,5 +1,6 @@ import { useFormContext } from "react-hook-form"; +import { ACTIVITY } from "@shared/entities/activity.enum"; import { COSTS_DTO_TO_NAME_MAP } from "@shared/schemas/assumptions/assumptions.enums"; import { flexRender, @@ -38,7 +39,10 @@ export default function OtherCostInputsTable() { ecosystem, countryCode, activity, - parameters: { restorationActivity }, + parameters: { + // @ts-expect-error fix later + restorationActivity, + }, } = form.getValues(); const { queryKey } = queryKeys.customProjects.defaultCosts({ diff --git a/client/src/containers/projects/form/restoration-plan/columns.tsx b/client/src/containers/projects/form/restoration-plan/columns.tsx index 357a34db..12efea57 100644 --- a/client/src/containers/projects/form/restoration-plan/columns.tsx +++ b/client/src/containers/projects/form/restoration-plan/columns.tsx @@ -1,8 +1,6 @@ import { createColumnHelper } from "@tanstack/react-table"; import CellValue from "@/containers/projects/form/cell-value"; -import { DataColumnDef } from "@/containers/projects/form/cost-inputs-overrides/constants"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; import { Label } from "@/components/ui/label"; @@ -27,14 +25,11 @@ export const COLUMNS = [ cell: (props) => ( ), - // size: 75, maxSize: 55, - // cell: (props) => { - // return ; - // }, }), ]; diff --git a/client/src/containers/projects/form/restoration-plan/index.tsx b/client/src/containers/projects/form/restoration-plan/index.tsx index d132aee6..1f49f95e 100644 --- a/client/src/containers/projects/form/restoration-plan/index.tsx +++ b/client/src/containers/projects/form/restoration-plan/index.tsx @@ -1,8 +1,7 @@ -import { useMemo } from "react"; +import { useEffect, useMemo } from "react"; -import { useFormContext } from "react-hook-form"; +import { useFormContext, useWatch } from "react-hook-form"; -import { ASSUMPTIONS_NAME_TO_DTO_MAP } from "@shared/schemas/assumptions/assumptions.enums"; import { flexRender, getCoreRowModel, @@ -12,7 +11,6 @@ import { import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; -import { AssumptionsFormProperty } from "@/containers/projects/form/assumptions/columns"; import { COLUMNS } from "@/containers/projects/form/restoration-plan/columns"; import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; @@ -31,17 +29,33 @@ import { TableRow, } from "@/components/ui/table"; +const useFormValues = () => { + const { getValues } = useFormContext(); + + return { + ...getValues(), + ...useWatch(), + }; +}; + export default function RestorationPlanProjectForm() { const form = useFormContext(); - const v = form.getValues(); - console.log(v); + const formValues = useFormValues(); const { ecosystem, activity, - assumptions: { projectLength }, - } = form.getValues(); + projectSizeHa, + assumptions: { + // @ts-expect-error fix later + projectLength, + }, + parameters: { + // @ts-expect-error fix later + restorationYearlyBreakdown, + }, + } = formValues; const { queryKey } = queryKeys.customProjects.assumptions({ ecosystem, @@ -68,8 +82,6 @@ export default function RestorationPlanProjectForm() { ? Number(projectLength) : defaultRestorationProjectLength; - console.log({ totalYears }); - const DATA = useMemo( () => Array.from({ @@ -89,6 +101,34 @@ export default function RestorationPlanProjectForm() { getCoreRowModel: getCoreRowModel(), }); + const { setError, clearErrors } = form; + + const breakDownError = + // @ts-expect-error fix later + form.formState.errors.parameters?.restorationYearlyBreakdown; + + useEffect(() => { + const totalHectares = (restorationYearlyBreakdown as string[]) + ?.filter(Boolean) + .reduce((acc, hectares) => acc + Number(hectares), 0); + + if (totalHectares > projectSizeHa) { + setError("parameters.restorationYearlyBreakdown", { + message: `Total hectares restored cannot exceed project size. (${totalHectares}/${projectSizeHa})`, + }); + } else { + if (breakDownError?.message) { + clearErrors("parameters.restorationYearlyBreakdown"); + } + } + }, [ + projectSizeHa, + restorationYearlyBreakdown, + setError, + clearErrors, + breakDownError?.message, + ]); + return ( @@ -103,6 +143,9 @@ export default function RestorationPlanProjectForm() {

Overrides annual hectares inputs

+ {breakDownError && ( +

{breakDownError.message}

+ )} diff --git a/client/src/containers/projects/form/setup/conservation-project-details/loss-rate.tsx b/client/src/containers/projects/form/setup/conservation-project-details/loss-rate.tsx index 1a640c25..2b32c16a 100644 --- a/client/src/containers/projects/form/setup/conservation-project-details/loss-rate.tsx +++ b/client/src/containers/projects/form/setup/conservation-project-details/loss-rate.tsx @@ -26,7 +26,10 @@ export default function LossRate() { ecosystem, countryCode, activity, - parameters: { lossRateUsed }, + parameters: { + // @ts-expect-error fix later + lossRateUsed, + }, } = form.getValues(); const queryKey = queryKeys.customProjects.defaultActivityTypes({ @@ -34,21 +37,24 @@ export default function LossRate() { countryCode, }).queryKey; - const { data } = client.customProjects.getActivityTypesDefaults.useQuery( - queryKey, - { query: { ecosystem, countryCode } }, - { + const { data, isSuccess } = + client.customProjects.getActivityTypesDefaults.useQuery( queryKey, - enabled: - !!ecosystem && - !!countryCode && - lossRateUsed === LOSS_RATE_USED.NATIONAL_AVERAGE, - select: (response) => { - const { data } = response.body; - return data[activity as ACTIVITY.CONSERVATION].ecosystemLossRate; + { query: { ecosystem, countryCode } }, + { + queryKey, + enabled: + !!ecosystem && + !!countryCode && + lossRateUsed === LOSS_RATE_USED.NATIONAL_AVERAGE, + select: (response) => { + const { data } = response.body; + return data[activity as ACTIVITY.CONSERVATION].ecosystemLossRate; + }, }, - }, - ); + ); + + if (!isSuccess) return null; if (lossRateUsed === LOSS_RATE_USED.NATIONAL_AVERAGE) { return ( diff --git a/client/src/containers/projects/form/setup/conservation-project-details/t1-global-emission-factor.tsx b/client/src/containers/projects/form/setup/conservation-project-details/t1-global-emission-factor.tsx index 49962da6..a7a5e87b 100644 --- a/client/src/containers/projects/form/setup/conservation-project-details/t1-global-emission-factor.tsx +++ b/client/src/containers/projects/form/setup/conservation-project-details/t1-global-emission-factor.tsx @@ -23,18 +23,21 @@ export default function T1GlobalEmissionFactor() { countryCode, }).queryKey; - const { data } = client.customProjects.getActivityTypesDefaults.useQuery( - queryKey, - { query: { ecosystem, countryCode } }, - { + const { data, isSuccess } = + client.customProjects.getActivityTypesDefaults.useQuery( queryKey, - enabled: !!ecosystem && !!countryCode, - select: (response) => { - const { data } = response.body; - return data[activity as ACTIVITY.CONSERVATION].emissionFactor.tier1; + { query: { ecosystem, countryCode } }, + { + queryKey, + enabled: !!ecosystem && !!countryCode, + select: (response) => { + const { data } = response.body; + return data[activity as ACTIVITY.CONSERVATION].emissionFactor.tier1; + }, }, - }, - ); + ); + + if (!isSuccess) return null; return (
diff --git a/client/src/containers/projects/form/setup/conservation-project-details/t2-national-emission-factors.tsx b/client/src/containers/projects/form/setup/conservation-project-details/t2-national-emission-factors.tsx index 17680e47..2143b597 100644 --- a/client/src/containers/projects/form/setup/conservation-project-details/t2-national-emission-factors.tsx +++ b/client/src/containers/projects/form/setup/conservation-project-details/t2-national-emission-factors.tsx @@ -23,18 +23,21 @@ export default function T2NationalEmissionFactors() { countryCode, }).queryKey; - const { data } = client.customProjects.getActivityTypesDefaults.useQuery( - queryKey, - { query: { ecosystem, countryCode } }, - { + const { data, isSuccess } = + client.customProjects.getActivityTypesDefaults.useQuery( queryKey, - enabled: !!ecosystem && !!countryCode, - select: (response) => { - const { data } = response.body; - return data[activity as ACTIVITY.CONSERVATION].emissionFactor.tier2; + { query: { ecosystem, countryCode } }, + { + queryKey, + enabled: !!ecosystem && !!countryCode, + select: (response) => { + const { data } = response.body; + return data[activity as ACTIVITY.CONSERVATION].emissionFactor.tier2; + }, }, - }, - ); + ); + + if (!isSuccess) return null; return (
diff --git a/client/src/containers/projects/form/setup/restoration-project-detail/index.tsx b/client/src/containers/projects/form/setup/restoration-project-detail/index.tsx index be4f1f06..0183c849 100644 --- a/client/src/containers/projects/form/setup/restoration-project-detail/index.tsx +++ b/client/src/containers/projects/form/setup/restoration-project-detail/index.tsx @@ -47,18 +47,19 @@ export default function RestorationProjectDetails() { countryCode, }).queryKey; - const { data } = client.customProjects.getActivityTypesDefaults.useQuery( - queryKey, - { query: { ecosystem, countryCode } }, - { + const { data, isSuccess } = + client.customProjects.getActivityTypesDefaults.useQuery( queryKey, - enabled: !!ecosystem && !!countryCode, - select: (response) => { - const { data } = response.body; - return data[activity as ACTIVITY.RESTORATION].sequestrationRate; + { query: { ecosystem, countryCode } }, + { + queryKey, + enabled: !!ecosystem && !!countryCode, + select: (response) => { + const { data } = response.body; + return data[activity as ACTIVITY.RESTORATION].sequestrationRate; + }, }, - }, - ); + ); const RESTORATION_ACTIVITY_OPTIONS = Object.values( RESTORATION_ACTIVITY_SUBTYPE, @@ -179,81 +180,88 @@ export default function RestorationProjectDetails() { ( - - - Project Specific Emission Factor - - -
+ render={({ field }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { value, ...restField } = field; + + return ( + + + Planting Success Rate + + +
+ { + form.setValue( + "parameters.plantingSuccessRate", + Number(v.target.value), + ); + }} + readOnly + defaultValue={80} + disabled + /> +
+
+ +
+ ); + }} + /> + + <> + {tierSelector === SEQUESTRATION_RATE_TIER_TYPES.TIER_1 && + isSuccess && ( +
+ + IPCC default value + +
{ - form.setValue( - "parameters.plantingSuccessRate", - Number(v.target.value), - ); - // await form.trigger("parameters.plantingSuccessRate"); - }} - readOnly + className="w-full pr-32 text-muted-foreground" disabled + readOnly + value={data?.tier1} />
- - - - )} - /> - - <> - {tierSelector === SEQUESTRATION_RATE_TIER_TYPES.TIER_1 && ( -
- - IPCC default value - -
-
-
- )} - {tierSelector === SEQUESTRATION_RATE_TIER_TYPES.TIER_2 && ( -
- - Country-specific rate - -
- + )} + {tierSelector === SEQUESTRATION_RATE_TIER_TYPES.TIER_2 && + isSuccess && ( +
+ + Country-specific rate + +
+ +
-
- )} + )} {tierSelector === SEQUESTRATION_RATE_TIER_TYPES.TIER_3 && (