mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Merge pull request #274 from Hestia-Homes/feature/rc-rd-files-ui
Some checks failed
Test Suite / unit-tests (push) Has been cancelled
Some checks failed
Test Suite / unit-tests (push) Has been cancelled
Coordination and Design files UI
This commit is contained in:
commit
8b79f9a23e
5 changed files with 91 additions and 32 deletions
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import type { PropertyDocument, DocStatus } from "./types";
|
||||
import { EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES } from "./types";
|
||||
import { splitDocumentsByType, getMissingRetrofitTypes, getUnassignedInstallDocs } from "./propertyDocuments";
|
||||
import { splitDocumentsByType, getMissingSurveyDocTypes, getUnassignedInstallDocs } from "./propertyDocuments";
|
||||
import ContractorUploadModal from "./ContractorUploadModal";
|
||||
import type { ClassifiedDeal, PortfolioCapabilityType } from "./types";
|
||||
|
||||
|
|
@ -59,6 +59,9 @@ export const DOC_TYPE_LABELS: Record<string, string> = {
|
|||
installer_qualifications: "Installer Qualifications",
|
||||
installer_feedback: "Installer Feedback",
|
||||
contractor_other: "Other",
|
||||
improvement_option_evaluation: "Improvement Option Evaluation",
|
||||
medium_term_improvement_plan: "Medium Term Improvement Plan",
|
||||
retrofit_design_doc: "Retrofit Design Document"
|
||||
};
|
||||
|
||||
function formatDocDate(iso: string): string {
|
||||
|
|
@ -172,8 +175,8 @@ export default function PropertyDocumentsContent({
|
|||
return next;
|
||||
});
|
||||
|
||||
const { retrofitDocs, installDocs } = splitDocumentsByType(documents);
|
||||
const missingRetrofitTypes = getMissingRetrofitTypes(retrofitDocs);
|
||||
const { docs: surveyDocs, coordinationDocs, designDocs, installDocs } = splitDocumentsByType(documents);
|
||||
const missingSurveyDocTypes = getMissingSurveyDocTypes(surveyDocs);
|
||||
const hasDocuments = documents.length > 0;
|
||||
|
||||
const isContractor = userCapability?.includes("contractor") ?? false;
|
||||
|
|
@ -232,9 +235,9 @@ export default function PropertyDocumentsContent({
|
|||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-amber-500 px-0.5">
|
||||
Missing Documents ({missingRetrofitTypes.length})
|
||||
Missing Documents ({missingSurveyDocTypes.length})
|
||||
</h3>
|
||||
{missingRetrofitTypes.map((t) => (
|
||||
{missingSurveyDocTypes.map((t) => (
|
||||
<div
|
||||
key={t}
|
||||
className="flex items-center gap-2.5 p-3 rounded-lg border border-dashed border-amber-200 bg-amber-50/40"
|
||||
|
|
@ -254,25 +257,25 @@ export default function PropertyDocumentsContent({
|
|||
{!isFetching && !isError && hasDocuments && (
|
||||
<>
|
||||
{/* Retrofit Assessment */}
|
||||
<motion.div key="retrofit" initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-2">
|
||||
<motion.div key="survey" initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-2">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-gray-400 px-0.5">
|
||||
Retrofit Assessment Documents
|
||||
</h3>
|
||||
{retrofitDocs.length > 0 ? (
|
||||
{surveyDocs.length > 0 ? (
|
||||
<div className="space-y-1.5">
|
||||
{retrofitDocs.map((doc) => (
|
||||
{surveyDocs.map((doc) => (
|
||||
<DocumentRow key={doc.id} doc={doc} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-gray-400 px-0.5">None uploaded yet.</p>
|
||||
)}
|
||||
{missingRetrofitTypes.length > 0 && (
|
||||
{missingSurveyDocTypes.length > 0 && (
|
||||
<div className="space-y-1.5 pt-1">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-amber-500 px-0.5">
|
||||
Missing ({missingRetrofitTypes.length})
|
||||
Missing ({missingSurveyDocTypes.length})
|
||||
</h4>
|
||||
{missingRetrofitTypes.map((t) => (
|
||||
{missingSurveyDocTypes.map((t) => (
|
||||
<div
|
||||
key={t}
|
||||
className="flex items-center gap-2.5 p-3 rounded-lg border border-dashed border-amber-200 bg-amber-50/40"
|
||||
|
|
@ -287,6 +290,40 @@ export default function PropertyDocumentsContent({
|
|||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Coordination Documents */}
|
||||
<motion.div key="coordination" initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-3">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-gray-400 px-0.5 flex items-center gap-1.5">
|
||||
Coordination Documents
|
||||
</h3>
|
||||
{coordinationDocs.length > 0 ? (
|
||||
<div className="space-y-1.5">
|
||||
{coordinationDocs.map((doc) => (
|
||||
<DocumentRow key={doc.id} doc={doc} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-gray-400 px-0.5">None uploaded yet.</p>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Design Documents */}
|
||||
<motion.div key="design" initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-3">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-gray-400 px-0.5 flex items-center gap-1.5">
|
||||
Design Documents
|
||||
</h3>
|
||||
{designDocs.length > 0 ? (
|
||||
<div className="space-y-1.5">
|
||||
{designDocs.map((doc) => (
|
||||
<DocumentRow key={doc.id} doc={doc} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-gray-400 px-0.5">None uploaded yet.</p>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
|
||||
|
||||
{/* Install Documents */}
|
||||
<motion.div key="install" initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-3">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-gray-400 px-0.5 flex items-center gap-1.5">
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export function computeDocStatusMap(
|
|||
approved.length > 0
|
||||
? approved
|
||||
: (deal.proposedMeasures ?? "")
|
||||
.split(",")
|
||||
.split(/[,;]/)
|
||||
.map((m) => m.trim())
|
||||
.filter(Boolean);
|
||||
measuresByDealId.set(deal.dealId, measures);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
splitDocumentsByType,
|
||||
getMissingRetrofitTypes,
|
||||
getMissingSurveyDocTypes,
|
||||
getUnassignedInstallDocs,
|
||||
} from "./propertyDocuments";
|
||||
import type { PropertyDocument, MeasureDocProgress } from "./types";
|
||||
|
|
@ -35,21 +35,21 @@ function makeMeasureProgress(overrides: Partial<MeasureDocProgress> = {}): Measu
|
|||
describe("splitDocumentsByType", () => {
|
||||
it("puts survey doc types in retrofitDocs", () => {
|
||||
const doc = makeDoc({ docType: "photo_pack" });
|
||||
const { retrofitDocs, installDocs } = splitDocumentsByType([doc]);
|
||||
const { docs: retrofitDocs, installDocs } = splitDocumentsByType([doc]);
|
||||
expect(retrofitDocs).toHaveLength(1);
|
||||
expect(installDocs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("puts install doc types in installDocs", () => {
|
||||
const doc = makeDoc({ docType: "pre_photo" });
|
||||
const { retrofitDocs, installDocs } = splitDocumentsByType([doc]);
|
||||
const { docs: retrofitDocs, installDocs } = splitDocumentsByType([doc]);
|
||||
expect(retrofitDocs).toHaveLength(0);
|
||||
expect(installDocs).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("includes optional ecmk types in retrofitDocs", () => {
|
||||
const doc = makeDoc({ docType: "ecmk_site_note" });
|
||||
const { retrofitDocs } = splitDocumentsByType([doc]);
|
||||
const { docs: retrofitDocs } = splitDocumentsByType([doc]);
|
||||
expect(retrofitDocs).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
|
@ -60,13 +60,13 @@ describe("splitDocumentsByType", () => {
|
|||
makeDoc({ id: "3", docType: "site_note" }),
|
||||
makeDoc({ id: "4", docType: "post_photo" }),
|
||||
];
|
||||
const { retrofitDocs, installDocs } = splitDocumentsByType(docs);
|
||||
const { docs: retrofitDocs, installDocs } = splitDocumentsByType(docs);
|
||||
expect(retrofitDocs).toHaveLength(2);
|
||||
expect(installDocs).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("returns empty arrays for empty input", () => {
|
||||
const { retrofitDocs, installDocs } = splitDocumentsByType([]);
|
||||
const { docs: retrofitDocs, installDocs } = splitDocumentsByType([]);
|
||||
expect(retrofitDocs).toHaveLength(0);
|
||||
expect(installDocs).toHaveLength(0);
|
||||
});
|
||||
|
|
@ -74,15 +74,15 @@ describe("splitDocumentsByType", () => {
|
|||
|
||||
describe("getMissingRetrofitTypes", () => {
|
||||
it("returns all mandatory types when no docs uploaded", () => {
|
||||
const missing = getMissingRetrofitTypes([]);
|
||||
expect(missing).toHaveLength(9);
|
||||
const missing = getMissingSurveyDocTypes([]);
|
||||
expect(missing).toHaveLength(8);
|
||||
});
|
||||
|
||||
it("excludes types that have been uploaded", () => {
|
||||
const uploaded = [makeDoc({ docType: "photo_pack" })];
|
||||
const missing = getMissingRetrofitTypes(uploaded);
|
||||
const missing = getMissingSurveyDocTypes(uploaded);
|
||||
expect(missing).not.toContain("photo_pack");
|
||||
expect(missing).toHaveLength(8);
|
||||
expect(missing).toHaveLength(7);
|
||||
});
|
||||
|
||||
it("returns empty array when all mandatory types uploaded", () => {
|
||||
|
|
@ -91,14 +91,14 @@ describe("getMissingRetrofitTypes", () => {
|
|||
"pas_2023_condition", "pas_significance", "par_photo_pack",
|
||||
"pas_2023_property", "pas_2023_occupancy",
|
||||
].map((docType, i) => makeDoc({ id: String(i), docType }));
|
||||
expect(getMissingRetrofitTypes(uploaded)).toHaveLength(0);
|
||||
expect(getMissingSurveyDocTypes(uploaded)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not count ecmk types as mandatory", () => {
|
||||
const uploaded = [makeDoc({ docType: "ecmk_site_note" })];
|
||||
const missing = getMissingRetrofitTypes(uploaded);
|
||||
const missing = getMissingSurveyDocTypes(uploaded);
|
||||
expect(missing).not.toContain("ecmk_site_note");
|
||||
expect(missing).toHaveLength(9);
|
||||
expect(missing).toHaveLength(8);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,32 @@
|
|||
import {
|
||||
SURVEY_ALL_DOC_TYPES,
|
||||
EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES,
|
||||
COORDINATION_DOC_TYPES,
|
||||
DESIGN_DOC_TYPES,
|
||||
} from "./types";
|
||||
import type { PropertyDocument, MeasureDocProgress } from "./types";
|
||||
|
||||
export function splitDocumentsByType(docs: PropertyDocument[]): {
|
||||
retrofitDocs: PropertyDocument[];
|
||||
docs: PropertyDocument[];
|
||||
coordinationDocs: PropertyDocument[];
|
||||
designDocs: PropertyDocument[];
|
||||
installDocs: PropertyDocument[];
|
||||
} {
|
||||
return {
|
||||
retrofitDocs: docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.docType)),
|
||||
installDocs: docs.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.docType)),
|
||||
docs: docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.docType)),
|
||||
coordinationDocs: docs.filter((d) => COORDINATION_DOC_TYPES.has(d.docType)),
|
||||
designDocs: docs.filter((d) => DESIGN_DOC_TYPES.has(d.docType)),
|
||||
installDocs: docs.filter(
|
||||
(d) =>
|
||||
!SURVEY_ALL_DOC_TYPES.has(d.docType) &&
|
||||
!COORDINATION_DOC_TYPES.has(d.docType) &&
|
||||
!DESIGN_DOC_TYPES.has(d.docType),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function getMissingRetrofitTypes(retrofitDocs: PropertyDocument[]): string[] {
|
||||
const present = new Set(retrofitDocs.map((d) => d.docType));
|
||||
export function getMissingSurveyDocTypes(surveyDocs: PropertyDocument[]): string[] {
|
||||
const present = new Set(surveyDocs.map((d) => d.docType));
|
||||
return EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.filter((t) => !present.has(t));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -239,7 +239,6 @@ export const EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES = [
|
|||
"rd_sap_site_note",
|
||||
"pas_2023_ventilation",
|
||||
"pas_2023_condition",
|
||||
"pas_significance",
|
||||
"par_photo_pack",
|
||||
"pas_2023_property",
|
||||
"pas_2023_occupancy",
|
||||
|
|
@ -251,6 +250,18 @@ export const SURVEY_ALL_DOC_TYPES = new Set<string>([
|
|||
"ecmk_site_note",
|
||||
"ecmk_rd_sap_site_note",
|
||||
"ecmk_survey_xml",
|
||||
"pas_significance",
|
||||
]);
|
||||
|
||||
// Coordination doc types
|
||||
export const COORDINATION_DOC_TYPES = new Set<string>([
|
||||
"improvement_option_evaluation",
|
||||
"medium_term_improvement_plan"
|
||||
]);
|
||||
|
||||
// Design doc types
|
||||
export const DESIGN_DOC_TYPES = new Set<string>([
|
||||
"retrofit_design_doc"
|
||||
]);
|
||||
|
||||
// Per-measure document upload progress
|
||||
|
|
@ -267,7 +278,7 @@ export type DocStatus = {
|
|||
// Retrofit assessment docs
|
||||
presentSurveyTypes: string[];
|
||||
hasSurveyDocs: boolean;
|
||||
isSurveyComplete: boolean; // all 9 EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES present (ecmk not counted)
|
||||
isSurveyComplete: boolean; // all 8 EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES present (ecmk not counted)
|
||||
// Install docs
|
||||
hasInstallDocs: boolean;
|
||||
installStatus: "none" | "partial" | "hasDocs" | "all";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue