mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
added number of properties aggregation
This commit is contained in:
parent
35a1629336
commit
3c37a6e15e
4 changed files with 148 additions and 14 deletions
84
src/app/components/portfolio/plan/PlanTable.tsx
Normal file
84
src/app/components/portfolio/plan/PlanTable.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/app/shadcn_components/ui/table";
|
||||
|
||||
import { Feature, GeneralFeature } from "@/app/db/schema/property";
|
||||
|
||||
interface DataTableProps<T extends Feature | GeneralFeature> {
|
||||
columns: ColumnDef<T>[];
|
||||
data: T[];
|
||||
}
|
||||
|
||||
export default function FeatureTable<T extends Feature | GeneralFeature>({
|
||||
data,
|
||||
columns,
|
||||
}: DataTableProps<T>) {
|
||||
// Initialise the table
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
src/app/components/portfolio/plan/PlanTableColumns.tsx
Normal file
24
src/app/components/portfolio/plan/PlanTableColumns.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"use client";
|
||||
|
||||
import { PortfolioPlanRecommendation } from "@/app/db/schema/recommendations";
|
||||
import { Badge } from "@/app/shadcn_components/ui/badge";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
export const retrofitColumns: ColumnDef<PortfolioPlanRecommendation>[] = [
|
||||
{
|
||||
accessorKey: "materialType",
|
||||
header: "Retrofit Measure",
|
||||
},
|
||||
{
|
||||
accessorKey: "Number o",
|
||||
header: "Number of Properties",
|
||||
},
|
||||
{
|
||||
accessorKey: "quantity",
|
||||
header: "Number of Properties",
|
||||
},
|
||||
{
|
||||
accessorKey: "estimatedCost",
|
||||
header: "Estimated Cost",
|
||||
},
|
||||
];
|
||||
|
|
@ -145,3 +145,19 @@ export type PlanRecommendations = InferModel<
|
|||
// We allow recommendation types to be a string however we'll set up a typing for it here to control
|
||||
// the types we expect
|
||||
export type RecommendationType = "wall_insulation" | "floor_insulation";
|
||||
|
||||
export type UnnestedRecommendation = {
|
||||
quantity: number;
|
||||
quantityUnit: string;
|
||||
estimatedCost: number;
|
||||
materialType: string;
|
||||
propertyId: string;
|
||||
};
|
||||
|
||||
export interface PortfolioPlanRecommendation {
|
||||
quantity: number;
|
||||
quantityUnit: string;
|
||||
estimatedCost: number;
|
||||
materialType: string;
|
||||
numberOfProperties: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { recommendation } from "./../../db/schema/recommendations";
|
||||
import { material } from "./../../db/schema/materials";
|
||||
import {
|
||||
recommendation,
|
||||
UnnestedRecommendation,
|
||||
PortfolioPlanRecommendation,
|
||||
} from "./../../db/schema/recommendations";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { db } from "@/app/db/db";
|
||||
import { portfolio } from "@/app/db/schema/portfolio";
|
||||
|
|
@ -8,13 +11,6 @@ import type { Portfolio } from "@/app/db/schema/portfolio";
|
|||
import type { PropertyWithTarget } from "@/app/db/schema/property";
|
||||
import { plan, planRecommendations } from "@/app/db/schema/recommendations";
|
||||
|
||||
type UnnestedRecommendation = {
|
||||
quantity: number;
|
||||
quantityUnit: string;
|
||||
estimatedCost: number;
|
||||
materialType: string;
|
||||
};
|
||||
|
||||
export async function getPortfolio(portfolioId: string): Promise<Portfolio> {
|
||||
const data = await db
|
||||
.select()
|
||||
|
|
@ -61,15 +57,21 @@ export async function getProperties(
|
|||
return data;
|
||||
}
|
||||
|
||||
interface Recommendation {
|
||||
interface UnaggregatedPortfolioPlanRecommendation {
|
||||
quantity: number;
|
||||
quantityUnit: string;
|
||||
estimatedCost: number;
|
||||
materialType: string;
|
||||
propertyId?: string;
|
||||
numberOfProperties?: number; // Optional field to hold the count of unique propertyIds
|
||||
}
|
||||
|
||||
function aggregateRecommendations(data: Recommendation[]): Recommendation[] {
|
||||
const grouped: { [key: string]: Recommendation } = {};
|
||||
function aggregateRecommendations(
|
||||
data: UnaggregatedPortfolioPlanRecommendation[]
|
||||
): PortfolioPlanRecommendation[] {
|
||||
const grouped: {
|
||||
[key: string]: PortfolioPlanRecommendation & { propertyIds: Set<string> };
|
||||
} = {};
|
||||
|
||||
data.forEach((item) => {
|
||||
// Use the combination of quantityUnit and materialType as a unique key
|
||||
|
|
@ -78,19 +80,26 @@ function aggregateRecommendations(data: Recommendation[]): Recommendation[] {
|
|||
if (!grouped[key]) {
|
||||
grouped[key] = {
|
||||
...item,
|
||||
numberOfProperties: 0, // Initialize to 0
|
||||
propertyIds: item.propertyId ? new Set([item.propertyId]) : new Set(),
|
||||
};
|
||||
} else {
|
||||
grouped[key].quantity += item.quantity;
|
||||
grouped[key].estimatedCost += item.estimatedCost;
|
||||
if (item.propertyId) {
|
||||
grouped[key].propertyIds.add(item.propertyId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Round the results to 2 decimal places
|
||||
// Round the results to 2 decimal places and compute uniquePropertyCount
|
||||
for (const key in grouped) {
|
||||
grouped[key].quantity = parseFloat(grouped[key].quantity.toFixed(2));
|
||||
grouped[key].estimatedCost = parseFloat(
|
||||
grouped[key].estimatedCost.toFixed(2)
|
||||
);
|
||||
grouped[key].numberOfProperties = grouped[key].propertyIds.size;
|
||||
delete (grouped[key] as any).propertyIds; // using type assertion to bypass the TS error
|
||||
}
|
||||
|
||||
return Object.values(grouped);
|
||||
|
|
@ -128,6 +137,7 @@ export async function getPortfolioPlan(portfolioId: string) {
|
|||
inArray(recommendation.id, recommendationIds),
|
||||
eq(recommendation.default, true)
|
||||
),
|
||||
columns: { propertyId: true },
|
||||
with: {
|
||||
recommendationMaterials: {
|
||||
with: {
|
||||
|
|
@ -154,6 +164,7 @@ export async function getPortfolioPlan(portfolioId: string) {
|
|||
quantityUnit: material.quantityUnit,
|
||||
estimatedCost: material.estimatedCost,
|
||||
materialType: material.material.type,
|
||||
propertyId: String(recommendation.propertyId),
|
||||
})
|
||||
);
|
||||
return [...acc, ...materials];
|
||||
|
|
@ -162,7 +173,6 @@ export async function getPortfolioPlan(portfolioId: string) {
|
|||
);
|
||||
|
||||
const aggregated = aggregateRecommendations(unnestedRecommendations);
|
||||
console.log(aggregated);
|
||||
|
||||
return aggregated;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue