mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Merge pull request #62 from Hestia-Homes/engine-file-upload
fixing the dark view issues
This commit is contained in:
commit
b04daaa225
7 changed files with 89 additions and 20 deletions
|
|
@ -12,7 +12,9 @@ const PresignedUrlBodySchema = z.object({
|
|||
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(),
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -200,6 +234,7 @@ export function useUploadCsvPlan({
|
|||
scenarioName,
|
||||
scenarioId,
|
||||
selectedSheet,
|
||||
sheetCount,
|
||||
budget,
|
||||
ashpCop,
|
||||
measures,
|
||||
|
|
@ -217,6 +252,7 @@ export function useUploadCsvPlan({
|
|||
scenarioName: string;
|
||||
scenarioId?: string | null;
|
||||
selectedSheet: string;
|
||||
sheetCount: number;
|
||||
measures: (typeof measuresList)[number][];
|
||||
onSuccessRedirect: (path: string) => void;
|
||||
fileType: "csv" | "xlsx";
|
||||
|
|
@ -259,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,
|
||||
};
|
||||
|
|
@ -323,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(
|
||||
() =>
|
||||
|
|
@ -371,6 +409,7 @@ export default function UploadCsvModal({
|
|||
fileFormat: fileFormat,
|
||||
portfolioId,
|
||||
selectedSheet,
|
||||
sheetCount: sheetCounts[selectedSheet] || 0,
|
||||
onSuccessRedirect: (path) => router.push(path),
|
||||
});
|
||||
|
||||
|
|
@ -445,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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue