diff --git a/client/src/containers/projects/form/assumptions/columns.tsx b/client/src/containers/projects/form/assumptions/columns.tsx index 788e0e81..27a33dd9 100644 --- a/client/src/containers/projects/form/assumptions/columns.tsx +++ b/client/src/containers/projects/form/assumptions/columns.tsx @@ -2,7 +2,7 @@ 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 { CustomProjectForm } from "@/containers/projects/form/setup"; import { formatCellValue, shouldFormatToPercentage, @@ -11,7 +11,7 @@ import { import { Label } from "@/components/ui/label"; export type AssumptionsFormProperty = - `assumption.${keyof NonNullable}`; + `assumption.${keyof NonNullable}`; const columnHelper = createColumnHelper>(); @@ -45,8 +45,7 @@ export const COLUMNS = [ cell: (props) => ( diff --git a/client/src/containers/projects/form/assumptions/index.tsx b/client/src/containers/projects/form/assumptions/index.tsx index c6def173..1038fe58 100644 --- a/client/src/containers/projects/form/assumptions/index.tsx +++ b/client/src/containers/projects/form/assumptions/index.tsx @@ -15,7 +15,7 @@ import { COLUMNS, } from "@/containers/projects/form/assumptions/columns"; import { DataColumnDef } from "@/containers/projects/form/cost-inputs-overrides/constants"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { Accordion, @@ -35,7 +35,7 @@ import { export const NO_DATA: DataColumnDef[] = []; export default function AssumptionsProjectForm() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, activity } = form.getValues(); diff --git a/client/src/containers/projects/form/cell-value.tsx b/client/src/containers/projects/form/cell-value.tsx index 7807c38a..be75a1c2 100644 --- a/client/src/containers/projects/form/cell-value.tsx +++ b/client/src/containers/projects/form/cell-value.tsx @@ -5,7 +5,7 @@ import { useFormContext } from "react-hook-form"; import { toDecimalPercentageValue } from "@/lib/format"; import { cn } from "@/lib/utils"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { FormControl, @@ -21,12 +21,12 @@ export default function CellValue({ hasUnit = false, isPercentage = false, }: { - name: keyof CreateCustomProjectForm; + name: keyof CustomProjectForm; className?: ComponentProps["className"]; hasUnit?: boolean; isPercentage?: boolean; }) { - const form = useFormContext(); + const form = useFormContext(); return ( 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 26fd32c5..ae8f70c8 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 @@ -19,7 +19,7 @@ import { COST_INPUTS_KEYS, DataColumnDef, } from "@/containers/projects/form/cost-inputs-overrides/constants"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { Table, @@ -33,7 +33,7 @@ import { const NO_DATA: DataColumnDef[] = []; export default function CapexCostInputsTable() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, diff --git a/client/src/containers/projects/form/cost-inputs-overrides/constants.ts b/client/src/containers/projects/form/cost-inputs-overrides/constants.ts index 4464a2bb..e5726ec4 100644 --- a/client/src/containers/projects/form/cost-inputs-overrides/constants.ts +++ b/client/src/containers/projects/form/cost-inputs-overrides/constants.ts @@ -1,6 +1,6 @@ -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; -type CostInputsKeys = NonNullable; +type CostInputsKeys = NonNullable; // todo: label dictionary diff --git a/client/src/containers/projects/form/cost-inputs-overrides/opex/columns.tsx b/client/src/containers/projects/form/cost-inputs-overrides/opex/columns.tsx index 49dbc365..3d85c907 100644 --- a/client/src/containers/projects/form/cost-inputs-overrides/opex/columns.tsx +++ b/client/src/containers/projects/form/cost-inputs-overrides/opex/columns.tsx @@ -7,7 +7,7 @@ import { COST_INPUTS_KEYS, DataColumnDef, } from "@/containers/projects/form/cost-inputs-overrides/constants"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { formatCellValue, shouldFormatToPercentage, @@ -50,8 +50,7 @@ export const COLUMNS = [ return ( 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 56a5e81e..ac50d17b 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 @@ -19,7 +19,7 @@ import { COLUMNS, OpexFormProperty, } from "@/containers/projects/form/cost-inputs-overrides/opex/columns"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { Table, @@ -33,7 +33,7 @@ import { const NO_DATA: DataColumnDef[] = []; export default function OpexCostInputsTable() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, diff --git a/client/src/containers/projects/form/cost-inputs-overrides/other/columns.tsx b/client/src/containers/projects/form/cost-inputs-overrides/other/columns.tsx index b055ff52..324d039a 100644 --- a/client/src/containers/projects/form/cost-inputs-overrides/other/columns.tsx +++ b/client/src/containers/projects/form/cost-inputs-overrides/other/columns.tsx @@ -7,7 +7,7 @@ import { COST_INPUTS_KEYS, DataColumnDef, } from "@/containers/projects/form/cost-inputs-overrides/constants"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { formatCellValue, shouldFormatToPercentage, @@ -50,8 +50,7 @@ export const COLUMNS = [ return ( 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 34007fe0..fd5b20ef 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 @@ -19,7 +19,7 @@ import { COLUMNS, OtherFormProperty, } from "@/containers/projects/form/cost-inputs-overrides/other/columns"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { Table, @@ -33,7 +33,7 @@ import { const NO_DATA: DataColumnDef[] = []; export default function OtherCostInputsTable() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, diff --git a/client/src/containers/projects/form/header.tsx b/client/src/containers/projects/form/header.tsx index 71835f4e..68e2c400 100644 --- a/client/src/containers/projects/form/header.tsx +++ b/client/src/containers/projects/form/header.tsx @@ -10,7 +10,7 @@ import { useSession } from "next-auth/react"; import { queryKeys } from "@/lib/query-keys"; import { getAuthHeader } from "@/lib/utils"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { createCustomProject, parseFormValues, @@ -27,14 +27,14 @@ interface HeaderProps { } export default function Header({ name, id }: HeaderProps) { - const methods = useFormContext(); + const methods = useFormContext(); const { data: session } = useSession(); const queryClient = useQueryClient(); const router = useRouter(); const { toast } = useToast(); const isEdit = !!id; const handleSubmit = useCallback( - async (data: CreateCustomProjectForm) => { + async (data: CustomProjectForm) => { try { const formValues = parseFormValues(data); diff --git a/client/src/containers/projects/form/index.tsx b/client/src/containers/projects/form/index.tsx index 9f16cf81..2eb2d14e 100644 --- a/client/src/containers/projects/form/index.tsx +++ b/client/src/containers/projects/form/index.tsx @@ -10,7 +10,7 @@ import { ExtractAtomValue, useSetAtom } from "jotai"; import Header from "@/containers/projects/form/header"; import ProjectForm from "@/containers/projects/form/project-form"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { type CustomProjectForm } from "@/containers/projects/form/setup"; import ProjectSidebar from "@/containers/projects/form/sidebar"; import { useDefaultFormValues } from "@/containers/projects/form/utils"; import { formStepAtom } from "@/containers/projects/store"; @@ -26,7 +26,7 @@ export default function CustomProjectForm({ id }: CustomProjectFormProps) { const setIntersecting = useSetAtom(formStepAtom); const formValues = useDefaultFormValues(id); - const methods = useForm({ + const methods = useForm({ resolver: zodResolver(CustomProjectSchema), defaultValues: formValues, mode: "all", diff --git a/client/src/containers/projects/form/project-form/index.tsx b/client/src/containers/projects/form/project-form/index.tsx index 3ef882ba..557a4061 100644 --- a/client/src/containers/projects/form/project-form/index.tsx +++ b/client/src/containers/projects/form/project-form/index.tsx @@ -6,7 +6,7 @@ import AssumptionsProjectForm from "@/containers/projects/form/assumptions"; import CostInputsOverridesProjectForm from "@/containers/projects/form/cost-inputs-overrides"; import RestorationPlanProjectForm from "@/containers/projects/form/restoration-plan"; import SetupProjectForm, { - CreateCustomProjectForm, + CustomProjectForm, } from "@/containers/projects/form/setup"; import { PROJECT_SETUP_STEPS, @@ -16,7 +16,7 @@ import { import { Card } from "@/components/ui/card"; export const useFormValues = () => { - const { getValues } = useFormContext(); + const { getValues } = useFormContext(); return { ...getValues(), @@ -25,7 +25,7 @@ export const useFormValues = () => { }; export default function ProjectForm() { - const form = useFormContext(); + const form = useFormContext(); const { activity } = form.getValues(); return ( diff --git a/client/src/containers/projects/form/restoration-plan/index.tsx b/client/src/containers/projects/form/restoration-plan/index.tsx index a88e4623..82d765da 100644 --- a/client/src/containers/projects/form/restoration-plan/index.tsx +++ b/client/src/containers/projects/form/restoration-plan/index.tsx @@ -13,7 +13,7 @@ import { queryKeys } from "@/lib/query-keys"; import { useFormValues } from "@/containers/projects/form/project-form"; import { COLUMNS } from "@/containers/projects/form/restoration-plan/columns"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { Accordion, @@ -31,7 +31,7 @@ import { } from "@/components/ui/table"; export default function RestorationPlanProjectForm() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, diff --git a/client/src/containers/projects/form/setup/conservation-project-details/index.tsx b/client/src/containers/projects/form/setup/conservation-project-details/index.tsx index 88245f9c..cef6fefe 100644 --- a/client/src/containers/projects/form/setup/conservation-project-details/index.tsx +++ b/client/src/containers/projects/form/setup/conservation-project-details/index.tsx @@ -10,7 +10,7 @@ import { import { ECOSYSTEM } from "@shared/entities/ecosystem.enum"; import { LOSS_RATE_USED } from "@shared/schemas/custom-projects/custom-project.schema"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import LossRate from "@/containers/projects/form/setup/conservation-project-details/loss-rate"; import T1GlobalEmissionFactor from "@/containers/projects/form/setup/conservation-project-details/t1-global-emission-factor"; import T2NationalEmissionFactors from "@/containers/projects/form/setup/conservation-project-details/t2-national-emission-factors"; @@ -34,7 +34,7 @@ import { } from "@/components/ui/select"; export default function ConservationProjectDetails() { - const form = useFormContext(); + const form = useFormContext(); return ( 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 8ca22eee..dba6e0c5 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 @@ -9,19 +9,14 @@ import { toPercentageValue } from "@/lib/format"; import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; +import ProjectSpecificLossRate from "@/containers/projects/form/setup/conservation-project-details/project-specific-loss-rate"; -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; +import { FormLabel } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; export default function LossRate() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, @@ -81,35 +76,5 @@ export default function LossRate() { ); } - return ( - ( - -
- - Project-specific loss rate - -
- -
- -
-
- -
- )} - /> - ); + return ; } diff --git a/client/src/containers/projects/form/setup/conservation-project-details/project-specific-loss-rate.tsx b/client/src/containers/projects/form/setup/conservation-project-details/project-specific-loss-rate.tsx new file mode 100644 index 00000000..76451ceb --- /dev/null +++ b/client/src/containers/projects/form/setup/conservation-project-details/project-specific-loss-rate.tsx @@ -0,0 +1,77 @@ +import { useState, ChangeEvent } from "react"; + +import { useFormContext } from "react-hook-form"; + +import { toPercentageValue, toDecimalPercentageValue } from "@/lib/format"; + +import { CustomProjectForm } from "@/containers/projects/form/setup"; + +import { + FormField, + FormItem, + FormLabel, + FormControl, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; + +/** + * Separate component for project specific loss rate, + * to make sure the percentage value is formatted correctly + * on each input change + */ +const ProjectSpecificLossRate = () => { + const form = useFormContext(); + const parameters = form.getValues().parameters; + const [value, setValue] = useState( + "projectSpecificLossRate" in parameters + ? toPercentageValue(parameters.projectSpecificLossRate) + : "", + ); + const handleOnChange = (e: ChangeEvent) => { + const newValue = e.target.value; + + // if newValue is not a valid number, it will be an empty string + if (newValue.length > 0) { + setValue(newValue); + form.setValue( + "parameters.projectSpecificLossRate", + toDecimalPercentageValue(newValue), + ); + } + }; + + return ( + ( + +
+ + Project-specific loss rate + +
+ +
+ +
+
+ +
+ )} + /> + ); +}; + +export default ProjectSpecificLossRate; 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 a7a5e87b..e7a43245 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 @@ -7,14 +7,14 @@ import { ACTIVITY } from "@shared/entities/activity.enum"; import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { FormLabel } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; // ? this input is read-only as it is informative to the user. It is not meant to be sent to the API. export default function T1GlobalEmissionFactor() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, countryCode, activity } = form.getValues(); 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 2143b597..cbe553c3 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 @@ -7,14 +7,14 @@ import { ACTIVITY } from "@shared/entities/activity.enum"; import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { FormLabel } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; // ? these inputs are read-only as it is informative to the user. They are not meant to be sent to the API. export default function T2NationalEmissionFactors() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, countryCode, activity } = form.getValues(); diff --git a/client/src/containers/projects/form/setup/index.tsx b/client/src/containers/projects/form/setup/index.tsx index 17a06166..0a5714bf 100644 --- a/client/src/containers/projects/form/setup/index.tsx +++ b/client/src/containers/projects/form/setup/index.tsx @@ -47,7 +47,7 @@ import { SelectValue, } from "@/components/ui/select"; -export type CreateCustomProjectForm = z.infer; +export type CustomProjectForm = z.infer; export default function SetupProjectForm() { const { queryKey } = queryKeys.countries.all; @@ -61,7 +61,7 @@ export default function SetupProjectForm() { }, ); - const form = useFormContext(); + const form = useFormContext(); const { activity, 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 86ffabe5..5cf5c83d 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 @@ -13,7 +13,7 @@ import { toDecimalPercentageValue, toPercentageValue } from "@/lib/format"; import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; import { Card } from "@/components/ui/card"; import { @@ -33,7 +33,7 @@ import { } from "@/components/ui/select"; export default function RestorationProjectDetails() { - const form = useFormContext(); + const form = useFormContext(); const { ecosystem, diff --git a/client/src/containers/projects/form/utils.ts b/client/src/containers/projects/form/utils.ts index 8cd2ba22..0a237fd5 100644 --- a/client/src/containers/projects/form/utils.ts +++ b/client/src/containers/projects/form/utils.ts @@ -11,16 +11,15 @@ import { ASSUMPTIONS_NAME_TO_DTO_MAP } from "@shared/schemas/assumptions/assumpt import { LOSS_RATE_USED } from "@shared/schemas/custom-projects/custom-project.schema"; import { useSession } from "next-auth/react"; -import { toDecimalPercentageValue, toPercentageValue } from "@/lib/format"; import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; import { getAuthHeader } from "@/lib/utils"; import { getQueryClient } from "@/app/providers"; -import { CreateCustomProjectForm } from "@/containers/projects/form/setup"; +import { CustomProjectForm } from "@/containers/projects/form/setup"; -export const parseFormValues = (data: CreateCustomProjectForm) => { +export const parseFormValues = (data: CustomProjectForm) => { const queryClient = getQueryClient(); const originalValues = { ...data }; @@ -82,13 +81,6 @@ export const parseFormValues = (data: CreateCustomProjectForm) => { parameters: { ...restParameters, // @ts-expect-error fix later - ...(restParameters?.projectSpecificLossRate && { - projectSpecificLossRate: toDecimalPercentageValue( - // @ts-expect-error fix later - restParameters.projectSpecificLossRate, - ), - }), - // @ts-expect-error fix later ...(restParameters?.plantingSuccessRate && { plantingSuccessRate: // @ts-expect-error fix later @@ -134,7 +126,13 @@ export const parseFormValues = (data: CreateCustomProjectForm) => { }; }; -export const useDefaultFormValues = (id?: string): CreateCustomProjectForm => { +/** + * Note: All percentage values are kept in decimal form, + * formatting to whole percentage values (for UI display) is done at input component level + * + * @returns initial values for the form + */ +export const useDefaultFormValues = (id?: string): CustomProjectForm => { const { data: session } = useSession(); const { queryKey } = queryKeys.customProjects.countries; const { data: countryOptions } = @@ -185,11 +183,8 @@ export const useDefaultFormValues = (id?: string): CreateCustomProjectForm => { lossRateUsed: project?.input.parameters.lossRateUsed || LOSS_RATE_USED.PROJECT_SPECIFIC, - projectSpecificLossRate: parseFloat( - toPercentageValue( - project?.input.parameters.projectSpecificLossRate || -0.35, - ), - ), + projectSpecificLossRate: + project?.input.parameters.projectSpecificLossRate || -0.35, projectSpecificEmission: project?.input.parameters.projectSpecificEmission || PROJECT_SPECIFIC_EMISSION.ONE_EMISSION_FACTOR, @@ -216,7 +211,7 @@ export const useDefaultFormValues = (id?: string): CreateCustomProjectForm => { }; export const updateCustomProject = async (options: { - body: CreateCustomProjectForm; + body: CustomProjectForm; params: { id: string }; extraHeaders: | { @@ -241,7 +236,7 @@ export const updateCustomProject = async (options: { }; export const createCustomProject = async (options: { - body: CreateCustomProjectForm; + body: CustomProjectForm; }): Promise> => { try { const { status, body } =