mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
widen drawer and add stage-ordered read-only sections
This commit is contained in:
parent
998f36f1df
commit
75c0cde009
1 changed files with 171 additions and 14 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue