deplying new remote assessment model

This commit is contained in:
Khalim Conn-Kowlessar 2025-07-15 18:59:00 +01:00
parent 1f0d4f0dcb
commit 1e61b8c04a
3 changed files with 94 additions and 45 deletions

View file

@ -17,6 +17,8 @@ const PresignedUrlBodySchema = z.object({
inclusions: z.array(MeasureKeyEnum).optional(),
exclusions: z.array(MeasureKeyEnum).optional(),
already_installed_file_path: z.string().optional(),
// optional scenario_id to link the plan to an existing scenario
scenario_id: z.string().optional().nullable(),
});
export async function POST(request: NextRequest) {

View file

@ -72,6 +72,7 @@ export function SelectScenarioDropdown({
<Menu.Item key={opt.value}>
{({ active }) => (
<button
type="button"
onClick={() => onSelect(opt)}
className={
`group w-full text-left px-4 py-3 text-sm transition-colors flex flex-col space-y-1 ` +

View file

@ -158,6 +158,7 @@ interface EngineTriggerBody {
budget: null;
event_type: string;
inclusions: (typeof measuresList)[number][];
scenario_id?: string | null;
}
const formSchema = z.object({
@ -169,9 +170,9 @@ const formSchema = z.object({
postcode: z.string().min(1),
uprn: z.number().min(1),
valuation: z.number().min(1),
propertyType: z.string().optional().nullable(),
builtForm: z.string().optional().nullable(),
measures: z.array(MeasureKeyEnum),
propertyType: z.string().nullable(),
builtForm: z.string().nullable(),
measures: z.array(MeasureKeyEnum).min(1, "Select at least one measure."),
});
type FormValues = z.infer<typeof formSchema>;
@ -276,6 +277,7 @@ function useCreateRemoteAssessment({
propertyType,
builtForm,
measures,
scenarioId,
}: {
portfolioId: string;
uprn: number | null;
@ -285,6 +287,7 @@ function useCreateRemoteAssessment({
measures: (typeof measuresList)[number][];
propertyType?: string | null;
builtForm?: string | null;
scenarioId?: string | null;
}) {
// 1) We want to upload the asset data. To do this, we format the asset data, generate a presigned URL, and upload the data to S3.
// 2) We then want to upload valuation data. To do this, we format the valuation data, generate a presigned URL, and upload the data to S3.
@ -367,6 +370,7 @@ function useCreateRemoteAssessment({
async function triggerEngine(data: FormValues) {
try {
const triggerBody: EngineTriggerBody = {
scenario_id: scenarioId || null,
portfolio_id: portfolioId,
housing_type: data.housingType,
goal: data.goal,
@ -454,7 +458,7 @@ export default function RemoteAssessmentModal({
() => [
...scenarios.map((s) => ({
label: s.name || "",
value: s.name || "",
value: String(s.id) || "",
disabled: false,
})),
],
@ -465,6 +469,7 @@ export default function RemoteAssessmentModal({
const form = useForm({
resolver: zodResolver(formSchema),
mode: "onChange",
defaultValues: {
scenario: "",
housingType: "",
@ -474,12 +479,15 @@ export default function RemoteAssessmentModal({
postcode: "",
uprn: 0,
valuation: 0,
propertyType: null,
builtForm: null,
propertyType: null as string | null,
builtForm: null as string | null,
measures: measuresList,
},
});
const { reset, setValue } = form;
const { reset, setValue, formState } = form;
const { isValid, isSubmitting } = formState;
const measures = form.watch("measures");
const {
handleSubmit: triggerAssessment,
@ -493,7 +501,8 @@ export default function RemoteAssessmentModal({
valuation: form.watch("valuation"),
propertyType: form.watch("propertyType"),
builtForm: form.watch("builtForm"),
measures: form.watch("measures"),
measures: measures,
scenarioId: selectedScenario,
});
const onSelectScenario = (opt: Option) => {
@ -507,7 +516,8 @@ export default function RemoteAssessmentModal({
goalValue: "",
});
} else {
const picked = scenarios.find((s) => s.name === opt.value);
const picked = scenarios.find((s) => String(s.id) === opt.value);
if (!picked) return;
setValue("scenario", picked.name || "");
setValue("housingType", picked.housingType);
@ -840,7 +850,7 @@ export default function RemoteAssessmentModal({
<span>Measures</span>
<span>{showMeasures ? "" : "+"}</span>
</button>
{showMeasures && <MeasuresCheckboxes />}
{showMeasures && <MeasuresCheckboxes form={form} />}
</div>
<div className="flex justify-end gap-4">
@ -851,8 +861,15 @@ export default function RemoteAssessmentModal({
>
Cancel
</Button>
<Button type="submit" disabled={presignedUrlIsLoading}>
{presignedUrlIsLoading ? "Submitting…" : "Submit"}
<Button
type="submit"
disabled={
!isValid || presignedUrlIsLoading || isSubmitting
}
>
{isSubmitting || presignedUrlIsLoading
? "Submitting…"
: "Submit"}
</Button>
</div>
@ -872,40 +889,69 @@ function setIsOpen(arg0: boolean) {
throw new Error("Function not implemented.");
}
function MeasuresCheckboxes() {
const { control } = useFormContext();
function MeasuresCheckboxes({
form,
}: {
form: ReturnType<typeof useForm<FormValues>>;
}) {
const { control } = form;
const allMeasures = measuresList;
return (
<div className="grid grid-cols-2 gap-4 mt-4">
{measuresList.map((measure) => (
<FormField
key={measure}
control={control}
name="measures"
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<input
type="checkbox"
value={measure}
checked={field.value?.includes(measure) ?? true}
onChange={(e) => {
const checked = e.target.checked;
const current = new Set(field.value ?? []);
if (checked) {
current.add(measure);
} else {
current.delete(measure);
}
field.onChange(Array.from(current));
}}
className="h-4 w-4"
/>
<FormLabel className="text-sm">
{measuresDisplayLabels[measure]}
</FormLabel>
</FormItem>
)}
/>
))}
<div className="flex flex-col gap-4 mt-4 ">
<div className="flex justify-start gap-4">
<button
type="button"
onClick={() =>
form.setValue("measures", allMeasures, { shouldValidate: true })
}
className="text-sm text-brandbrown underline"
>
Select All
</button>
<button
type="button"
onClick={() =>
form.setValue("measures", [], { shouldValidate: true })
}
className="text-sm text-brandbrown underline"
>
Untick All
</button>
</div>
{/* Measures grid */}
<div className="grid grid-cols-2 gap-4">
{measuresList.map((measure) => (
<FormField
key={measure}
control={control}
name="measures"
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<input
type="checkbox"
value={measure}
checked={field.value?.includes(measure) ?? true}
onChange={(e) => {
const checked = e.target.checked;
const current = new Set(field.value ?? []);
if (checked) {
current.add(measure);
} else {
current.delete(measure);
}
field.onChange(Array.from(current));
}}
className="h-4 w-4"
/>
<FormLabel className="text-sm">
{measuresDisplayLabels[measure]}
</FormLabel>
</FormItem>
)}
/>
))}
</div>
</div>
);
}