mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
fixed form logic
This commit is contained in:
parent
ca63fa7c99
commit
6df5dc4b23
2 changed files with 262 additions and 48 deletions
|
|
@ -50,11 +50,14 @@ export const uploadCsvSchema = baseFormSchema.extend({
|
|||
if (val === "" || val === undefined) return undefined;
|
||||
return Number(val);
|
||||
}, z.number().min(0.1)),
|
||||
budget: z.preprocess((val) => {
|
||||
if (val === "" || val === undefined) return undefined;
|
||||
if (val === null) return null;
|
||||
return Number(val);
|
||||
}, z.union([z.number(), z.null()]).optional()),
|
||||
budget: z.preprocess(
|
||||
(val) => {
|
||||
if (val === "" || val === undefined) return undefined;
|
||||
if (val === null) return null;
|
||||
return Number(val);
|
||||
},
|
||||
z.union([z.number(), z.null()]).optional()
|
||||
),
|
||||
});
|
||||
|
||||
export type UploadCsvFormValues = z.infer<typeof uploadCsvSchema>;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,122 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormMessage,
|
||||
} from "@/app/shadcn_components/ui/form";
|
||||
|
||||
import { Card } from "@/app/shadcn_components/ui/card";
|
||||
import { Input } from "@/app/shadcn_components/ui/input";
|
||||
import { Button } from "@/app/shadcn_components/ui/button";
|
||||
import { SelectDropdown } from "@/app/portfolio/[slug]/components/RemoteAssessmentDropdowns";
|
||||
import {
|
||||
SelectScenarioDropdown,
|
||||
SelectDropdown,
|
||||
ScenarioOption,
|
||||
} from "@/app/portfolio/[slug]/components/RemoteAssessmentDropdowns";
|
||||
import MeasuresCheckboxes from "@/app/portfolio/[slug]/components/MeasuresCheckboxes";
|
||||
import { measuresList } from "@/app/db/schema/recommendations";
|
||||
import { ScenarioSelect } from "@/app/db/schema/recommendations";
|
||||
import {
|
||||
RemoteAssessmentFormSchema,
|
||||
type RemoteAssessmentFormValues,
|
||||
} from "@/app/portfolio/[slug]/components/FormSchema";
|
||||
|
||||
const housingTypeOptions = [
|
||||
{ label: "Social", value: "Social" },
|
||||
{ label: "Private", value: "Private" },
|
||||
];
|
||||
|
||||
// -------------------
|
||||
// Component
|
||||
// -------------------
|
||||
export default function ScenarioSetup({
|
||||
portfolioId,
|
||||
scenarios,
|
||||
disabled = false,
|
||||
onSubmitScenario,
|
||||
}: {
|
||||
portfolioId: string;
|
||||
scenarios: {
|
||||
id: string;
|
||||
name: string;
|
||||
housingType: string;
|
||||
goal: string;
|
||||
goalValue: string | null;
|
||||
}[];
|
||||
scenarios: ScenarioSelect[];
|
||||
disabled?: boolean;
|
||||
onSubmitScenario?: (values: RemoteAssessmentFormValues) => void;
|
||||
}) {
|
||||
const NEW_SENTINEL = "__new__";
|
||||
const [selectedScenario, setSelectedScenario] = useState<string | null>(null);
|
||||
const [showMeasures, setShowMeasures] = useState(false);
|
||||
|
||||
const form = useForm<RemoteAssessmentFormValues>({
|
||||
resolver: zodResolver(RemoteAssessmentFormSchema),
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
scenario: "",
|
||||
goal: "",
|
||||
goalValue: "",
|
||||
budget: undefined,
|
||||
housingType: "Social",
|
||||
addressLineOne: "",
|
||||
postcode: "",
|
||||
uprn: 0,
|
||||
valuation: 0,
|
||||
propertyType: null,
|
||||
builtForm: null,
|
||||
measures: measuresList,
|
||||
},
|
||||
});
|
||||
|
||||
const { setValue, watch, handleSubmit, formState } = form;
|
||||
const values = watch();
|
||||
|
||||
const scenarioOptions: ScenarioOption[] = useMemo(
|
||||
() =>
|
||||
scenarios.map((s) => ({
|
||||
label: s.name || "",
|
||||
value: String(s.id) || "",
|
||||
housingType: s.housingType || "",
|
||||
goal: s.goal || "",
|
||||
goalValue: s.goalValue || "",
|
||||
})),
|
||||
[scenarios]
|
||||
);
|
||||
|
||||
function handleSelect(opt: ScenarioOption) {
|
||||
setSelectedScenario(opt.value);
|
||||
|
||||
if (opt.value === NEW_SENTINEL) {
|
||||
form.reset({
|
||||
...form.getValues(),
|
||||
scenario: "",
|
||||
housingType: "",
|
||||
goal: "",
|
||||
goalValue: "",
|
||||
budget: undefined,
|
||||
valuation: undefined,
|
||||
measures: measuresList, // all measures preselected
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
scenario: opt.label || "",
|
||||
housingType: opt.housingType || "",
|
||||
goal: opt.goal || "",
|
||||
goalValue: opt.goalValue || "",
|
||||
budget: undefined,
|
||||
valuation: undefined,
|
||||
measures: measuresList,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmit(data: RemoteAssessmentFormValues) {
|
||||
if (onSubmitScenario) onSubmitScenario(data);
|
||||
console.log("Submitted scenario data:", data);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
|
@ -35,42 +130,158 @@ export default function ScenarioSetup({
|
|||
Step 2: Select or Create Scenario
|
||||
</h2>
|
||||
|
||||
<SelectDropdown
|
||||
options={[
|
||||
{ label: "Create new scenario", value: "__new__" },
|
||||
...scenarios.map((s) => ({
|
||||
label: s.name,
|
||||
value: s.id,
|
||||
})),
|
||||
]}
|
||||
selectedOption={selectedScenario || ""}
|
||||
onSelectOption={(opt) => setSelectedScenario(opt.value)}
|
||||
/>
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-gray-800 text-sm font-medium mb-2">
|
||||
Select Scenario
|
||||
</label>
|
||||
<SelectScenarioDropdown
|
||||
selectedValue={selectedScenario}
|
||||
onSelect={handleSelect}
|
||||
scenarios={scenarioOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-3">
|
||||
<Input
|
||||
placeholder="Scenario name"
|
||||
disabled={disabled}
|
||||
className="border-brandbrown focus-visible:ring-brandbrown"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Budget (£)"
|
||||
disabled={disabled}
|
||||
className="border-brandbrown focus-visible:ring-brandbrown"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Valuation (optional)"
|
||||
disabled={disabled}
|
||||
className="border-brandbrown focus-visible:ring-brandbrown"
|
||||
/>
|
||||
</div>
|
||||
{selectedScenario && (
|
||||
<>
|
||||
{/* Scenario Name + Housing Type */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scenario"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Scenario Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={selectedScenario !== NEW_SENTINEL}
|
||||
placeholder="Enter scenario name"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="mt-4 bg-brandbrown hover:bg-hoverblue"
|
||||
disabled={disabled}
|
||||
>
|
||||
Save Scenario
|
||||
</Button>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="housingType"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Housing Type</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={housingTypeOptions}
|
||||
selectedOption={field.value}
|
||||
onSelectOption={(opt) => field.onChange(opt.value)}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value} disabled />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Goal + EPC */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="goal"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Goal</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={[
|
||||
{
|
||||
label: "Increasing EPC",
|
||||
value: "Increasing EPC",
|
||||
},
|
||||
{
|
||||
label: "Energy Savings",
|
||||
value: "Energy Savings",
|
||||
},
|
||||
{
|
||||
label: "Reducing CO₂ emissions",
|
||||
value: "Reducing CO2 emissions",
|
||||
},
|
||||
]}
|
||||
selectedOption={field.value}
|
||||
onSelectOption={(opt) => field.onChange(opt.value)}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value} disabled />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{values.goal === "Increasing EPC" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="goalValue"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Target EPC Rating</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={[
|
||||
{ label: "C", value: "C" },
|
||||
{ label: "B", value: "B" },
|
||||
{ label: "A", value: "A" },
|
||||
]}
|
||||
selectedOption={field.value || ""}
|
||||
onSelectOption={(opt) =>
|
||||
field.onChange(opt.value)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value || ""} disabled />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Measures Section */}
|
||||
<div className="border-t pt-4 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowMeasures(!showMeasures)}
|
||||
className="flex items-center justify-between w-full text-sm font-medium text-gray-800"
|
||||
>
|
||||
<span>Measures</span>
|
||||
<span>{showMeasures ? "−" : "+"}</span>
|
||||
</button>
|
||||
{showMeasures && <MeasuresCheckboxes form={form} />}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-2">
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-brandbrown text-white hover:bg-hoverblue"
|
||||
disabled={!formState.isValid}
|
||||
>
|
||||
Save Scenario
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
</FormProvider>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue