Skip to content

Commit

Permalink
adds restoration panel
Browse files Browse the repository at this point in the history
  • Loading branch information
agnlez committed Dec 16, 2024
1 parent f28cc76 commit 06d8ad2
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 6 deletions.
13 changes: 12 additions & 1 deletion client/src/containers/projects/form/cell-value.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ComponentProps } from "react";

import { useFormContext } from "react-hook-form";

import { cn } from "@/lib/utils";

import { CreateCustomProjectForm } from "@/containers/projects/form/setup";

import {
Expand All @@ -12,8 +16,12 @@ import { Input } from "@/components/ui/input";

export default function CellValue({
name,
className,
hasUnit = false,
}: {
name: keyof CreateCustomProjectForm;
className?: ComponentProps<typeof FormControl>["className"];
hasUnit?: boolean;
}) {
const form = useFormContext<CreateCustomProjectForm>();

Expand All @@ -26,14 +34,17 @@ export default function CellValue({
const { value, ...restField } = field;

return (
<FormItem>
<FormItem className={className}>
<FormControl>
<Input
{...restField}
type="number"
placeholder="Insert value"
min={0}
defaultValue={field.value as number}
className={cn({
"pr-12": hasUnit,
})}
/>
</FormControl>
<FormMessage />
Expand Down
17 changes: 16 additions & 1 deletion client/src/containers/projects/form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { useFormContext } from "react-hook-form";

import { ACTIVITY } from "@shared/entities/activity.enum";

import AssumptionsProjectForm from "@/containers/projects/form/assumptions";
import CostInputsOverridesProjectForm from "@/containers/projects/form/cost-inputs-overrides";
import SetupProjectForm from "@/containers/projects/form/setup";
import RestorationPlanProjectForm from "@/containers/projects/form/restoration-plan";
import SetupProjectForm, {
CreateCustomProjectForm,
} from "@/containers/projects/form/setup";

import { Card } from "@/components/ui/card";

export default function ProjectForm({ onSubmit }: { onSubmit: () => void }) {
const form = useFormContext<CreateCustomProjectForm>();
const { activity } = form.getValues();

return (
<form className="w-full space-y-8" onSubmit={onSubmit}>
<div className="flex flex-col gap-3">
Expand All @@ -17,6 +27,11 @@ export default function ProjectForm({ onSubmit }: { onSubmit: () => void }) {
<Card className="flex flex-1 flex-col" variant="secondary">
<CostInputsOverridesProjectForm />
</Card>
{activity === ACTIVITY.RESTORATION && (
<Card className="flex flex-1 flex-col" variant="secondary">
<RestorationPlanProjectForm />
</Card>
)}
</div>
</form>
);
Expand Down
40 changes: 40 additions & 0 deletions client/src/containers/projects/form/restoration-plan/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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";

export type RestorationPlanFormProperty = {
year: number;
annualHectaresRestored: number;
};

const columnHelper = createColumnHelper<RestorationPlanFormProperty>();

export const COLUMNS = [
columnHelper.accessor("year", {
header: () => <span>Year</span>,
cell: (props) => {
return <Label>{props.getValue()}</Label>;
},
}),
columnHelper.accessor("annualHectaresRestored", {
header: () => (
<span className="flex justify-end">Annual hectares restored / year</span>
),
cell: (props) => (
<CellValue
className="relative after:absolute after:right-6 after:top-1/2 after:inline-block after:-translate-y-1/2 after:text-sm after:text-muted-foreground after:content-['ha']"
name={`parameters.restorationYearlyBreakdown.${props.row.original.year}`}
hasUnit
/>
),
// size: 75,
maxSize: 55,
// cell: (props) => {
// return <Label>{props.getValue()}</Label>;
// },
}),
];
167 changes: 167 additions & 0 deletions client/src/containers/projects/form/restoration-plan/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { useMemo } from "react";

import { useFormContext } from "react-hook-form";

import { ASSUMPTIONS_NAME_TO_DTO_MAP } from "@shared/schemas/assumptions/assumptions.enums";
import {
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";

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";

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

export default function RestorationPlanProjectForm() {
const form = useFormContext<CreateCustomProjectForm>();

const v = form.getValues();
console.log(v);

const {
ecosystem,
activity,
assumptions: { projectLength },
} = form.getValues();

const { queryKey } = queryKeys.customProjects.assumptions({
ecosystem,
activity,
});
const { data: defaultRestorationProjectLength } =
client.customProjects.getDefaultAssumptions.useQuery(
queryKey,
{
query: { ecosystem, activity },
},
{
queryKey,
select: (data) =>
Number(
data.body.data.find(
(assumption) => assumption.name === "Restoration project length",
)?.value,
),
},
);

const totalYears = projectLength
? Number(projectLength)
: defaultRestorationProjectLength;

console.log({ totalYears });

const DATA = useMemo(
() =>
Array.from({
length: (totalYears as NonNullable<typeof totalYears>) + 2,
})
.map((_, i) => ({
year: i - 1,
annualHectaresRestored: 0,
}))
.filter(({ year }) => year != 0),
[totalYears],
);

const table = useReactTable({
data: DATA,
columns: COLUMNS,
getCoreRowModel: getCoreRowModel(),
});

return (
<Accordion type="single" collapsible defaultValue="assumptions">
<AccordionItem value="assumptions" className="border-b-0">
<AccordionTrigger className="pt-0">
<div className="flex flex-col gap-3">
<div className="flex items-baseline gap-2">
<h2 className="text-2xl font-medium">Restoration Plan</h2>
<span className="font-normal text-muted-foreground">
optional
</span>
</div>
<p className="font-normal text-muted-foreground">
Overrides annual hectares inputs
</p>
</div>
</AccordionTrigger>
<AccordionContent className="pb-0">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="divide-x-0">
{headerGroup.headers.map((header) => {
return (
<TableHead
key={header.id}
className="bg-transparent font-normal"
style={{
width: header.column.getSize(),
}}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
className="divide-x-0 divide-y-0 border-b-0"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="py-3">
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={COLUMNS.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</AccordionContent>
</AccordionItem>
</Accordion>
);
}
2 changes: 0 additions & 2 deletions client/src/containers/projects/form/setup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ export default function SetupProjectForm() {
disabled: isDisabled(ecosystem),
}));

console.log(form.formState.errors);

return (
<Accordion type="single" collapsible defaultValue="assumptions">
<AccordionItem value="assumptions" className="border-b-0">
Expand Down
10 changes: 9 additions & 1 deletion client/src/containers/projects/new/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ export const onSubmit = (data: CreateCustomProjectForm) => {
// @ts-expect-error fix later
plantingSuccessRate: plantingSuccessRate / 100,
}),
...(originalValues.activity === ACTIVITY.RESTORATION && {
restorationYearlyBreakdown: [
...new Set(originalValues.parameters.restorationYearlyBreakdown),
].map((hectareas, index) => ({
year: index,
annualHectaresRestored: hectareas,
})),
}),
},
assumptions: {
...Object.keys(originalValues.assumptions ?? {}).reduce(
Expand Down Expand Up @@ -128,7 +136,7 @@ export default function CreateCustomProject() {
resolver: zodResolver(CreateCustomProjectSchema),
defaultValues: {
projectName: "test",
activity: ACTIVITY.CONSERVATION,
activity: ACTIVITY.RESTORATION,
ecosystem: ECOSYSTEM.SEAGRASS,
countryCode: countryOptions?.[0]?.value,
projectSizeHa: 20,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const RestorationCustomProjectSchema = z
plantingSuccessRate: z.preprocess(parseNumber, z.number().nonnegative({
message: 'Planting Success Rate should be a non-negative number',
})),
restorationYearlyBreakdown: z.array(z.preprocess(parseNumber, z.number()).optional()).optional(),
})
.superRefine((data, ctx) => {
if (
Expand All @@ -82,7 +83,9 @@ export const AssumptionsSchema = z.object({
restorationRate: z.preprocess(parseNumber, z.number().positive()).optional(),
carbonPriceIncrease: z.preprocess(parseNumber, z.number().positive()).optional(),
buffer: z.preprocess(parseNumber, z.number().positive()).optional(),
projectLength: z.preprocess(parseNumber, z.number().positive()).optional(),
projectLength: z.preprocess(parseNumber, z.number().positive().min(1).max(40, {
message: 'Project Length should be between 1 and 40 years',
})).optional(),
});

export const InputCostsSchema = z.object({
Expand Down

0 comments on commit 06d8ad2

Please sign in to comment.