From 8ecc159bf3008ca96817c9aa3b3c062d91d58f3f Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 5 Nov 2025 20:09:20 +0000 Subject: [PATCH 1/3] save current progress --- src/app/db/schema/crm/hubspot_deal_table.ts | 3 + src/app/globals.css | 2 + src/app/page.tsx | 2 + .../your-projects/live/DealStageChart.tsx | 127 ++++++++++-------- .../(portfolio)/your-projects/live/Report.tsx | 73 +++++++--- 5 files changed, 130 insertions(+), 77 deletions(-) diff --git a/src/app/db/schema/crm/hubspot_deal_table.ts b/src/app/db/schema/crm/hubspot_deal_table.ts index 753a737..7b28343 100644 --- a/src/app/db/schema/crm/hubspot_deal_table.ts +++ b/src/app/db/schema/crm/hubspot_deal_table.ts @@ -15,6 +15,9 @@ export const hubspotDealData = pgTable("hubspot_deal_data", { outcome: text("outcome"), outcomeNotes: text("outcome_notes"), + majorConditionIssueDescription: text("major_condition_issue_description"), + majorConditionIssuePhotos: text("major_condition_issue_photos"), + createdAt: timestamp("created_at", { precision: 6, withTimezone: true }) .defaultNow() .notNull(), diff --git a/src/app/globals.css b/src/app/globals.css index 3b98d12..7c2a88d 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,6 +2,8 @@ @tailwind components; @tailwind utilities; + + /* 🌞 Light Theme (raw HSL values) */ :root { --background: 0 0% 100%; diff --git a/src/app/page.tsx b/src/app/page.tsx index fcabe67..5e7f0c4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,6 +5,8 @@ import MicrosoftSignInButton from "./components/signin/MicrosoftSignInButton"; import EmailSignInButton from "./components/signin/EmailSignInButton"; import { redirect } from "next/navigation"; import Image from "next/image"; +import "@tremor/react/dist/esm/tremor.css"; + export default async function Home(props: { searchParams: Promise<{ error?: string }>; diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx index dc999eb..0466604 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx @@ -7,11 +7,11 @@ const STAGE_ORDER = [ "Initial planning", "Booking team to contact tenant", "Survey in progress", - "Not viable", - "Needs Heating Upgrade installed", - "Needs support", "Coordination + design", - "Ready to be installed", + "Ready for install", + "Installed", + "Needs support from HA", + "Not viable for funding", ]; const stage = (label: string) => STAGE_ORDER.find((s) => s === label)!; @@ -26,8 +26,8 @@ const STAGE_LABELS: Record = { "1984184569": stage("Booking team to contact tenant"), "3569572028": stage("Booking team to contact tenant"), "3570936026": stage("Booking team to contact tenant"), - "2663668937": stage("Booking team to contact tenant"), - "1984401629": stage("Booking team to contact tenant"), + "2663668937": stage("Needs support from HA"), + "1984401629": stage("Survey in progress"), "2558220518": stage("Booking team to contact tenant"), "3474594026": stage("Booking team to contact tenant"), @@ -35,27 +35,25 @@ const STAGE_LABELS: Record = { "1617223913": stage("Survey in progress"), "3206388924": stage("Survey in progress"), "1617223915": stage("Survey in progress"), - "1617223917": stage("Survey in progress"), - "2571417798": stage("Survey in progress"), - "1887736000": stage("Survey in progress"), - "1617223916": stage("Survey in progress"), - "2628341989": stage("Survey in progress"), - "3441170637": stage("Survey in progress"), + "1617223917": stage("Not viable for funding"), + "2571417798": stage("Booking team to contact tenant"), - "1887735998": stage("Not viable"), + "1617223916": stage("Coordination + design"), + "2628341989": stage("Coordination + design"), + "3441170637": stage("Coordination + design"), - "3061261536": stage("Needs Heating Upgrade installed"), - "1887735999": stage("Needs Heating Upgrade installed"), - "3016601828": stage("Needs Heating Upgrade installed"), + "1887735998": stage("Not viable for funding"), + "3061261536": stage("Needs support from HA"), + "1887735999": stage("Needs support from HA"), + "3016601828": stage("Needs support from HA"), + "1617223914": stage("Coordination + design"), + "2628233422": stage("Coordination + design"), + "2702650617": stage("Coordination + design"), + "2473886962": stage("Coordination + design"), - "1617223914": stage("Needs support"), - "2628233422": stage("Needs support"), - "2702650617": stage("Needs support"), - "2473886962": stage("Needs support"), - - "1668803774": stage("Coordination + design"), - "3440363736": stage("Coordination + design"), - "2769407183": stage("Needs Heating Upgrade installed"), + "1668803774": stage("Ready for install"), + "3440363736": stage("Ready for install"), + "2769407183": stage("Needs support from HA"), }; interface DealStageChartProps { @@ -67,31 +65,18 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { const data = useMemo(() => { const counts: Record = {}; - // Count deals by stage deals.forEach((d) => { const stageId = d.dealstage || "unknown"; const stageName = STAGE_LABELS[stageId] || "Unknown Stage"; counts[stageName] = (counts[stageName] || 0) + 1; }); - // Ensure every stage in STAGE_ORDER has a default of 0 - const complete = STAGE_ORDER.map((name) => ({ + return STAGE_ORDER.map((name) => ({ name, value: counts[name] || 0, })); - - // Sort according to STAGE_ORDER (just in case) - return complete.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]); - // βœ… Calculate total deals const total = useMemo(() => deals.length, [deals]); const handleBarClick = (value: { name: string; value: number }) => { @@ -103,31 +88,61 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { onOpenTable?.(value.name, filtered); }; + // βœ… Split into normal and exception stages + const normalStages = data.filter( + (d) => + !["Needs support from HA", "Not viable for funding"].includes(d.name) && + d.name && + d.name !== "" + ); + const exceptionStages = data.filter((d) => + ["Needs support from HA", "Not viable for funding"].includes(d.name) + ); + return ( - -
- - Project Progress by Stage - -

- Click a bar to view related properties -

+
{/* Reduced gap */} + {/* Main Progress Chart */} + +
+ + Project Progress by Stage + +

+ Click a bar to view related properties +

+

+ Total: {total.toLocaleString()} properties +

+
- {/* βœ… Total count */} -

- Total: {total.toLocaleString()} properties -

-
- -
-
- + + + {/* Exception Chart */} + +
+ + Needs HA Support & Not Viable + +

+ Click to explore exceptions +

+
+ + +
+
); } diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx index 30b0fb1..0cdb911 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx @@ -10,13 +10,12 @@ import { CardTitle, CardContent, } from "@/app/shadcn_components/ui/card"; -import { Home, AlertTriangle, BarChart3 } from "lucide-react"; +import { Home, AlertTriangle } from "lucide-react"; import { motion } from "framer-motion"; interface ReportsProps { deals: Record[]; } - const MAJOR_CONDITION_STAGE_ID = "3061261536"; export default function LiveTracker({ deals }: ReportsProps) { @@ -32,7 +31,10 @@ export default function LiveTracker({ deals }: ReportsProps) { const [openTable, setOpenTable] = useState<{ stage: string; data: any[]; + columns: string[]; + columnLabels: Record; } | null>(null); + const projectCodes = Object.keys(groupedDeals); const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); const currentDeals = groupedDeals[currentProjectCode]; @@ -43,8 +45,23 @@ export default function LiveTracker({ deals }: ReportsProps) { const majorIssues = majorConditionDeals.length; const majorPercent = ((majorIssues / totalProperties) * 100).toFixed(1); - const handleOpenTable = (stage: string, filteredDeals: any[]) => { - setOpenTable({ stage, data: filteredDeals }); + const handleOpenTable = ( + stage: string, + filteredDeals: any[], + columns?: string[], + columnLabels?: Record + ) => { + setOpenTable({ + stage, + data: filteredDeals, + columns: + columns || ["dealname", "landlordPropertyId"], + columnLabels: + columnLabels || { + dealname: "Address Ref.", + landlordPropertyId: "Property Ref.", + }, + }); }; if (!deals?.length) { @@ -66,18 +83,42 @@ export default function LiveTracker({ deals }: ReportsProps) { icon={Home} title="Total Properties" value={totalProperties} - onClick={() => handleOpenTable("All Properties", deals)} + onClick={() => + handleOpenTable( + "All Properties", + deals, + ["dealname", "landlordPropertyId", "projectCode"], + { + dealname: "Address Ref.", + landlordPropertyId: "Property Ref.", + projectCode: "Project Code", + } + ) + } accent="brandblue" /> {/* Major Issues */} - handleOpenTable("Major Condition Issues", majorConditionDeals) + handleOpenTable( + "Awaab's Law Reporting", + majorConditionDeals, + [ + "dealname", + "landlordPropertyId", + "majorConditionIssueDescription", + ], + { + dealname: "Address Ref.", + landlordPropertyId: "Property Ref.", + majorConditionIssueDescription: "Surveyor's Notes" + } + ) } accent="red" /> @@ -155,18 +196,8 @@ export default function LiveTracker({ deals }: ReportsProps) {
@@ -185,7 +216,7 @@ export default function LiveTracker({ deals }: ReportsProps) { ); } -/** πŸ”ΈSmall stat card to match DashboardSummary visuals */ +/** πŸ”ΈSmall stat card component */ function StatCard({ icon: Icon, title, From ece75781b9e6d9f791ff7ae2578f4c75476b5d24 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 5 Nov 2025 21:22:35 +0000 Subject: [PATCH 2/3] everything looks nice! --- .../live/SurveyedResultsPieChart.tsx | 88 +++++++++++++------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx index d31bcd5..df1bda9 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx @@ -24,6 +24,17 @@ export default function SurveyedPieChart({ "Rescheduled", ]; + const colors = [ + "indigo-600", + "indigo-400", + "blue-300", + "amber-400", + "amber-200", + "slate-400", + "gray-300", + "gray-100", + ]; + const data = useMemo(() => { const outcomeCounts: Record = {}; deals.forEach((deal) => { @@ -32,9 +43,11 @@ export default function SurveyedPieChart({ outcomeCounts[outcome] = (outcomeCounts[outcome] || 0) + 1; } }); + const total = Object.values(outcomeCounts).reduce((a, b) => a + b, 0); return Object.entries(outcomeCounts).map(([name, amount]) => ({ name, amount, + percentage: total ? ((amount / total) * 100).toFixed(1) : "0.0", })); }, [deals]); @@ -45,35 +58,54 @@ export default function SurveyedPieChart({ }; return ( - -
- - Survey Outcomes - -

- Click a segment to view filtered properties -

+ + {/* Header */} +
+ + Survey Performance + +

+ Click a segment or label to view filtered properties +

+
- `${n.toLocaleString()}`} - colors={[ - "#2d348f", - "#14163d", - "#3943b7", - "#5d6be0", - "black", - "#eff6fc", - "lightBlue", - "navy", - "azure", - ]} - className="w-64 h-64 cursor-pointer transition-transform hover:scale-[1.03]" - onValueChange={handleClick} - /> + {/* Chart */} +
+ `${n.toLocaleString()}`} + colors={colors} + onValueChange={handleClick} + showLabel={false} + className="w-64 h-64 cursor-pointer" + customTooltip={({ payload }) => { + const item = payload?.[0]?.payload; + if (!item) return null; + const { name, amount } = item; + return ( +
+
+ {name} + {amount.toLocaleString()} +
+
+ ); + }} + /> + {data.length > 0 && ( +
+ + {data.reduce((a, b) => a + b.amount, 0)} +
- + )} +
+
+ ); } From b3253d7e84572c5fb0e0499e7e2f7d57cff1b701 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Thu, 6 Nov 2025 12:44:42 +0000 Subject: [PATCH 3/3] push to production --- src/app/page.tsx | 1 - .../your-projects/live/DealStageChart.tsx | 206 +++++++++++------- .../live/SurveyedResultsPieChart.tsx | 139 ++++++++---- 3 files changed, 224 insertions(+), 122 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 5e7f0c4..3de27a2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,6 @@ import MicrosoftSignInButton from "./components/signin/MicrosoftSignInButton"; import EmailSignInButton from "./components/signin/EmailSignInButton"; import { redirect } from "next/navigation"; import Image from "next/image"; -import "@tremor/react/dist/esm/tremor.css"; export default async function Home(props: { diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx index 0466604..b9d7dbe 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx @@ -9,56 +9,85 @@ const STAGE_ORDER = [ "Survey in progress", "Coordination + design", "Ready for install", - "Installed", + "Installed - Ready for Post work EPC", "Needs support from HA", "Not viable for funding", ]; const stage = (label: string) => STAGE_ORDER.find((s) => s === label)!; +// 🏷️ Deal stage β†’ display stage mapping const STAGE_LABELS: Record = { - "1617223910": stage("Initial planning"), - "3583836399": stage("Initial planning"), + "1617223910": stage("Initial planning"), // 0 - [Ops] Backlog + "3583836399": stage("Initial planning"), // 0 - [Ops] Route Planning - "3589581001": stage("Booking team to contact tenant"), - "3569878239": stage("Booking team to contact tenant"), - "1617223911": stage("Booking team to contact tenant"), - "1984184569": stage("Booking team to contact tenant"), - "3569572028": stage("Booking team to contact tenant"), - "3570936026": stage("Booking team to contact tenant"), - "2663668937": stage("Needs support from HA"), - "1984401629": stage("Survey in progress"), - "2558220518": stage("Booking team to contact tenant"), - "3474594026": stage("Booking team to contact tenant"), + "3589581001": stage("Booking team to contact tenant"), // 1 - [Bookings] Ready for Bookings Team + "3569878239": stage("Booking team to contact tenant"), // 1 - [Bookings] Send initial booking SMS + "1617223911": stage("Booking team to contact tenant"), // 1 - [Bookings] Send Email + "1984184569": stage("Booking team to contact tenant"), // 1 - [Bookings] Phone booking + "3569572028": stage("Booking team to contact tenant"), // 1 - [Bookings] Preferences received from Tenant + "3570936026": stage("Booking team to contact tenant"), // 1 - [Bookings] Send Confirmation Comms + "2663668937": stage("Needs support from HA"), // 4 - [Bookings/Sales] Booking issues - needs HA support (Check with Aidan) + "1984401629": stage("Survey in progress"), // 2 - [Bookings/Ops/Sales] No Contact Details - Ready for Route + "2558220518": stage("Booking team to contact tenant"), // 1 - [Ops] Not attempted - needs reallocation + "3474594026": stage("Booking team to contact tenant"), // 1 - [Ops/Bookings] Rebooked - Needs updating - "1617223912": stage("Survey in progress"), - "1617223913": stage("Survey in progress"), - "3206388924": stage("Survey in progress"), - "1617223915": stage("Survey in progress"), - "1617223917": stage("Not viable for funding"), - "2571417798": stage("Booking team to contact tenant"), + "1617223912": stage("Survey in progress"), // 2 - [Ops] Ready for Assignment to Route + "1617223913": stage("Survey in progress"), // 2 - [Ops] Survey in Progress + "3206388924": stage("Survey in progress"), // 2 - [Ops] Surveyed - Pending Upload from Surveyor + "1617223915": stage("Survey in progress"), // 2 - [Ops] No Access - Need Sign Off + "1617223917": stage("Not viable for funding"), // 3 - [Ops] No Access - No Revisit + "2571417798": stage("Booking team to contact tenant"), // 1 - [Ops] Surveyed under 2019 - Needs Re-survey - "1617223916": stage("Coordination + design"), - "2628341989": stage("Coordination + design"), - "3441170637": stage("Coordination + design"), + "1617223916": stage("Coordination + design"), // 5 - [Ops] Properties to Review Manually + "2628341989": stage("Coordination + design"), // 5 - [Ops] Assessment needs correction + "3441170637": stage("Coordination + design"), // 5 - [Ops] Awaiting PV Design - "1887735998": stage("Not viable for funding"), - "3061261536": stage("Needs support from HA"), - "1887735999": stage("Needs support from HA"), - "3016601828": stage("Needs support from HA"), - "1617223914": stage("Coordination + design"), - "2628233422": stage("Coordination + design"), - "2702650617": stage("Coordination + design"), - "2473886962": stage("Coordination + design"), + "1887735998": stage("Not viable for funding"), // 3 - [Ops] Not Viable + "3061261536": stage("Needs support from HA"), // 4 - [Sales/Tech] Major condition issue + "1887735999": stage("Needs support from HA"), // 4 - [Ops] Needs HA Works + "3016601828": stage("Needs support from HA"), // 4 - [Engagement Team] EPC C Before Works + "1617223914": stage("Coordination + design"), // 5 - [Ops] Surveyed in Pashub, Transit Job to Co-ordination + "2628233422": stage("Coordination + design"), // 5 - [Coordination] Ready for coordination + "2702650617": stage("Coordination + design"), // 5 - [Design] Ready for Design + "2473886962": stage("Coordination + design"), // 5 - [Design] Design in progress - "1668803774": stage("Ready for install"), - "3440363736": stage("Ready for install"), - "2769407183": stage("Needs support from HA"), + "1668803774": stage("Ready for install"), // 6 - [Finance] Ready for Invoicing + "3440363736": stage("Ready for install"), // 6 - [Finance] Needs Invoicing - Files Sent + "2769407183": stage("Needs support from HA"), // 4 - [Ops] PV - Needs Heating Upgrade (Pre EPR D) }; +// 🧩 Reasons for exception stages (HA support / Not viable) +const STAGE_REASONS: Record = { + // ---- Needs support from HA ---- + "2663668937": "Booking issues due to tenant difficulties.", + "3061261536": "Awaab's Law", + "1887735999": "", + "3016601828": "RA is currently EPR C. Convert to EPC?", + "2769407183": "Needs HA heating upgrade. Domna/HA discussion required.", + + // ---- Not viable for funding ---- + "1617223917": "", + "1887735998": "", +}; + +// βœ… Define an explicit Deal type for clarity +interface Deal { + dealname: string; + landlordPropertyId: string; + dealstage: string; + reason?: string; + [key: string]: any; +} + interface DealStageChartProps { - deals: any[]; - onOpenTable?: (stageName: string, filteredDeals: any[]) => void; + deals: Deal[]; + onOpenTable?: ( + stageName: string, + filteredDeals: Deal[], + columns?: string[], + columnLabels?: { [key: string]: string } + ) => void; } export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { @@ -77,44 +106,72 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { })); }, [deals]); - const total = useMemo(() => deals.length, [deals]); + const total = deals.length; 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); + const filteredDeals: Deal[] = deals + .filter((d) => { + const stageName = STAGE_LABELS[d.dealstage] || "Unknown Stage"; + return stageName === value.name; + }) + .map((d) => ({ + ...d, + // βœ… Always provide a string to avoid undefined issues + reason: STAGE_REASONS[d.dealstage] ?? "", + })); + + const isException = + value.name === "Needs support from HA" || + value.name === "Not viable for funding"; + + // Add "Reason" column if it's an exception stage + const columns = isException + ? ["dealname", "landlordPropertyId", "reason"] + : ["dealname", "landlordPropertyId"]; + + const columnLabels = isException + ? { + dealname: "Address Ref.", + landlordPropertyId: "Property Ref.", + reason: "Reason", + } + : { + dealname: "Address Ref.", + landlordPropertyId: "Property Ref.", + }; + + // βœ… Explicit cast ensures no type mismatch + onOpenTable?.(value.name, filteredDeals, columns, columnLabels as Record); }; - // βœ… Split into normal and exception stages + // Split into normal + exception stages const normalStages = data.filter( (d) => !["Needs support from HA", "Not viable for funding"].includes(d.name) && - d.name && d.name !== "" ); + const exceptionStages = data.filter((d) => ["Needs support from HA", "Not viable for funding"].includes(d.name) ); - return ( -
{/* Reduced gap */} - {/* Main Progress Chart */} - -
- - Project Progress by Stage - -

- Click a bar to view related properties -

-

- Total: {total.toLocaleString()} properties -

-
+return ( +
+ {/* βœ… Main Progress Chart */} + +
+ + Project Progress by Stage + +

+ Click a bar to view related properties +

+

+ Total: {total.toLocaleString()} properties +

+
+
- +
+
- {/* Exception Chart */} - -
- - Needs HA Support & Not Viable - -

- Click to explore exceptions -

-
+ {/* 🚨 Exception Chart */} + +
+ + Needs HA Support & Not Viable + +

+ Click to explore exception properties (reasons appear in table) +

+
+
- -
- ); +
+
+
+); } diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx index df1bda9..b126c43 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx @@ -1,7 +1,7 @@ "use client"; import { DonutChart, Card, Title } from "@tremor/react"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; interface SurveyedPieChartProps { deals: Record[]; @@ -33,6 +33,7 @@ export default function SurveyedPieChart({ "slate-400", "gray-300", "gray-100", + "gray-200", ]; const data = useMemo(() => { @@ -57,55 +58,97 @@ export default function SurveyedPieChart({ onOpenTable?.(value.name, filteredDeals); }; + const [hovered, setHovered] = useState(null); + return ( - - {/* Header */} -
- - Survey Performance - -

- Click a segment or label to view filtered properties -

-
- - {/* Chart */} -
- `${n.toLocaleString()}`} - colors={colors} - onValueChange={handleClick} - showLabel={false} - className="w-64 h-64 cursor-pointer" - customTooltip={({ payload }) => { - const item = payload?.[0]?.payload; - if (!item) return null; - const { name, amount } = item; - return ( -
-
- {name} - {amount.toLocaleString()} -
-
- ); - }} - /> - {data.length > 0 && ( -
- - {data.reduce((a, b) => a + b.amount, 0)} - + + {/* Header */} +
+ + Survey Performance + +

+ Click a segment or label to view filtered properties +

- )} -
- + {/* Donut Chart (Centered) */} +
+ `${n.toLocaleString()}`} + colors={colors} + onValueChange={handleClick} + showLabel={false} + className="w-64 h-64 cursor-pointer" + customTooltip={({ payload }) => { + const item = payload?.[0]?.payload; + if (!item) return null; + const { name, amount } = item; + return ( +
+
+ + {name} + + {amount.toLocaleString()} +
+
+ ); + }} + /> + {data.length > 0 && ( +
+ + {data.reduce((a, b) => a + b.amount, 0)} + +
+ )} +
+ + {/* Legend (Clean Grid Layout) */} +
+ {data.map((item, idx) => ( +
handleClick(item)} + onMouseEnter={() => setHovered(item.name)} + onMouseLeave={() => setHovered(null)} + className="relative flex items-center space-x-2 text-sm text-gray-700 hover:text-gray-900 cursor-pointer transition-colors" + > + + + {item.name} + + + {item.percentage}% + + + {/* Tooltip on hover */} + {hovered === item.name && ( +
+
+ + {item.name} + + {item.amount.toLocaleString()} +
+
+ )} +
+ ))} +
+ ); }