diff --git a/src/app/components/portfolio/plan/PlanTable.tsx b/src/app/components/portfolio/plan/PlanTable.tsx new file mode 100644 index 0000000..d711dbc --- /dev/null +++ b/src/app/components/portfolio/plan/PlanTable.tsx @@ -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 { + columns: ColumnDef[]; + data: T[]; +} + +export default function FeatureTable({ + data, + columns, +}: DataTableProps) { + // Initialise the table + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ ); +} diff --git a/src/app/components/portfolio/plan/PlanTableColumns.tsx b/src/app/components/portfolio/plan/PlanTableColumns.tsx new file mode 100644 index 0000000..da705e2 --- /dev/null +++ b/src/app/components/portfolio/plan/PlanTableColumns.tsx @@ -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[] = [ + { + accessorKey: "materialType", + header: "Retrofit Measure", + }, + { + accessorKey: "Number o", + header: "Number of Properties", + }, + { + accessorKey: "quantity", + header: "Number of Properties", + }, + { + accessorKey: "estimatedCost", + header: "Estimated Cost", + }, +]; diff --git a/src/app/db/schema/recommendations.ts b/src/app/db/schema/recommendations.ts index 78a8d92..40df900 100644 --- a/src/app/db/schema/recommendations.ts +++ b/src/app/db/schema/recommendations.ts @@ -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; +} diff --git a/src/app/portfolio/[slug]/utils.ts b/src/app/portfolio/[slug]/utils.ts index adbf53a..ac170be 100644 --- a/src/app/portfolio/[slug]/utils.ts +++ b/src/app/portfolio/[slug]/utils.ts @@ -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 { 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 }; + } = {}; 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; }