From 8325cb6206d6aeebc1137677a3651bc219518344 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Thu, 30 Oct 2025 17:15:30 +0000 Subject: [PATCH 1/8] added files for reports --- .db-env | 2 + .devcontainer/docker-compose.yml | 13 +- package-lock.json | 4 +- package.json | 4 +- .../db/schema/crm/hubspot_company_table.ts | 18 +++ src/app/db/schema/crm/hubspot_deal_table.ts | 27 ++++ .../(portfolio)/reports/DealStageChart.tsx | 122 +++++++++++++++++ .../[slug]/(portfolio)/reports/Report.tsx | 123 ++++++++++++++++++ .../reports/SurveyedResultsPieChart.tsx | 79 +++++++++++ .../(portfolio)/reports/TableViewer.tsx | 79 +++++++++++ .../[slug]/(portfolio)/reports/page.tsx | 34 +++++ .../building-passport/[propertyId]/layout.tsx | 2 + src/lib/utils.ts | 11 ++ 13 files changed, 513 insertions(+), 5 deletions(-) create mode 100644 .db-env create mode 100644 src/app/db/schema/crm/hubspot_company_table.ts create mode 100644 src/app/db/schema/crm/hubspot_deal_table.ts create mode 100644 src/app/portfolio/[slug]/(portfolio)/reports/DealStageChart.tsx create mode 100644 src/app/portfolio/[slug]/(portfolio)/reports/Report.tsx create mode 100644 src/app/portfolio/[slug]/(portfolio)/reports/SurveyedResultsPieChart.tsx create mode 100644 src/app/portfolio/[slug]/(portfolio)/reports/TableViewer.tsx create mode 100644 src/app/portfolio/[slug]/(portfolio)/reports/page.tsx diff --git a/.db-env b/.db-env new file mode 100644 index 0000000..d30a643 --- /dev/null +++ b/.db-env @@ -0,0 +1,2 @@ +PGADMIN_DEFAULT_EMAIL=junte@domna.homes +PGADMIN_DEFAULT_PASSWORD=makingwarmhomes \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 9fdf7eb..b6b6509 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" services: frontend: @@ -14,6 +14,17 @@ services: networks: - frontend-net + pgadmin: + image: dpage/pgadmin4 + hostname: pgadmin + ports: + - 5556:80 + env_file: + - ../.db-env + restart: unless-stopped + networks: + - frontend-net + networks: frontend-net: driver: bridge diff --git a/package-lock.json b/package-lock.json index 76492d3..cc71fb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@remixicon/react": "^4.2.0", "@tanstack/react-query": "^4.29.12", "@tanstack/react-table": "^8.9.3", - "@tremor/react": "^3.16.0", + "@tremor/react": "^3.18.7", "@types/node": "20.2.3", "@types/react": "18.3.1", "@types/react-dom": "18.3.1", @@ -64,7 +64,7 @@ "zod": "^3.23.8" }, "devDependencies": { - "@tailwindcss/forms": "^0.5.7", + "@tailwindcss/forms": "^0.5.10", "@testing-library/cypress": "^10.0.3", "@types/nodemailer": "^7.0.2", "@types/pg": "^8.10.2", diff --git a/package.json b/package.json index 922bc3e..fcb44cc 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@remixicon/react": "^4.2.0", "@tanstack/react-query": "^4.29.12", "@tanstack/react-table": "^8.9.3", - "@tremor/react": "^3.16.0", + "@tremor/react": "^3.18.7", "@types/node": "20.2.3", "@types/react": "18.3.1", "@types/react-dom": "18.3.1", @@ -70,7 +70,7 @@ "zod": "^3.23.8" }, "devDependencies": { - "@tailwindcss/forms": "^0.5.7", + "@tailwindcss/forms": "^0.5.10", "@testing-library/cypress": "^10.0.3", "@types/nodemailer": "^7.0.2", "@types/pg": "^8.10.2", diff --git a/src/app/db/schema/crm/hubspot_company_table.ts b/src/app/db/schema/crm/hubspot_company_table.ts new file mode 100644 index 0000000..71aa419 --- /dev/null +++ b/src/app/db/schema/crm/hubspot_company_table.ts @@ -0,0 +1,18 @@ +import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core"; + +export const hubspotCompanyData = pgTable("hubspot_company_data", { + id: uuid("id").defaultRandom().primaryKey(), + + companyId: text("company_id").notNull(), + companyName: text("company_name"), + groupId: text("group_id"), + + createdAt: timestamp("created_at", { precision: 6, withTimezone: true }) + .defaultNow() + .notNull(), + + updatedAt: timestamp("updated_at", { precision: 6, withTimezone: true }) + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), +}); diff --git a/src/app/db/schema/crm/hubspot_deal_table.ts b/src/app/db/schema/crm/hubspot_deal_table.ts new file mode 100644 index 0000000..753a737 --- /dev/null +++ b/src/app/db/schema/crm/hubspot_deal_table.ts @@ -0,0 +1,27 @@ +import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core"; +import { InferModel } from "drizzle-orm"; + +export const hubspotDealData = pgTable("hubspot_deal_data", { + id: uuid("id").defaultRandom().primaryKey(), + + dealId: text("deal_id").notNull(), + dealname: text("dealname"), + dealstage: text("dealstage"), + companyId: text("company_id"), + projectCode: text("project_code"), + + landlordPropertyId: text("landlord_property_id"), + uprn: text("uprn"), + outcome: text("outcome"), + outcomeNotes: text("outcome_notes"), + + createdAt: timestamp("created_at", { precision: 6, withTimezone: true }) + .defaultNow() + .notNull(), + + updatedAt: timestamp("updated_at", { precision: 6, withTimezone: true }) + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), +}); + diff --git a/src/app/portfolio/[slug]/(portfolio)/reports/DealStageChart.tsx b/src/app/portfolio/[slug]/(portfolio)/reports/DealStageChart.tsx new file mode 100644 index 0000000..4a6bd73 --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/reports/DealStageChart.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { BarList, Card, Title } from "@tremor/react"; +import TableViewer from "./TableViewer"; + +const STAGE_ORDER = [ + "Initial Planning", // 0 + "Booking Team to contact Tenant", // 1 + "Survey in Progress", // 2 + "Not viable", // 3 + "Needs HA Support", // 4 + "Coordination + Design", // 5 + "Ready to be installed" //7 +]; + +const STAGE_LABELS: Record = { + "1617223910": STAGE_ORDER[0], + "3583836399": STAGE_ORDER[0], + "3589581001": STAGE_ORDER[1], + "3569878239": STAGE_ORDER[1], + "1617223911": STAGE_ORDER[1], + "1984184569": STAGE_ORDER[1], + "3569572028": STAGE_ORDER[1], + "3570936026": STAGE_ORDER[1], + "2663668937": STAGE_ORDER[1], + "1984401629": STAGE_ORDER[1], + "1617223912": STAGE_ORDER[2], + "1617223913": STAGE_ORDER[2], + "2558220518": STAGE_ORDER[1], + "3474594026": STAGE_ORDER[1], + "3206388924": STAGE_ORDER[2], + "1617223915": STAGE_ORDER[2], + "1617223917": STAGE_ORDER[2], + "1887735998": STAGE_ORDER[3], + "3061261536": STAGE_ORDER[4], + "2571417798": "[Ops] Surveyed under 2019 - Needs Re-survey", + "1617223914": STAGE_ORDER[5], + "1887736000": "[Deprecated, please don't use] Files Missing From Assessor", + "1617223916": "[Ops] Properties to Review Manually", + "2628341989": STAGE_ORDER[2], + "3441170637": STAGE_ORDER[2], // check if assessor or coordination + "2628233422": STAGE_ORDER[5], + "1887735999": STAGE_ORDER[4], + "1960060104": "[Ops] HA Informed", + "1960060105": "[Ops] HA Works Scheduled", + "1960060106": "[Ops] HA Works Complete", + "1668803772": "[Ops] ERF Delivered to HA", + "1668803773": "[Ops] ERF Signed", + "2769407183": "[Ops] PV - Needs Heating Upgrade (Pre EPR D)", + "2769407184": "[Ops] Talk to client, Needs Heating Upgrade (Pre EPR C)", + "2702650617": STAGE_ORDER[5], + "2473886962": STAGE_ORDER[5], + "3016601828": STAGE_ORDER[4], + "3389868276": "[Engagement Team] Blocked - Needs Completion of Pilot", + "3389880508": "[Engagement Team] Blocked - Installer Negotiation", + "3399016689": "[Engagement Team] Eligible but blocked - part of incomplete flat", + "1668803774": STAGE_ORDER[6], // Ready for Invoicing + "3440363736": STAGE_ORDER[6], // [Finance] Needs Invoicing - Files Sent + "1618526429": "[Ops] Invoiced - Send Files to Installer", + "3080225005": "[Ops] Files Sent to Installer", + "1961258215": "[Ops] Installer Cancelled - Finalized", + "1961258214": "[Ops] Installer Cancelled - In Progress", + "1961258213": "[Ops] Install Scheduled", + "1617223918": "[Ops] Install Complete", + "1961258216": "[Compliance] Lodgement Complete", + "1961258217": "[Compliance] Documentation Sent to HA", + "3027432668": "[Team ???] Submitted to " +} + +interface DealStageChartProps { + deals: any[]; + onOpenTable?: (stageName: string, filteredDeals: any[]) => void; +} + +export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { + const data = useMemo(() => { + const counts: Record = {}; + deals.forEach((d) => { + const stageId = d.dealstage || "unknown"; + const stageName = STAGE_LABELS[stageId] || "Unknown Stage"; + counts[stageName] = (counts[stageName] || 0) + 1; + }); + + const unsorted = Object.entries(counts).map(([name, value]) => ({ + name, + value, + })); + + return unsorted.sort((a, b) => { + const aIndex = STAGE_ORDER.indexOf(a.name); + const bIndex = STAGE_ORDER.indexOf(b.name); + return ( + (aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex) - + (bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex) + ); + }); + }, [deals]); + + // handle click event + const handleBarClick = (value: { name: string; value: number }) => { + const filtered = deals.filter((d) => { + const stageId = d.dealstage || "unknown"; + const stageName = STAGE_LABELS[stageId] || "Unknown Stage"; + return stageName === value.name; + }); + + onOpenTable?.(value.name, filtered); + }; + + return ( + + Project Progress + + + ); +} \ No newline at end of file diff --git a/src/app/portfolio/[slug]/(portfolio)/reports/Report.tsx b/src/app/portfolio/[slug]/(portfolio)/reports/Report.tsx new file mode 100644 index 0000000..b2726c1 --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/reports/Report.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useState } from "react"; +import { DealStageChart } from "./DealStageChart"; +import SurveyedPieChart from "./SurveyedResultsPieChart"; +import TableViewer from "./TableViewer"; + +interface ReportsProps { + deals: Record[]; +} + +export default function Reports({ deals }: ReportsProps) { + const [openTable, setOpenTable] = useState<{ + stage: string; + data: any[]; + } | null>(null); + + const handleOpenTable = (stage: string, filteredDeals: any[]) => { + setOpenTable({ stage, data: filteredDeals }); + }; + + if (!deals || deals.length === 0) { + return ( +
+ No deal data available. +
+ ); + } + + // Group deals by projectCode + const groupedDeals = deals.reduce((acc, deal) => { + const project = deal.projectCode || "Unknown Project"; + if (!acc[project]) acc[project] = []; + acc[project].push(deal); + return acc; + }, {} as Record); + + const projectCodes = Object.keys(groupedDeals); + const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); + const currentDeals = groupedDeals[currentProjectCode]; + + return ( +
+{/* πŸ”Ή Centered Dropdown Selector for Projects */} +
+

+ Select Project +

+ +
+ + + {/* Custom dropdown arrow */} +
+ β–Ό +
+
+
+ + {/* Charts */} +
+
+ +
+
+ +
+
+ +
+ Showing project {currentProjectCode} +
+ + {/* πŸ”Ή Modal Table */} + {openTable && ( +
+
+

+ {openTable.stage} β€” {openTable.data.length} Properties +

+ +
+ +
+ +
+ +
+
+
+ )} +
+ ); +} diff --git a/src/app/portfolio/[slug]/(portfolio)/reports/SurveyedResultsPieChart.tsx b/src/app/portfolio/[slug]/(portfolio)/reports/SurveyedResultsPieChart.tsx new file mode 100644 index 0000000..7cb0d3c --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/reports/SurveyedResultsPieChart.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { DonutChart, Card, Title } from "@tremor/react"; +import { useMemo, useState } from "react"; + +interface SurveyedPieChartProps { + deals: Record[]; + onOpenTable?: (outcome: string, filteredDeals: Record[]) => void; +} + +export default function SurveyedPieChart({ + deals, + onOpenTable, +}: SurveyedPieChartProps) { + const [selected, setSelected] = useState(null); + + const surveyorOutcomes = [ + "Surveyed", + "Surveyed - Pending Upload", + "Tenant Refusal", + "Other", + "Not Viable", + "Not Attempted", + "No Answer", + "Cancelled / No Show", + "Rescheduled", + ]; + + const data = useMemo(() => { + const outcomeCounts: Record = {}; + + deals.forEach((deal) => { + const outcome = deal.outcome; + if (outcome && surveyorOutcomes.includes(outcome)) { + outcomeCounts[outcome] = (outcomeCounts[outcome] || 0) + 1; + } + }); + + return Object.entries(outcomeCounts).map(([name, amount]) => ({ + name, + amount, + })); + }, [deals]); + + const handleClick = (value: { name: string; amount: number }) => { + if (!value) return; // guard clause + const filteredDeals = deals.filter((d) => d.outcome === value.name); + setSelected(null); // remove highlight after click + onOpenTable?.(value.name, filteredDeals); + }; + + return ( + +
+ + Surveyed Outcome + + + `${n.toLocaleString()}`} + colors={[ + "indigo", + "cyan", + "emerald", + "amber", + "rose", + "violet", + "gray", + ]} + className="w-64 h-64 cursor-pointer" + onValueChange={handleClick} + /> +
+
+ ); +} diff --git a/src/app/portfolio/[slug]/(portfolio)/reports/TableViewer.tsx b/src/app/portfolio/[slug]/(portfolio)/reports/TableViewer.tsx new file mode 100644 index 0000000..0488332 --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/reports/TableViewer.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { useState, useMemo } from "react"; + +interface TableViewerProps { + data: Record[]; + columns?: string[]; // optional: which columns to show + columnLabels?: Record; // πŸ‘ˆ map data keys to display names +} + +export default function TableViewer({ data, columns, columnLabels }: TableViewerProps) { + const [searchTerms, setSearchTerms] = useState>({}); + + const visibleColumns = columns?.length ? columns : Object.keys(data?.[0] || {}); + + const filteredData = useMemo(() => { + return data.filter((row) => + visibleColumns.every((col) => { + const term = searchTerms[col]?.toLowerCase() || ""; + if (!term) return true; + const value = String(row[col] ?? "").toLowerCase(); + return value.includes(term); + }) + ); + }, [data, searchTerms, visibleColumns]); + + return ( +
+ + + + {visibleColumns.map((col) => ( + + ))} + + + + {filteredData.length === 0 ? ( + + + + ) : ( + filteredData.map((row, i) => ( + + {visibleColumns.map((col) => ( + + ))} + + )) + )} + +
+
+ + {columnLabels?.[col] || col} + + + setSearchTerms((prev) => ({ + ...prev, + [col]: e.target.value, + })) + } + /> +
+
+ No results found +
+ {String(row[col] ?? "")} +
+
+ ); +} diff --git a/src/app/portfolio/[slug]/(portfolio)/reports/page.tsx b/src/app/portfolio/[slug]/(portfolio)/reports/page.tsx new file mode 100644 index 0000000..30dacdf --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/reports/page.tsx @@ -0,0 +1,34 @@ +import { getServerSession } from "next-auth"; +import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions"; +import { redirect } from "next/navigation"; +import { surveyDB } from "../../../../db/surveyDB/connection"; +import { hubspotDealData } from "../../../../db/schema/crm/hubspot_deal_table"; +import { eq } from "drizzle-orm"; +import Reports from "./Report"; + +const Demo = async () => { + const user = await getServerSession(AuthOptions); + + if (!user?.user) { + console.error("User not found"); + redirect("/"); + } + + // Abri company id + const companyId = "237615001799"; + + const deals = await surveyDB + .select() + .from(hubspotDealData) + .where(eq(hubspotDealData.companyId, companyId)); + console.log(deals); + + return ( + <> +

hello reports

+ {/* */} + + ); +}; + +export default Demo; \ No newline at end of file diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/layout.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/layout.tsx index 11358e3..2c47041 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/layout.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/layout.tsx @@ -2,6 +2,8 @@ import { Toolbar } from "@/app/components/building-passport/Toolbar"; import { getPropertyMeta, getDocument } from "./utils"; import BackToPortfolioButton from "@/app/components/building-passport/BackToPortfolioButton"; import { ExclamationCircleIcon } from "@heroicons/react/24/outline"; +// import "@tremor/react/dist/esm/tremor.css"; + function EstimatedDataNotification() { return ( diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6c7dcee..dc8e111 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,3 +4,14 @@ import { twMerge } from "tailwind-merge" export function cn (...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } + +export const focusRing = [ + // base + "outline outline-offset-2 outline-0 focus-visible:outline-2", + // outline color + "outline-blue-500 dark:outline-blue-500", +] + +export function cx(...args: ClassValue[]) { + return twMerge(clsx(...args)) +} \ No newline at end of file From 276b23a900b933a8671d1cdfbdd6b93cc82f3152 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Thu, 30 Oct 2025 17:22:46 +0000 Subject: [PATCH 2/8] renamed --- src/app/components/building-passport/Toolbar.tsx | 3 ++- src/app/components/portfolio/Toolbar.tsx | 12 +++++++++++- .../{reports => live-projects}/DealStageChart.tsx | 0 .../{reports => live-projects}/Report.tsx | 0 .../SurveyedResultsPieChart.tsx | 0 .../{reports => live-projects}/TableViewer.tsx | 0 .../(portfolio)/{reports => live-projects}/page.tsx | 3 +-- 7 files changed, 14 insertions(+), 4 deletions(-) rename src/app/portfolio/[slug]/(portfolio)/{reports => live-projects}/DealStageChart.tsx (100%) rename src/app/portfolio/[slug]/(portfolio)/{reports => live-projects}/Report.tsx (100%) rename src/app/portfolio/[slug]/(portfolio)/{reports => live-projects}/SurveyedResultsPieChart.tsx (100%) rename src/app/portfolio/[slug]/(portfolio)/{reports => live-projects}/TableViewer.tsx (100%) rename src/app/portfolio/[slug]/(portfolio)/{reports => live-projects}/page.tsx (91%) diff --git a/src/app/components/building-passport/Toolbar.tsx b/src/app/components/building-passport/Toolbar.tsx index 20aa844..966f283 100644 --- a/src/app/components/building-passport/Toolbar.tsx +++ b/src/app/components/building-passport/Toolbar.tsx @@ -124,6 +124,8 @@ export function Toolbar({ ); + + return ( <>
@@ -145,7 +147,6 @@ export function Toolbar({ {solarAnalysisButton} {recommendationsButton} {documentsButton} - Measures */} - + + + Live Projects + { return ( <> -

hello reports

- {/* */} + ); }; From 4d61224cf372e214481ccb1d0c14bbd0c7f7375b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 31 Oct 2025 12:52:37 +0000 Subject: [PATCH 3/8] save --- src/app/components/portfolio/Toolbar.tsx | 2 +- .../(portfolio)/live-projects/Report.tsx | 145 ++++++++++++++---- .../[slug]/(portfolio)/live-projects/page.tsx | 3 +- 3 files changed, 114 insertions(+), 36 deletions(-) diff --git a/src/app/components/portfolio/Toolbar.tsx b/src/app/components/portfolio/Toolbar.tsx index 0d6226a..7fcae07 100644 --- a/src/app/components/portfolio/Toolbar.tsx +++ b/src/app/components/portfolio/Toolbar.tsx @@ -50,7 +50,7 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) { } function handleClickProgressReport() { - router.push(`/portfolio/${portfolioId}/reports`); + router.push(`/portfolio/${portfolioId}/live-projects`); } const [modalIsOpen, setModalIsOpen] = useState(false); diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx index b2726c1..91bad8b 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx @@ -9,7 +9,10 @@ interface ReportsProps { deals: Record[]; } -export default function Reports({ deals }: ReportsProps) { +// 🟩 Stage mapping: β€œMajor Condition Issues” = dealstage 3061261536 +const MAJOR_CONDITION_STAGE_ID = "3061261536"; + +export default function LiveTracker({ deals }: ReportsProps) { const [openTable, setOpenTable] = useState<{ stage: string; data: any[]; @@ -39,46 +42,120 @@ export default function Reports({ deals }: ReportsProps) { const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); const currentDeals = groupedDeals[currentProjectCode]; + // πŸ”Ή Compute overall summary (across all projects) + const totalProperties = deals.length; + const majorConditionDeals = deals.filter( + (d) => d.dealstage === MAJOR_CONDITION_STAGE_ID + ); + const majorIssues = majorConditionDeals.length; + const majorPercent = ((majorIssues / totalProperties) * 100).toFixed(1); + + // πŸ”Ή Click handlers + const handleTotalClick = () => { + console.log("Opening all deals (global)"); + handleOpenTable("All Properties", deals); + }; + + const handleMajorClick = () => { + console.log("Opening all Major Condition Issues (global)"); + handleOpenTable("Major Condition Issues", majorConditionDeals); + }; + return ( -
-{/* πŸ”Ή Centered Dropdown Selector for Projects */} -
-

- Select Project -

+
+ {/* πŸ”Ή Global Overview Row */} +
+

+ 🌍 Global Portfolio Overview +

-
- +
+ {/* Total Properties */} + - {/* Custom dropdown arrow */} -
- β–Ό -
-
-
+ {/* Major Condition Issues */} + - {/* Charts */} -
-
- -
-
- + {/* Project Dropdown Selector */} +
+ +
+ + + {/* Custom dropdown arrow */} +
+ β–Ό +
+
+
-
- Showing project {currentProjectCode} + {/* πŸ”Ή Project-Level Section */} +
+

+ πŸ“Š Project-Level Insights +

+

+ Showing data for{" "} + + {currentProjectCode} + +

+ +
+
+ +
+
+ +
+
{/* πŸ”Ή Modal Table */} diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx index db8e31c..08a5a3b 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx @@ -5,6 +5,7 @@ import { surveyDB } from "../../../../db/surveyDB/connection"; import { hubspotDealData } from "../../../../db/schema/crm/hubspot_deal_table"; import { eq } from "drizzle-orm"; import Reports from "./Report"; +import LiveTracker from "./Report"; const Demo = async () => { const user = await getServerSession(AuthOptions); @@ -25,7 +26,7 @@ const Demo = async () => { return ( <> - + ); }; From 7a6b6c12bb685d62e8ad6b74b37f7af273428c17 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 3 Nov 2025 09:13:19 +0000 Subject: [PATCH 4/8] gets portfolio id from db --- .devcontainer/post-install.sh | 2 +- .../db/schema/crm/hubspot_company_table.ts | 1 + .../live-projects/SurveyedResultsPieChart.tsx | 1 - .../[slug]/(portfolio)/live-projects/page.tsx | 46 +++++++++++++------ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index 9f018f3..c847f6d 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -1 +1 @@ -npm install; \ No newline at end of file +npm install; diff --git a/src/app/db/schema/crm/hubspot_company_table.ts b/src/app/db/schema/crm/hubspot_company_table.ts index 71aa419..618da19 100644 --- a/src/app/db/schema/crm/hubspot_company_table.ts +++ b/src/app/db/schema/crm/hubspot_company_table.ts @@ -16,3 +16,4 @@ export const hubspotCompanyData = pgTable("hubspot_company_data", { .$onUpdate(() => new Date()) .notNull(), }); + diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx index 7cb0d3c..d5919ab 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx @@ -7,7 +7,6 @@ interface SurveyedPieChartProps { deals: Record[]; onOpenTable?: (outcome: string, filteredDeals: Record[]) => void; } - export default function SurveyedPieChart({ deals, onOpenTable, diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx index 08a5a3b..7eb6ded 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx @@ -3,11 +3,13 @@ import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions"; import { redirect } from "next/navigation"; import { surveyDB } from "../../../../db/surveyDB/connection"; import { hubspotDealData } from "../../../../db/schema/crm/hubspot_deal_table"; +import { hubspotCompanyData } from "@/app/db/schema/crm/hubspot_company_table"; import { eq } from "drizzle-orm"; -import Reports from "./Report"; import LiveTracker from "./Report"; -const Demo = async () => { +export default async function Demo(props: { + params: Promise<{ slug: string }>; +}) { const user = await getServerSession(AuthOptions); if (!user?.user) { @@ -15,20 +17,38 @@ const Demo = async () => { redirect("/"); } - // Abri company id - const companyId = "237615001799"; + const { slug: portfolioId } = await props.params; + // Fetch the single company + const [company] = await surveyDB + .select() + .from(hubspotCompanyData) + .where(eq(hubspotCompanyData.groupId, portfolioId)); + + if (!company) { + console.log("No company found for this portfolioId"); + return ( +
+ No information to show. +
+ ); + } + + // Fetch deals related to that company const deals = await surveyDB .select() .from(hubspotDealData) - .where(eq(hubspotDealData.companyId, companyId)); - console.log(deals); + .where(eq(hubspotDealData.companyId, company.companyId)); - return ( - <> - - - ); -}; + console.log("Deals:", deals); -export default Demo; \ No newline at end of file + if (!deals || deals.length === 0) { + return ( +
+ No information to show. +
+ ); + } + + return ; +} From 0003102ca152539933ca2291167436a0cd032fef Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 3 Nov 2025 09:21:01 +0000 Subject: [PATCH 5/8] save --- .../live-projects/SurveyedResultsPieChart.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx index d5919ab..95582ed 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/SurveyedResultsPieChart.tsx @@ -61,13 +61,15 @@ export default function SurveyedPieChart({ index="name" valueFormatter={(n) => `${n.toLocaleString()}`} colors={[ - "indigo", - "cyan", - "emerald", - "amber", - "rose", - "violet", - "gray", + "sky", // light airy blue + "cyan", // bright modern blue + "blue", // brand-level core blue + "indigo", // deeper professional tone + "violet", // slight bluish-purple contrast + "slate", // muted cool gray-blue + "lightBlue", // soft complementary blue + "navy", // deep accent + "azure", // fresh pop for clarity ]} className="w-64 h-64 cursor-pointer" onValueChange={handleClick} From b8caeca263e3da5b9e26e6e70646d332eaaf594c Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 3 Nov 2025 10:23:54 +0000 Subject: [PATCH 6/8] added the surveyor button changes --- .../components/building-passport/Toolbar.tsx | 4 +- .../live-projects/DealStageChart.tsx | 77 +++++------ .../(portfolio)/live-projects/Report.tsx | 126 ++++++------------ .../live-projects/SurveyedResultsPieChart.tsx | 41 +++--- .../(portfolio)/live-projects/TableViewer.tsx | 40 +++--- .../[slug]/(portfolio)/live-projects/page.tsx | 43 ++++-- .../[slug]/components/BookSurveyModal.tsx | 13 +- tailwind.config.js | 4 +- 8 files changed, 155 insertions(+), 193 deletions(-) diff --git a/src/app/components/building-passport/Toolbar.tsx b/src/app/components/building-passport/Toolbar.tsx index 966f283..48f7d2e 100644 --- a/src/app/components/building-passport/Toolbar.tsx +++ b/src/app/components/building-passport/Toolbar.tsx @@ -184,8 +184,8 @@ export function Toolbar({ setShowToast(false)} - message="Survey Booked Successfully!" - subtext="Your Survey Request is with Domna and we will be in contact. πŸŽ‰" + message="Survey Request Recieved!" + subtext="We'll be in contact soon. πŸŽ‰" /> ); diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx index 4a6bd73..b4dec63 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx @@ -1,17 +1,16 @@ "use client"; -import { useState, useMemo } from "react"; +import { useMemo } from "react"; import { BarList, Card, Title } from "@tremor/react"; -import TableViewer from "./TableViewer"; const STAGE_ORDER = [ - "Initial Planning", // 0 - "Booking Team to contact Tenant", // 1 - "Survey in Progress", // 2 - "Not viable", // 3 - "Needs HA Support", // 4 - "Coordination + Design", // 5 - "Ready to be installed" //7 + "Initial Planning", + "Booking Team to contact Tenant", + "Survey in Progress", + "Not viable", + "Needs HA Support", + "Coordination + Design", + "Ready to be installed", ]; const STAGE_LABELS: Record = { @@ -36,37 +35,18 @@ const STAGE_LABELS: Record = { "3061261536": STAGE_ORDER[4], "2571417798": "[Ops] Surveyed under 2019 - Needs Re-survey", "1617223914": STAGE_ORDER[5], - "1887736000": "[Deprecated, please don't use] Files Missing From Assessor", + "1887736000": "[Deprecated] Files Missing From Assessor", "1617223916": "[Ops] Properties to Review Manually", "2628341989": STAGE_ORDER[2], - "3441170637": STAGE_ORDER[2], // check if assessor or coordination + "3441170637": STAGE_ORDER[2], "2628233422": STAGE_ORDER[5], "1887735999": STAGE_ORDER[4], - "1960060104": "[Ops] HA Informed", - "1960060105": "[Ops] HA Works Scheduled", - "1960060106": "[Ops] HA Works Complete", - "1668803772": "[Ops] ERF Delivered to HA", - "1668803773": "[Ops] ERF Signed", - "2769407183": "[Ops] PV - Needs Heating Upgrade (Pre EPR D)", - "2769407184": "[Ops] Talk to client, Needs Heating Upgrade (Pre EPR C)", "2702650617": STAGE_ORDER[5], "2473886962": STAGE_ORDER[5], "3016601828": STAGE_ORDER[4], - "3389868276": "[Engagement Team] Blocked - Needs Completion of Pilot", - "3389880508": "[Engagement Team] Blocked - Installer Negotiation", - "3399016689": "[Engagement Team] Eligible but blocked - part of incomplete flat", - "1668803774": STAGE_ORDER[6], // Ready for Invoicing - "3440363736": STAGE_ORDER[6], // [Finance] Needs Invoicing - Files Sent - "1618526429": "[Ops] Invoiced - Send Files to Installer", - "3080225005": "[Ops] Files Sent to Installer", - "1961258215": "[Ops] Installer Cancelled - Finalized", - "1961258214": "[Ops] Installer Cancelled - In Progress", - "1961258213": "[Ops] Install Scheduled", - "1617223918": "[Ops] Install Complete", - "1961258216": "[Compliance] Lodgement Complete", - "1961258217": "[Compliance] Documentation Sent to HA", - "3027432668": "[Team ???] Submitted to " -} + "1668803774": STAGE_ORDER[6], + "3440363736": STAGE_ORDER[6], +}; interface DealStageChartProps { deals: any[]; @@ -97,26 +77,35 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { }); }, [deals]); - // handle click event const handleBarClick = (value: { name: string; value: number }) => { const filtered = deals.filter((d) => { const stageId = d.dealstage || "unknown"; const stageName = STAGE_LABELS[stageId] || "Unknown Stage"; return stageName === value.name; }); - onOpenTable?.(value.name, filtered); }; return ( - - Project Progress - + +
+ + Project Progress by Stage + +

+ Click a bar to view related properties +

+
+ +
+ +
); -} \ No newline at end of file +} diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx index 91bad8b..09c2999 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx @@ -9,32 +9,26 @@ interface ReportsProps { deals: Record[]; } -// 🟩 Stage mapping: β€œMajor Condition Issues” = dealstage 3061261536 const MAJOR_CONDITION_STAGE_ID = "3061261536"; export default function LiveTracker({ deals }: ReportsProps) { - const [openTable, setOpenTable] = useState<{ - stage: string; - data: any[]; - } | null>(null); + const [openTable, setOpenTable] = useState<{ stage: string; data: any[] } | null>(null); const handleOpenTable = (stage: string, filteredDeals: any[]) => { setOpenTable({ stage, data: filteredDeals }); }; - if (!deals || deals.length === 0) { + if (!deals?.length) { return ( -
+
No deal data available.
); } - // Group deals by projectCode const groupedDeals = deals.reduce((acc, deal) => { const project = deal.projectCode || "Unknown Project"; - if (!acc[project]) acc[project] = []; - acc[project].push(deal); + (acc[project] ||= []).push(deal); return acc; }, {} as Record); @@ -42,77 +36,59 @@ export default function LiveTracker({ deals }: ReportsProps) { const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); const currentDeals = groupedDeals[currentProjectCode]; - // πŸ”Ή Compute overall summary (across all projects) const totalProperties = deals.length; - const majorConditionDeals = deals.filter( - (d) => d.dealstage === MAJOR_CONDITION_STAGE_ID - ); + const majorConditionDeals = deals.filter(d => d.dealstage === MAJOR_CONDITION_STAGE_ID); const majorIssues = majorConditionDeals.length; const majorPercent = ((majorIssues / totalProperties) * 100).toFixed(1); - // πŸ”Ή Click handlers - const handleTotalClick = () => { - console.log("Opening all deals (global)"); - handleOpenTable("All Properties", deals); - }; - - const handleMajorClick = () => { - console.log("Opening all Major Condition Issues (global)"); - handleOpenTable("Major Condition Issues", majorConditionDeals); - }; - return ( -
- {/* πŸ”Ή Global Overview Row */} -
-

+
+ {/* 🌍 Global Portfolio Overview */} +
+

🌍 Global Portfolio Overview

-
- {/* Total Properties */} +
+ {/* Total */} - {/* Major Condition Issues */} + {/* Major Issues */} - {/* Project Dropdown Selector */} -
+ {/* Project Selector */} +
-
+
- - {/* Custom dropdown arrow */} -
- β–Ό -
+
β–Ό
- {/* πŸ”Ή Project-Level Section */} -
-

+ {/* πŸ“Š Project Insights */} +
+

πŸ“Š Project-Level Insights

-

- Showing data for{" "} - - {currentProjectCode} - +

+ Showing data for {currentProjectCode}

-
-
- +
+
+
-
- +
+
- {/* πŸ”Ή Modal Table */} + {/* πŸ”Ή Modal */} {openTable && ( -
-
-

+
+
+

{openTable.stage} β€” {openTable.data.length} Properties

[]; onOpenTable?: (outcome: string, filteredDeals: Record[]) => void; } + export default function SurveyedPieChart({ deals, onOpenTable, }: SurveyedPieChartProps) { - const [selected, setSelected] = useState(null); - const surveyorOutcomes = [ "Surveyed", "Surveyed - Pending Upload", @@ -27,14 +26,12 @@ export default function SurveyedPieChart({ const data = useMemo(() => { const outcomeCounts: Record = {}; - deals.forEach((deal) => { const outcome = deal.outcome; if (outcome && surveyorOutcomes.includes(outcome)) { outcomeCounts[outcome] = (outcomeCounts[outcome] || 0) + 1; } }); - return Object.entries(outcomeCounts).map(([name, amount]) => ({ name, amount, @@ -42,18 +39,20 @@ export default function SurveyedPieChart({ }, [deals]); const handleClick = (value: { name: string; amount: number }) => { - if (!value) return; // guard clause + if (!value) return; const filteredDeals = deals.filter((d) => d.outcome === value.name); - setSelected(null); // remove highlight after click onOpenTable?.(value.name, filteredDeals); }; return ( - -
- - Surveyed Outcome + <Card className="max-w-lg mx-auto bg-white rounded-2xl shadow-md hover:shadow-lg transition-all duration-200 p-6"> + <div className="flex flex-col items-center space-y-4"> + <Title className="text-gray-800 text-lg font-semibold tracking-tight text-center"> + Survey Outcomes +

+ Click a segment to view filtered properties +

`${n.toLocaleString()}`} colors={[ - "sky", // light airy blue - "cyan", // bright modern blue - "blue", // brand-level core blue - "indigo", // deeper professional tone - "violet", // slight bluish-purple contrast - "slate", // muted cool gray-blue - "lightBlue", // soft complementary blue - "navy", // deep accent - "azure", // fresh pop for clarity + "sky", + "cyan", + "blue", + "indigo", + "violet", + "slate", + "lightBlue", + "navy", + "azure", ]} - className="w-64 h-64 cursor-pointer" + className="w-64 h-64 cursor-pointer transition-transform hover:scale-[1.03]" onValueChange={handleClick} />
diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/TableViewer.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/TableViewer.tsx index 0488332..c35a586 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/TableViewer.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/TableViewer.tsx @@ -4,13 +4,12 @@ import { useState, useMemo } from "react"; interface TableViewerProps { data: Record[]; - columns?: string[]; // optional: which columns to show - columnLabels?: Record; // πŸ‘ˆ map data keys to display names + columns?: string[]; + columnLabels?: Record; } export default function TableViewer({ data, columns, columnLabels }: TableViewerProps) { const [searchTerms, setSearchTerms] = useState>({}); - const visibleColumns = columns?.length ? columns : Object.keys(data?.[0] || {}); const filteredData = useMemo(() => { @@ -25,25 +24,20 @@ export default function TableViewer({ data, columns, columnLabels }: TableViewer }, [data, searchTerms, visibleColumns]); return ( -
+
- - + + {visibleColumns.map((col) => ( - {filteredData.length === 0 ? ( - ) : ( filteredData.map((row, i) => ( - + {visibleColumns.map((col) => ( - ))} diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx index 7eb6ded..cc135b1 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx @@ -19,36 +19,55 @@ export default async function Demo(props: { const { slug: portfolioId } = await props.params; - // Fetch the single company + // 🏒 Fetch the company const [company] = await surveyDB .select() .from(hubspotCompanyData) .where(eq(hubspotCompanyData.groupId, portfolioId)); if (!company) { - console.log("No company found for this portfolioId"); return ( -
- No information to show. -
+
+
+ No information to show. +
+
); } - // Fetch deals related to that company + // πŸ’Ό Fetch deals for that company const deals = await surveyDB .select() .from(hubspotDealData) .where(eq(hubspotDealData.companyId, company.companyId)); - console.log("Deals:", deals); - if (!deals || deals.length === 0) { return ( -
- No information to show. -
+
+
+ No information to show. +
+
); } - return ; + return ( +
+ {/* 🌊 Domna-inspired layered background */} +
+ + {/* ✨ Subtle translucent grid texture */} +
+ + {/* πŸ’‘ Optional soft light glow at top */} +
+ + {/* Main content */} +
+
+ +
+
+
+ ); } diff --git a/src/app/portfolio/[slug]/components/BookSurveyModal.tsx b/src/app/portfolio/[slug]/components/BookSurveyModal.tsx index 39f6651..a3a7b52 100644 --- a/src/app/portfolio/[slug]/components/BookSurveyModal.tsx +++ b/src/app/portfolio/[slug]/components/BookSurveyModal.tsx @@ -39,7 +39,7 @@ export default function BookSurveyModal({ body: JSON.stringify({ dealName: address, pipelineId: "2400089278", - dealStageId: "3288115388", + dealStageId: "3660660975", propertyId: propertyId.toString(), portfolioId: portfolioId, }), @@ -68,8 +68,10 @@ export default function BookSurveyModal({ return ( - - Confirm Booking a Survey + + + Confirm and we’ll be in touch! +
@@ -79,14 +81,13 @@ export default function BookSurveyModal({ className="w-full" disabled={bookSurveyMutation.isPending} > - {bookSurveyMutation.isPending ? "Creating..." : "Submit"} + {bookSurveyMutation.isPending ? "Creating..." : "Confirm"}
- - ); + ); } diff --git a/tailwind.config.js b/tailwind.config.js index 30f1628..fdc4b23 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -26,7 +26,9 @@ module.exports = { "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", - }, + "domna-gradient": + "linear-gradient(135deg, #14163d 0%, #2d348f 45%, #3943b7 70%, #eff6fc 100%)", + }, colors: { tremor: { brand: { From 895b6e2e725cc82ed5e61426a0257f476ff167b2 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 3 Nov 2025 12:26:54 +0000 Subject: [PATCH 7/8] save without background color --- src/app/api/book-survey/route.ts | 1 + .../(portfolio)/live-projects/Report.tsx | 28 +++++++++---------- .../[slug]/(portfolio)/live-projects/page.tsx | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/app/api/book-survey/route.ts b/src/app/api/book-survey/route.ts index 3340ddf..7f02909 100644 --- a/src/app/api/book-survey/route.ts +++ b/src/app/api/book-survey/route.ts @@ -9,6 +9,7 @@ export async function POST(req: Request) { const { dealName, pipelineId, dealStageId, propertyId, portfolioId } = await req.json(); + // 1️⃣ Create HubSpot deal const hsRes = await fetch("https://api.hubapi.com/crm/v3/objects/deals", { method: "POST", diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx index 09c2999..4277eaa 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/Report.tsx @@ -12,7 +12,20 @@ interface ReportsProps { const MAJOR_CONDITION_STAGE_ID = "3061261536"; export default function LiveTracker({ deals }: ReportsProps) { + const groupedDeals = deals.reduce((acc, deal) => { + const project = deal.projectCode || "Unknown Project"; + (acc[project] ||= []).push(deal); + return acc; + }, {} as Record); + const [openTable, setOpenTable] = useState<{ stage: string; data: any[] } | null>(null); + const projectCodes = Object.keys(groupedDeals); + const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); + const currentDeals = groupedDeals[currentProjectCode]; + const totalProperties = deals.length; + const majorConditionDeals = deals.filter(d => d.dealstage === MAJOR_CONDITION_STAGE_ID); + const majorIssues = majorConditionDeals.length; + const majorPercent = ((majorIssues / totalProperties) * 100).toFixed(1); const handleOpenTable = (stage: string, filteredDeals: any[]) => { setOpenTable({ stage, data: filteredDeals }); @@ -26,21 +39,6 @@ export default function LiveTracker({ deals }: ReportsProps) { ); } - const groupedDeals = deals.reduce((acc, deal) => { - const project = deal.projectCode || "Unknown Project"; - (acc[project] ||= []).push(deal); - return acc; - }, {} as Record); - - const projectCodes = Object.keys(groupedDeals); - const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); - const currentDeals = groupedDeals[currentProjectCode]; - - const totalProperties = deals.length; - const majorConditionDeals = deals.filter(d => d.dealstage === MAJOR_CONDITION_STAGE_ID); - const majorIssues = majorConditionDeals.length; - const majorPercent = ((majorIssues / totalProperties) * 100).toFixed(1); - return (
{/* 🌍 Global Portfolio Overview */} diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx index cc135b1..d5e4e7c 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/page.tsx @@ -54,7 +54,7 @@ export default async function Demo(props: { return (
{/* 🌊 Domna-inspired layered background */} -
+ {/*
*/} {/* ✨ Subtle translucent grid texture */}
From 98d174d278e46af41f55c009c7c8233d2bedb576 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 3 Nov 2025 12:29:53 +0000 Subject: [PATCH 8/8] fix pipeline --- .../[slug]/(portfolio)/live-projects/DealStageChart.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx b/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx index b4dec63..4aec8d7 100644 --- a/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/live-projects/DealStageChart.tsx @@ -33,10 +33,10 @@ const STAGE_LABELS: Record = { "1617223917": STAGE_ORDER[2], "1887735998": STAGE_ORDER[3], "3061261536": STAGE_ORDER[4], - "2571417798": "[Ops] Surveyed under 2019 - Needs Re-survey", + "2571417798": STAGE_ORDER[2], "1617223914": STAGE_ORDER[5], - "1887736000": "[Deprecated] Files Missing From Assessor", - "1617223916": "[Ops] Properties to Review Manually", + "1887736000": STAGE_ORDER[2], + "1617223916": STAGE_ORDER[2], "2628341989": STAGE_ORDER[2], "3441170637": STAGE_ORDER[2], "2628233422": STAGE_ORDER[5],
-
- - {columnLabels?.[col] || col} - +
+
+ {columnLabels?.[col] || col} - setSearchTerms((prev) => ({ - ...prev, - [col]: e.target.value, - })) + setSearchTerms((prev) => ({ ...prev, [col]: e.target.value })) } />
@@ -54,18 +48,18 @@ export default function TableViewer({ data, columns, columnLabels }: TableViewer
+ No results found
+ {String(row[col] ?? "")}