widen drawer and add stage-ordered read-only sections

This commit is contained in:
Khalim Conn-Kowlessar 2026-05-05 12:09:57 +00:00
parent 998f36f1df
commit 75c0cde009

View file

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { X, CheckCircle2, Circle, AlertTriangle, ChevronRight, ChevronDown, Trash2, RotateCcw } from "lucide-react";
import {
@ -25,6 +25,17 @@ import {
} from "@/app/shadcn_components/ui/tooltip";
import { STAGE_COLORS } from "./types";
import type { ClassifiedDeal, PortfolioCapabilityType, RemovalRequest } from "./types";
import { parseMeasures } from "@/app/lib/parseMeasures";
// Sections that the drawer can scroll-focus on initial open. Keep the keys in
// stable, stage-ordered order so the layout remains predictable.
export type DrawerSection =
| "survey"
| "measures"
| "pibi"
| "domna"
| "halted"
| "technical";
// -----------------------------------------------------------------------
// Removal request section
@ -476,6 +487,34 @@ interface PropertyDetailDrawerProps {
userRole: string;
userCapability: PortfolioCapabilityType;
userEmail: string;
/**
* If provided, the drawer scrolls the named section into view on open. This
* powers entry-points like the Measures table row click that should land
* the user inside the Measures section instead of the top of the drawer.
*/
focusSection?: DrawerSection;
}
// Section metadata — central to keep the on-screen ordering aligned with the
// stage-ordered acceptance criteria of issue #251.
const SECTION_TITLES: Record<DrawerSection, string> = {
survey: "Survey",
measures: "Measures",
pibi: "PIBI",
domna: "Domna Survey",
halted: "Halted",
technical: "Technical Approved",
};
function SectionHeader({ id, label }: { id: DrawerSection; label: string }) {
return (
<h3
data-testid={`drawer-section-${id}`}
className="text-xs font-bold uppercase tracking-wider text-gray-400 mb-3"
>
{label}
</h3>
);
}
export default function PropertyDetailDrawer({
@ -484,13 +523,54 @@ export default function PropertyDetailDrawer({
onClose,
userRole,
userCapability,
focusSection,
}: PropertyDetailDrawerProps) {
const open = !!deal;
const [isLogOpen, setIsLogOpen] = useState(false);
// Refs for each scroll-targetable section.
const sectionRefs = useRef<Record<DrawerSection, HTMLDivElement | null>>({
survey: null,
measures: null,
pibi: null,
domna: null,
halted: null,
technical: null,
});
// Scroll the requested section into view once the drawer has rendered.
useEffect(() => {
if (!open || !focusSection) return;
// Defer to next tick so the drawer body has mounted.
const t = setTimeout(() => {
const el = sectionRefs.current[focusSection];
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}, 50);
return () => clearTimeout(t);
}, [open, focusSection, deal?.dealId]);
// Parsed measure lists used by the new sections.
const pibiMeasures = parseMeasures(deal?.measuresForPibiOrdered ?? null);
const technicalApprovedMeasures = parseMeasures(
deal?.technicalApprovedMeasuresForInstall ?? null,
);
// Domna section: prefer the new text column when present, otherwise fall
// back to the legacy boolean ("Required" / "Not required").
const domnaSurveyTypeDisplay: string | null = (() => {
if (!deal) return null;
if (deal.domnaSurveyType) return deal.domnaSurveyType;
if (deal.domnaSurveyRequired === true) return "Required";
if (deal.domnaSurveyRequired === false) return "Not required";
return null;
})();
return (
<Drawer open={open} onOpenChange={(v) => !v && onClose()} direction="right">
<DrawerContent className="fixed right-0 top-0 bottom-0 h-full w-[42vw] min-w-80 max-w-lg rounded-l-2xl rounded-r-none mt-0 flex flex-col border-l border-t-0 border-b-0 border-r-0 border-brandblue/10 bg-white shadow-2xl overflow-hidden">
<DrawerContent
data-testid="property-detail-drawer"
className="fixed right-0 top-0 bottom-0 h-full w-[60vw] max-w-5xl rounded-l-2xl rounded-r-none mt-0 flex flex-col border-l border-t-0 border-b-0 border-r-0 border-brandblue/10 bg-white shadow-2xl overflow-hidden"
>
<div className="hidden" />
{deal && (
@ -547,7 +627,7 @@ export default function PropertyDetailDrawer({
</div>
)}
{/* Key details */}
{/* Property details (general context — kept above stage sections) */}
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-gray-400 mb-3">Property Details</h3>
<div className="divide-y divide-gray-50">
@ -578,18 +658,95 @@ export default function PropertyDetailDrawer({
</div>
</div>
{/* Measures */}
{(deal.proposedMeasures || deal.approvedPackage || deal.actualMeasuresInstalled) && (
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-gray-400 mb-3">Measures</h3>
<div className="divide-y divide-gray-50">
<InfoRow label="Proposed" value={deal.proposedMeasures} />
<InfoRow label="Approved Package" value={deal.approvedPackage} />
<InfoRow label="Installed" value={deal.actualMeasuresInstalled} />
<InfoRow label="Lodgement Status" value={deal.lodgementStatus} />
</div>
{/* Survey section */}
<div ref={(el) => { sectionRefs.current.survey = el; }}>
<SectionHeader id="survey" label={SECTION_TITLES.survey} />
<div className="divide-y divide-gray-50">
<InfoRow label="Survey Type" value={deal.surveyType} />
<InfoRow label="Surveyed Date" value={formatDate(deal.surveyedDate)} />
<InfoRow label="Confirmed Survey Date" value={formatDate(deal.confirmedSurveyDate)} />
<InfoRow label="Confirmed Survey Time" value={deal.confirmedSurveyTime} />
</div>
)}
</div>
{/* Measures section — keeps existing approval table content */}
<div ref={(el) => { sectionRefs.current.measures = el; }}>
<SectionHeader id="measures" label={SECTION_TITLES.measures} />
<div className="divide-y divide-gray-50">
<InfoRow label="Proposed" value={deal.proposedMeasures} />
<InfoRow label="Approved Package" value={deal.approvedPackage} />
<InfoRow label="Installed" value={deal.actualMeasuresInstalled} />
<InfoRow label="Lodgement Status" value={deal.lodgementStatus} />
</div>
</div>
{/* PIBI section */}
<div ref={(el) => { sectionRefs.current.pibi = el; }}>
<SectionHeader id="pibi" label={SECTION_TITLES.pibi} />
<div className="divide-y divide-gray-50">
<InfoRow label="PIBI Order Date" value={formatDate(deal.pibiOrderDate)} />
<InfoRow label="PIBI Completed Date" value={formatDate(deal.pibiCompletedDate)} />
<InfoRow
label="Measures for PIBI"
value={
pibiMeasures.length > 0 ? (
<span className="flex flex-wrap gap-1.5">
{pibiMeasures.map((m) => (
<span
key={m}
className="px-2 py-0.5 rounded-full text-[11px] bg-gray-50 border border-gray-200 text-gray-600"
>
{m}
</span>
))}
</span>
) : null
}
/>
</div>
</div>
{/* Domna Survey section */}
<div ref={(el) => { sectionRefs.current.domna = el; }}>
<SectionHeader id="domna" label={SECTION_TITLES.domna} />
<div className="divide-y divide-gray-50">
<InfoRow label="Domna Survey Type" value={domnaSurveyTypeDisplay} />
<InfoRow label="Domna Survey Date" value={formatDate(deal.domnaSurveyDate)} />
</div>
</div>
{/* Halted section */}
<div ref={(el) => { sectionRefs.current.halted = el; }}>
<SectionHeader id="halted" label={SECTION_TITLES.halted} />
<div className="divide-y divide-gray-50">
<InfoRow label="Property Halted Date" value={formatDate(deal.propertyHaltedDate)} />
<InfoRow label="Property Halted Reason" value={deal.propertyHaltedReason} />
</div>
</div>
{/* Technical Approved section */}
<div ref={(el) => { sectionRefs.current.technical = el; }}>
<SectionHeader id="technical" label={SECTION_TITLES.technical} />
<div className="divide-y divide-gray-50">
<InfoRow
label="Technical Approved Measures"
value={
technicalApprovedMeasures.length > 0 ? (
<span className="flex flex-wrap gap-1.5">
{technicalApprovedMeasures.map((m) => (
<span
key={m}
className="px-2 py-0.5 rounded-full text-[11px] bg-emerald-50 border border-emerald-200 text-emerald-700"
>
{m}
</span>
))}
</span>
) : null
}
/>
</div>
</div>
{/* Timeline */}
<div>