Merge pull request #62 from Hestia-Homes/engine-file-upload

fixing the dark view issues
This commit is contained in:
KhalimCK 2025-07-28 16:55:55 +01:00 committed by GitHub
commit b04daaa225
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 89 additions and 20 deletions

View file

@ -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(),

View file

@ -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) {

View file

@ -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>

View file

@ -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)}>

View file

@ -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) {

View file

@ -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) => (

View file

@ -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);