mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Merge branch 'main' of github.com:Hestia-Homes/assessment-model into feature/upload_a_file_to_s2_bucket_via_lambda
This commit is contained in:
commit
3986e99887
9 changed files with 238 additions and 136 deletions
|
|
@ -1,28 +1,36 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { MeasureKeyEnum } from "@/app/db/schema/recommendations";
|
||||
import { PortfolioGoal } from "@/app/db/schema/portfolio";
|
||||
|
||||
const PresignedUrlBodySchema = z.object({
|
||||
portfolio_id: z.string(),
|
||||
housing_type: z.enum(["Social", "Private"]),
|
||||
goal: z.enum(["Increasing EPC", "Reduce energy consumption"]),
|
||||
goal_value: z.string(),
|
||||
trigger_file_path: z.string(),
|
||||
valuation_file_path: z.string(),
|
||||
multi_plan: z.boolean().optional(),
|
||||
budget: z.number().optional().nullable(),
|
||||
scenario_name: z.string().optional(),
|
||||
event_type: z.enum(["remote_assessment"]).optional(),
|
||||
// inclusions is a list of measures, where the values are in measuresList
|
||||
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(),
|
||||
file_type: z.enum(["csv", "xlsx"]).optional(), // Specify the file type
|
||||
file_format: z.enum(["domna_asset_list"]).optional().nullable(), // Specify the file format
|
||||
sheet_name: z.string().optional().nullable(), // Specify the sheet name if applicable
|
||||
});
|
||||
const PresignedUrlBodySchema = z
|
||||
.object({
|
||||
portfolio_id: z.string(),
|
||||
housing_type: z.enum(["Social", "Private"]),
|
||||
goal: z.enum(PortfolioGoal),
|
||||
goal_value: z.string().nullable(), // Nullable to handle cases where goal_value is not applicable
|
||||
trigger_file_path: z.string(),
|
||||
valuation_file_path: z.string(),
|
||||
multi_plan: z.boolean().optional(),
|
||||
budget: z.number().optional().nullable(),
|
||||
scenario_name: z.string().optional(),
|
||||
sheet_count: z.number().optional(), // Number of rows in the selected sheet
|
||||
event_type: z.enum(["remote_assessment"]).optional(),
|
||||
ashp_cop: z.number().optional(),
|
||||
// inclusions is a list of measures, where the values are in measuresList
|
||||
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(),
|
||||
file_type: z.enum(["csv", "xlsx"]).optional(), // Specify the file type
|
||||
file_format: z.enum(["domna_asset_list"]).optional().nullable(), // Specify the file format
|
||||
sheet_name: z.string().optional().nullable(), // Specify the sheet name if applicable
|
||||
})
|
||||
.refine((data) => data.goal !== "Increasing EPC" || !!data.goal_value, {
|
||||
path: ["goal_value"],
|
||||
message: "Target EPC Rating is required when goal is Increasing EPC",
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// For the moment, this api specifically handles uploads of csvs
|
||||
|
|
|
|||
|
|
@ -23,7 +23,34 @@ interface ToolbarProps {
|
|||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"bg-gray-50 cursor-pointer group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-gray-200 hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-gray-200"
|
||||
[
|
||||
"bg-gray-50",
|
||||
"cursor-pointer",
|
||||
"group",
|
||||
"inline-flex",
|
||||
"h-10",
|
||||
"w-max",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"rounded-md",
|
||||
"bg-background",
|
||||
"px-4",
|
||||
"py-2",
|
||||
"text-sm",
|
||||
"font-medium",
|
||||
"transition-colors",
|
||||
"hover:bg-gray-200",
|
||||
"hover:text-accent-foreground",
|
||||
"focus:bg-accent",
|
||||
"focus:text-accent-foreground",
|
||||
"focus:outline-none",
|
||||
"disabled:pointer-events-none",
|
||||
"disabled:opacity-50",
|
||||
"data-[active]:bg-accent/50",
|
||||
"data-[state=open]:bg-gray-200",
|
||||
//
|
||||
"text-gray-900",
|
||||
].join(" ")
|
||||
);
|
||||
|
||||
export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default function AddNewDropDown({
|
|||
|
||||
return (
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger className="bg-gray-50">
|
||||
<NavigationMenuTrigger className="bg-gray-50 text-gray-900">
|
||||
Add New
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ function SummaryBox({ scenarios, numProperties }: SummaryBoxProps) {
|
|||
|
||||
return (
|
||||
<div className="p-6 bg-white rounded-lg leading-relaxed">
|
||||
<h2 className="text-2xl font-bold mb-4 text-brandblue text-center">
|
||||
<h2 className="text-2xl font-bold mb-4 text-brandblue text-center ">
|
||||
Portfolio Summary
|
||||
</h2>
|
||||
<div className="mb-4 flex items-center justify-center">
|
||||
|
|
@ -102,7 +102,7 @@ function SummaryBox({ scenarios, numProperties }: SummaryBoxProps) {
|
|||
id="scenario-select"
|
||||
value={selectedScenarioId}
|
||||
onChange={(e) => handleScenarioChange(e.target.value)}
|
||||
className="p-2 border rounded w-full"
|
||||
className="p-2 border rounded w-full text-black"
|
||||
>
|
||||
{scenarios.map((scenario) => (
|
||||
<option key={String(scenario.id)} value={String(scenario.id)}>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ interface ToolbarProps {
|
|||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"bg-gray-50 cursor-pointer group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-gray-200 hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-gray-200 "
|
||||
"bg-gray-50 cursor-pointer group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-gray-200 hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-gray-200 text-gray-900"
|
||||
);
|
||||
|
||||
export function Toolbar({ portfolioId, scenarios }: ToolbarProps) {
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ const SummaryTable = ({
|
|||
}}
|
||||
/>
|
||||
|
||||
<div className="overflow-x-auto shadow-md sm:rounded-lg">
|
||||
<div className="text-black overflow-x-auto shadow-md sm:rounded-lg">
|
||||
<Table className="min-w-full">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// formSchemas.ts
|
||||
import * as z from "zod";
|
||||
import { MeasureKeyEnum } from "@/app/db/schema/recommendations";
|
||||
import { MeasureKeyEnum, HousingType } from "@/app/db/schema/recommendations";
|
||||
|
||||
export const baseFormSchema = z.object({
|
||||
measures: z.array(MeasureKeyEnum).min(1, "At least one measure is required"),
|
||||
|
|
@ -8,18 +8,34 @@ export const baseFormSchema = z.object({
|
|||
|
||||
export type BaseFormValues = z.infer<typeof baseFormSchema>;
|
||||
|
||||
export const RemoteAssessmentFormSchema = baseFormSchema.extend({
|
||||
scenario: z.string().min(1),
|
||||
goal: z.string().min(1),
|
||||
goalValue: z.string().min(1),
|
||||
housingType: z.string().min(1),
|
||||
addressLineOne: z.string().min(1),
|
||||
postcode: z.string().min(1),
|
||||
uprn: z.number().min(1, "UPRN must be a valid number"),
|
||||
valuation: z.number().min(1, "Valuation must be a valid number"),
|
||||
propertyType: z.string().nullable(),
|
||||
builtForm: z.string().nullable(),
|
||||
});
|
||||
export const RemoteAssessmentFormSchema = baseFormSchema
|
||||
.extend({
|
||||
scenario: z.string().min(1),
|
||||
goal: z.string().min(1),
|
||||
// goalValue: z.string().min(1),
|
||||
goalValue: z.string().optional(),
|
||||
budget: z.number().optional(),
|
||||
housingType: z.enum(HousingType),
|
||||
addressLineOne: z.string().min(1),
|
||||
postcode: z.string().min(1),
|
||||
uprn: z.number().min(1, "UPRN must be a valid number"),
|
||||
valuation: z.number().min(1, "Valuation must be a valid number"),
|
||||
propertyType: z.string().nullable(),
|
||||
builtForm: z.string().nullable(),
|
||||
})
|
||||
.refine((data) => data.goal !== "Increasing EPC" || !!data.goalValue, {
|
||||
path: ["goalValue"],
|
||||
message: "Target EPC Rating is required when goal is Increasing EPC",
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
data.goal === "Increasing EPC" ||
|
||||
(typeof data.budget === "number" && data.budget > 0),
|
||||
{
|
||||
path: ["budget"],
|
||||
message: "Budget is required for this goal",
|
||||
}
|
||||
);
|
||||
|
||||
export type RemoteAssessmentFormValues = z.infer<
|
||||
typeof RemoteAssessmentFormSchema
|
||||
|
|
|
|||
|
|
@ -119,9 +119,14 @@ const selectGoalOptions = [
|
|||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Reduce energy consumption",
|
||||
value: "Reduce energy consumption",
|
||||
disabled: true,
|
||||
label: "Energy Savings",
|
||||
value: "Energy Savings",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Reducing CO2 emissions",
|
||||
value: "Reducing CO2 emissions",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -147,7 +152,7 @@ interface EngineTriggerBody {
|
|||
portfolio_id: string;
|
||||
housing_type: string;
|
||||
goal: string;
|
||||
goal_value: string;
|
||||
goal_value: string | null;
|
||||
trigger_file_path: string;
|
||||
already_installed_file_path: string;
|
||||
patches_file_path: string;
|
||||
|
|
@ -155,7 +160,7 @@ interface EngineTriggerBody {
|
|||
valuation_file_path: string;
|
||||
scenario_name: string;
|
||||
multi_plan: boolean;
|
||||
budget: null;
|
||||
budget: number | null;
|
||||
event_type: string;
|
||||
inclusions: (typeof measuresList)[number][];
|
||||
scenario_id?: string | null;
|
||||
|
|
@ -357,12 +362,18 @@ function useCreateRemoteAssessment({
|
|||
|
||||
async function triggerEngine(data: RemoteAssessmentFormValues) {
|
||||
try {
|
||||
// Goal value should not be missing at this point
|
||||
if (data.goal === "Increasing EPC" && !data.goalValue) {
|
||||
throw new Error("Goal value is required");
|
||||
}
|
||||
|
||||
const triggerBody: EngineTriggerBody = {
|
||||
scenario_id: scenarioId === "__new__" ? null : scenarioId,
|
||||
portfolio_id: portfolioId,
|
||||
housing_type: data.housingType,
|
||||
goal: data.goal,
|
||||
goal_value: data.goalValue,
|
||||
// We only send goal_value if the goal is "Increasing EPC"
|
||||
goal_value: data.goalValue || null,
|
||||
trigger_file_path: assetListFileKey,
|
||||
already_installed_file_path: "",
|
||||
patches_file_path: "",
|
||||
|
|
@ -371,7 +382,8 @@ function useCreateRemoteAssessment({
|
|||
scenario_name: data.scenario,
|
||||
inclusions: data.measures,
|
||||
multi_plan: true,
|
||||
budget: null,
|
||||
// If the goal is "Increasing EPC", we don't send a budget
|
||||
budget: data.budget || null,
|
||||
event_type: "remote_assessment",
|
||||
};
|
||||
|
||||
|
|
@ -461,10 +473,11 @@ export default function RemoteAssessmentModal({
|
|||
housingType: "",
|
||||
goal: "",
|
||||
goalValue: "",
|
||||
budget: undefined,
|
||||
addressLineOne: "",
|
||||
postcode: "",
|
||||
uprn: undefined, // Must match expected type
|
||||
valuation: undefined, // Must match expected type
|
||||
uprn: undefined,
|
||||
valuation: undefined,
|
||||
propertyType: null,
|
||||
builtForm: null,
|
||||
measures: measuresList,
|
||||
|
|
@ -474,6 +487,7 @@ export default function RemoteAssessmentModal({
|
|||
const { isValid, isSubmitting } = formState;
|
||||
|
||||
const measures = form.watch("measures");
|
||||
const goal = form.watch("goal");
|
||||
|
||||
const {
|
||||
handleSubmit: triggerAssessment,
|
||||
|
|
@ -657,32 +671,73 @@ export default function RemoteAssessmentModal({
|
|||
)}
|
||||
/>
|
||||
|
||||
{/* Goal Value */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="goalValue"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-gray-800">
|
||||
Goal Value
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={goalValueOptions}
|
||||
selectedOption={field.value}
|
||||
onSelectOption={(opt) =>
|
||||
field.onChange(opt.value)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value} disabled />
|
||||
{goal && (
|
||||
<>
|
||||
{goal === "Increasing EPC" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="goalValue"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-gray-800">
|
||||
Target EPC Rating
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
{selectedScenario === NEW_SENTINEL ? (
|
||||
<SelectDropdown
|
||||
options={goalValueOptions}
|
||||
selectedOption={field.value || ""}
|
||||
onSelectOption={(opt) =>
|
||||
field.onChange(opt.value)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Input value={field.value} disabled />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage className="text-brandbrown" />
|
||||
</FormItem>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage className="text-brandbrown" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ✅ Budget shows for ALL goals but is only mandatory when goal != Increasing EPC */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="budget"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-gray-800">
|
||||
{/* We mark budget as (optional) when the goal is increasing EPC*/}
|
||||
Budget (£){" "}
|
||||
{goal === "Increasing EPC" && (
|
||||
<span className="text-sm text-gray-500">
|
||||
(optional)
|
||||
</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Enter budget"
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value === ""
|
||||
? undefined
|
||||
: Number(e.target.value)
|
||||
)
|
||||
}
|
||||
className="border-brandbrown focus-visible:ring-brandbrown focus-visible:border-brandbrown"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage className="text-brandbrown" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export async function validateClientFile(file: File): Promise<{
|
|||
file_type?: "csv" | "xlsx";
|
||||
isStandardised?: boolean;
|
||||
sheetNames?: string[];
|
||||
sheetRowCounts?: Record<string, number>;
|
||||
fileFormat?: "domna_asset_list" | null;
|
||||
}> {
|
||||
const sizeMB = file.size / (1024 * 1024);
|
||||
|
|
@ -114,45 +115,78 @@ export async function validateClientFile(file: File): Promise<{
|
|||
|
||||
let isStandardised = false;
|
||||
let sheetNames: string[] = [];
|
||||
let sheetRowCounts: Record<string, number> = {};
|
||||
|
||||
if (fileType === "csv") {
|
||||
const text = await file.text();
|
||||
const lines = text.split("\n").map((line) => line.split(","));
|
||||
const headers = lines[0].map((h) => h.trim());
|
||||
isStandardised = headers.includes("domna_property_id");
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
file_type: "csv",
|
||||
isStandardised,
|
||||
fileFormat: isStandardised ? "domna_asset_list" : null,
|
||||
};
|
||||
}
|
||||
|
||||
if (fileType === "xlsx") {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
const workbook = XLSX.read(arrayBuffer, {
|
||||
type: "array",
|
||||
sheets: ["Standardised Asset List"],
|
||||
});
|
||||
|
||||
const workbook = XLSX.read(arrayBuffer, { type: "array" });
|
||||
sheetNames = workbook.SheetNames;
|
||||
|
||||
const worksheet = workbook.Sheets["Standardised Asset List"];
|
||||
if (worksheet) {
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
const headers = jsonData[0] as string[];
|
||||
isStandardised = headers?.includes("domna_property_id");
|
||||
for (const sheetName of sheetNames) {
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
if (!worksheet) continue;
|
||||
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
|
||||
header: 1,
|
||||
}) as any[][];
|
||||
|
||||
if (sheetName === "Standardised Asset List") {
|
||||
const headers = jsonData[0] as string[];
|
||||
isStandardised = headers?.includes("domna_property_id");
|
||||
}
|
||||
|
||||
// Count data rows, ignoring trailing blank rows and headers
|
||||
const rowCount = jsonData
|
||||
.slice(1) // skip header
|
||||
.filter((row) =>
|
||||
row.some((cell) => cell !== undefined && cell !== null && cell !== "")
|
||||
).length;
|
||||
|
||||
sheetRowCounts[sheetName] = rowCount;
|
||||
}
|
||||
|
||||
console.log("Sheet Counts", sheetRowCounts);
|
||||
|
||||
if (!isStandardised) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: "Excel file is not a valid domna asset list.",
|
||||
file_type: "xlsx",
|
||||
sheetNames,
|
||||
sheetRowCounts,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
file_type: "xlsx",
|
||||
isStandardised,
|
||||
fileFormat: "domna_asset_list",
|
||||
sheetNames,
|
||||
sheetRowCounts,
|
||||
};
|
||||
}
|
||||
|
||||
// fallback
|
||||
return {
|
||||
isValid: true,
|
||||
file_type: fileType,
|
||||
isStandardised,
|
||||
sheetNames: fileType === "xlsx" && isStandardised ? sheetNames : undefined,
|
||||
fileFormat: isStandardised ? "domna_asset_list" : null,
|
||||
isStandardised: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +232,9 @@ export function useUploadCsvPlan({
|
|||
goalValue,
|
||||
housingType,
|
||||
scenarioName,
|
||||
scenarioId,
|
||||
selectedSheet,
|
||||
sheetCount,
|
||||
budget,
|
||||
ashpCop,
|
||||
measures,
|
||||
|
|
@ -214,7 +250,9 @@ export function useUploadCsvPlan({
|
|||
ashpCop: number;
|
||||
housingType: string;
|
||||
scenarioName: string;
|
||||
scenarioId?: string | null;
|
||||
selectedSheet: string;
|
||||
sheetCount: number;
|
||||
measures: (typeof measuresList)[number][];
|
||||
onSuccessRedirect: (path: string) => void;
|
||||
fileType: "csv" | "xlsx";
|
||||
|
|
@ -241,7 +279,7 @@ export function useUploadCsvPlan({
|
|||
await uploadFileToS3({ presignedUrl: url, file });
|
||||
|
||||
const body = {
|
||||
scenario_id: scenarioName === NEW_SENTINEL ? null : scenarioName,
|
||||
scenario_id: scenarioName === NEW_SENTINEL ? null : scenarioId,
|
||||
portfolio_id: portfolioId,
|
||||
housing_type: housingType,
|
||||
goal: goal,
|
||||
|
|
@ -257,7 +295,8 @@ export function useUploadCsvPlan({
|
|||
inclusions: measures,
|
||||
event_type: "remote_assessment",
|
||||
sheet_name: selectedSheet,
|
||||
ashp_cop: ashpCop,
|
||||
sheet_count: sheetCount,
|
||||
ashp_cop: Number(ashpCop),
|
||||
file_type: fileType, // Pass the file type for backend processing
|
||||
file_format: fileFormat,
|
||||
};
|
||||
|
|
@ -321,6 +360,7 @@ export default function UploadCsvModal({
|
|||
const [fileFormat, setFileFormat] = useState<"domna_asset_list" | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [fileIsValid, setFileIsValid] = useState<boolean | null>(null);
|
||||
const [sheetCounts, setSheetCounts] = useState<Record<string, number>>({});
|
||||
|
||||
const scenarioOptions = useMemo(
|
||||
() =>
|
||||
|
|
@ -363,11 +403,13 @@ export default function UploadCsvModal({
|
|||
budget: form.watch("budget") || null,
|
||||
ashpCop: form.watch("ashpCop"),
|
||||
scenarioName: form.watch("scenario"),
|
||||
scenarioId: selectedScenario === NEW_SENTINEL ? null : selectedScenario,
|
||||
measures: form.watch("measures"),
|
||||
fileType: fileType,
|
||||
fileFormat: fileFormat,
|
||||
portfolioId,
|
||||
selectedSheet,
|
||||
sheetCount: sheetCounts[selectedSheet] || 0,
|
||||
onSuccessRedirect: (path) => router.push(path),
|
||||
});
|
||||
|
||||
|
|
@ -442,6 +484,7 @@ export default function UploadCsvModal({
|
|||
setCsvFile(file);
|
||||
setFileType(result.file_type!);
|
||||
setFileFormat(result.fileFormat ?? null);
|
||||
setSheetCounts(result.sheetRowCounts || {});
|
||||
|
||||
if (result.sheetNames) {
|
||||
setSheetNames(result.sheetNames);
|
||||
|
|
@ -657,53 +700,6 @@ export default function UploadCsvModal({
|
|||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="cop"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Heat pump COP</FormLabel>
|
||||
<FormControl>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
{...field}
|
||||
disabled={selectedScenario !== "__new__"}
|
||||
className={`w-full rounded-lg border ${
|
||||
selectedScenario !== "__new__"
|
||||
? "bg-gray-100"
|
||||
: "bg-white"
|
||||
} border-brandbrown px-4 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brandbrown`}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="budget"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Budget (optional)</FormLabel>
|
||||
<FormControl>
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value === ""
|
||||
? null
|
||||
: parseFloat(e.target.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue