mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Merge pull request #78 from Hestia-Homes/feature/condition
Feature/condition - added fix for showing funding ui when funding not produced
This commit is contained in:
commit
5609e513c1
14 changed files with 811 additions and 239 deletions
98
package-lock.json
generated
98
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@headlessui/react": "^2.2.7",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
|
|
@ -2734,6 +2735,43 @@
|
|||
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
|
||||
"integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collapsible": "1.1.12",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
|
||||
|
|
@ -2787,6 +2825,66 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
|
||||
"integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
|
||||
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"@headlessui/react": "^2.2.7",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ export default function RecommendationContainer({
|
|||
console.warn("Multiple funding packages found, using the first one.");
|
||||
}
|
||||
|
||||
const [totalFunding, setTotalFunding] = useState(funding[0].projectFunding)
|
||||
const [totalFunding, setTotalFunding] = useState(funding[0]?.projectFunding || 0)
|
||||
|
||||
const currentEpcRating = propertyMeta.currentEpcRating;
|
||||
const currentSapPoints = propertyMeta.currentSapPoints;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ import {
|
|||
NavigationMenuLink,
|
||||
} from "@/app/shadcn_components/ui/navigation-menu";
|
||||
import { cva } from "class-variance-authority";
|
||||
import { getUploadedFile } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
|
||||
interface ToolbarProps {
|
||||
propertyId: string;
|
||||
portfolioId: string;
|
||||
conditionReport: getUploadedFile;
|
||||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
|
|
@ -53,7 +55,7 @@ const navigationMenuTriggerStyle = cva(
|
|||
].join(" ")
|
||||
);
|
||||
|
||||
export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
||||
export function Toolbar({ propertyId, portfolioId, conditionReport }: ToolbarProps) {
|
||||
function handleClickSettings() {
|
||||
console.log("Settings were clicked, implement me");
|
||||
}
|
||||
|
|
@ -61,7 +63,7 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
const preAssessmentReportButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/pre-assessment-report`}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/assessment`}
|
||||
>
|
||||
<NewspaperIcon className="h-4 w-4 mr-2" />
|
||||
Data
|
||||
|
|
@ -123,7 +125,7 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
{solarAnalysisButton}
|
||||
{recommendationsButton}
|
||||
{documentsButton}
|
||||
{energyAssessmentsReportButton}
|
||||
{/* {energyAssessmentsReportButton} */}
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickSettings}
|
||||
|
|
|
|||
|
|
@ -105,14 +105,14 @@ export default function ValuationImpactComponent({
|
|||
</div>
|
||||
|
||||
<FundingSummary
|
||||
scheme={funding.scheme}
|
||||
scheme={funding?.scheme}
|
||||
onSeeMore={openFundingModal}
|
||||
/>
|
||||
<FundingSummaryModal
|
||||
isOpen={fundingModalIsOpen}
|
||||
closeModal={() => setFundingModalIsOpen(false)}
|
||||
scheme={funding.scheme}
|
||||
fundingPackageMeasures={funding.fundingPackageMeasures}
|
||||
scheme={funding?.scheme}
|
||||
fundingPackageMeasures={funding?.fundingPackageMeasures || []}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const DB_REPORT_TYPES = [
|
|||
|
||||
export const docTypeEnum = pgEnum("reporttype", DB_REPORT_TYPES);
|
||||
|
||||
export const uploaded_files = pgTable("uploaded_files", {
|
||||
export const uploadedFiles = pgTable("uploaded_files", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
|
||||
s3JsonUri: text("s3_json_uri"),
|
||||
|
|
@ -26,6 +26,6 @@ export const uploaded_files = pgTable("uploaded_files", {
|
|||
uprn: text("uprn").notNull(),
|
||||
});
|
||||
|
||||
export type getUploadedFile = typeof uploaded_files.$inferSelect
|
||||
export type getUploadedFile = typeof uploadedFiles.$inferSelect
|
||||
|
||||
export type getUploadedFiles = getUploadedFile[];
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ export default async function PortfolioSummary(
|
|||
// Get user id from the session
|
||||
const scenarios = await getNonDefaultPortfolioScenarios(portfolioId);
|
||||
|
||||
console.log(data)
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-3xl text-gray-700 font-bold my-4">Summary</h1>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,257 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
} from "@/app/shadcn_components/ui/card";
|
||||
import {
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
HelpCircle,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/app/shadcn_components/ui/accordion";
|
||||
import clsx from "clsx";
|
||||
|
||||
function ChecklistItem({
|
||||
label,
|
||||
passed,
|
||||
note,
|
||||
alert = false,
|
||||
roomsWithIssues = [],
|
||||
}: {
|
||||
label: string;
|
||||
passed?: boolean;
|
||||
note?: string;
|
||||
alert?: boolean;
|
||||
roomsWithIssues?: [string, any][];
|
||||
}) {
|
||||
const icon = passed === true ? (
|
||||
<CheckCircle className="text-brandbrown w-5 h-5 shrink-0" />
|
||||
) : passed === false ? (
|
||||
<XCircle
|
||||
className={clsx(
|
||||
"w-5 h-5 shrink-0",
|
||||
alert ? "text-red-600" : "text-brandblue"
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<HelpCircle className="text-brandblue w-5 h-5 shrink-0" />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-muted/50 px-3 py-2 rounded-md space-y-2">
|
||||
<div className="flex items-center gap-3">
|
||||
{icon}
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{label}
|
||||
{note && (
|
||||
<span className="ml-1 text-xs text-muted-foreground font-normal">
|
||||
({note})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{roomsWithIssues.length > 0 && (
|
||||
<Accordion type="multiple" className="ml-6 border-l border-muted pl-4 mt-2">
|
||||
{roomsWithIssues.map(([roomName, room]: any) => (
|
||||
<AccordionItem key={roomName} value={roomName}>
|
||||
<AccordionTrigger>{formatRoomName(roomName)}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="text-sm text-muted-foreground space-y-1 px-1">
|
||||
{room.room_info?.overall_condition_of_the_room && (
|
||||
<div>
|
||||
<strong>Condition:</strong> {room.room_info.overall_condition_of_the_room}
|
||||
</div>
|
||||
)}
|
||||
{room.room_info?.does_the_room_have_any_defects && (
|
||||
<div>
|
||||
<strong>Defects:</strong> {room.room_info.does_the_room_have_any_defects}
|
||||
</div>
|
||||
)}
|
||||
{room.room_info?.ventilation_info
|
||||
?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room !==
|
||||
undefined && (
|
||||
<div>
|
||||
<strong>Damp/Mould:</strong>{" "}
|
||||
{room.room_info.ventilation_info
|
||||
.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
|
||||
? "Yes"
|
||||
: "No"}
|
||||
</div>
|
||||
)}
|
||||
{room.room_info?.windows_info?.condition_of_the_windows && (
|
||||
<div>
|
||||
<strong>Window Condition:</strong>{" "}
|
||||
{room.room_info.windows_info.condition_of_the_windows}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatRoomName(name: string) {
|
||||
return name
|
||||
.replaceAll("_", " ")
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
||||
.replace("Room Info", "Room");
|
||||
}
|
||||
|
||||
function getRecommendedOccupants(bedrooms: number): number {
|
||||
if (bedrooms <= 0) return 0;
|
||||
if (bedrooms === 1) return 2;
|
||||
if (bedrooms === 2) return 4;
|
||||
if (bedrooms === 3) return 6;
|
||||
return 7; // 4 or more
|
||||
}
|
||||
|
||||
export default function ConditionReport({
|
||||
conditionReport,
|
||||
totalFloorArea
|
||||
}: {
|
||||
conditionReport: {
|
||||
rooms: Record<string, any>;
|
||||
[key: string]: any;
|
||||
},
|
||||
totalFloorArea: number;
|
||||
}) {
|
||||
|
||||
// Documentation on decent home standards can be found here:
|
||||
// https://assets.publishing.service.gov.uk/media/5a7968b740f0b63d72fc5926/138355.pdf
|
||||
|
||||
const rooms = conditionReport.rooms;
|
||||
|
||||
const allRoomData = [
|
||||
...Object.entries(rooms).filter(([k, v]) => v?.room_info),
|
||||
...(rooms.bedrooms || []).map((b: any, i: number) => ["Bedroom " + (i + 1), b]),
|
||||
...(rooms.bathrooms || []).map((b: any, i: number) => ["Bathroom " + (i + 1), b]),
|
||||
];
|
||||
|
||||
const hasDampIssues = allRoomData.some(
|
||||
([, room]: any) =>
|
||||
room.room_info?.ventilation_info
|
||||
?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
|
||||
);
|
||||
|
||||
const hasDefects = allRoomData.some(
|
||||
([, room]: any) => room.room_info?.does_the_room_have_any_defects === "Yes"
|
||||
);
|
||||
|
||||
const windowsOk = allRoomData.every(([, room]: any) => {
|
||||
const wi = room.room_info?.windows_info;
|
||||
return wi?.does_the_room_have_any_windows
|
||||
? wi?.condition_of_the_windows === "Good condition"
|
||||
: true;
|
||||
});
|
||||
|
||||
const heatingWorking =
|
||||
conditionReport.heating_system?.general_condition
|
||||
?.is_the_heating_system_in_working_order === true;
|
||||
|
||||
const kitchenOk = rooms.kitchen?.room_info?.overall_condition_of_the_room === "Good";
|
||||
|
||||
const bathroomsOk = Array.isArray(rooms.bathrooms) &&
|
||||
rooms.bathrooms.length > 0 &&
|
||||
rooms.bathrooms.every(
|
||||
(b: any) =>
|
||||
b?.room_info?.overall_condition_of_the_room === "Good"
|
||||
)
|
||||
|
||||
const roomsWithDefects = allRoomData.filter(
|
||||
([, room]: any) => room.room_info?.does_the_room_have_any_defects === "Yes"
|
||||
);
|
||||
|
||||
const roomsWithDamp = allRoomData.filter(
|
||||
([, room]: any) =>
|
||||
room.room_info?.ventilation_info
|
||||
?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
|
||||
);
|
||||
|
||||
const roomsWithBadWindows = allRoomData.filter(
|
||||
([, room]: any) => {
|
||||
const wi = room.room_info?.windows_info;
|
||||
return wi?.does_the_room_have_any_windows && wi.condition_of_the_windows !== "Good condition";
|
||||
}
|
||||
);
|
||||
|
||||
// Check if the property has adequate space
|
||||
// We've seen a case where the number of adult occupants + child occupants is greater than the total_number_of_occupants so we
|
||||
// take the biggest of the two
|
||||
const totalOccupants = conditionReport.occupant_info?.total_number_of_occupants ?? 0;
|
||||
const totalAdults = conditionReport.occupant_info?.no_of_adult_occupants ?? 0;
|
||||
const totalChildren = conditionReport.occupant_info?.no_of_child_occupants ?? 0;
|
||||
const numberOfBedrooms = Array.isArray(rooms.bedrooms)
|
||||
? rooms.bedrooms.length
|
||||
: 0;
|
||||
|
||||
const occupantsToUse = Math.max(totalAdults + totalChildren, totalOccupants);
|
||||
|
||||
const maxOccupants = getRecommendedOccupants(numberOfBedrooms);
|
||||
const areaPerPerson = totalFloorArea / occupantsToUse;
|
||||
const hasSufficientSpace = (occupantsToUse <= maxOccupants) && (areaPerPerson >= 20);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 mt-8">
|
||||
<Card>
|
||||
<CardHeader className="text-lg font-semibold text-brandblue">
|
||||
Decent Homes Checklist
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<ChecklistItem
|
||||
label={roomsWithDamp ? "Signs of damp or mould present": "No signs of damp or mould"}
|
||||
passed={roomsWithDamp.length === 0}
|
||||
alert={roomsWithDamp.length > 0}
|
||||
roomsWithIssues={roomsWithDamp}
|
||||
/>
|
||||
<ChecklistItem
|
||||
label={hasDefects ? "Room defects present" : "No room defects present"}
|
||||
passed={!hasDefects}
|
||||
alert={hasDefects}
|
||||
roomsWithIssues={roomsWithDefects}
|
||||
/>
|
||||
<ChecklistItem
|
||||
label={heatingWorking ? "Heating system operational" : "Heating system not operational"}
|
||||
passed={heatingWorking}
|
||||
alert={!heatingWorking}
|
||||
/>
|
||||
<ChecklistItem
|
||||
label={windowsOk ? "Windows in good condition" : "Windows not in good condition"}
|
||||
passed={windowsOk}
|
||||
alert={!windowsOk}
|
||||
roomsWithIssues={roomsWithBadWindows}
|
||||
/>
|
||||
<ChecklistItem
|
||||
label={kitchenOk ? "Kitchen in good condition" : "Kitchen not in good condition"}
|
||||
passed={kitchenOk}
|
||||
alert={!kitchenOk}
|
||||
/>
|
||||
<ChecklistItem
|
||||
label={bathroomsOk ? "Bathrooms in good condition" : "Bathrooms not in good condition"}
|
||||
passed={bathroomsOk}
|
||||
alert={!bathroomsOk}
|
||||
/>
|
||||
<ChecklistItem
|
||||
label="Sufficient space for number of occupants"
|
||||
passed={hasSufficientSpace}
|
||||
alert={!hasSufficientSpace}
|
||||
note={`${totalOccupants} occupants, ${numberOfBedrooms} bedrooms. ${areaPerPerson}m² per person`}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -20,16 +20,10 @@ import {
|
|||
getPropertyMeta,
|
||||
getSpatialData,
|
||||
getNonIntrusiveSurvey,
|
||||
getDocument,
|
||||
getEnergyAssessmentFromS3
|
||||
} from "../utils";
|
||||
|
||||
function AddressCard({ address }: { address: string | null }) {
|
||||
// In the future, we might want to use react-wrap-balancer for some of this text
|
||||
return (
|
||||
<div className="flex flex-col items-center p-4 shadow rounded-md max-w-xl mx-auto justify-start text-gray-100 bg-brandblue">
|
||||
<div className="text-2xl font-bold max-w-l">{address}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import ConditionReport from "@/app/portfolio/[slug]/building-passport/[propertyId]/assessment/ConditionReport";
|
||||
|
||||
interface PropertyDetailsCardProps {
|
||||
conditionReportData: PropertyDetailsEpc;
|
||||
|
|
@ -141,6 +135,15 @@ export default async function PreAssessmentReport(
|
|||
conditionReportData,
|
||||
propertyMeta.propertyType
|
||||
);
|
||||
const conditionReportMeta = await getDocument(
|
||||
{ uprn: String(propertyMeta.uprn), documentType: "ECO_CONDITION_REPORT" }
|
||||
);
|
||||
let conditionReport = { rooms: {} };
|
||||
if (conditionReportMeta && conditionReportMeta.s3JsonUri) {
|
||||
conditionReport = await getEnergyAssessmentFromS3(conditionReportMeta.s3JsonUri);
|
||||
}
|
||||
|
||||
// console.log("conditionReport", conditionReport.rooms.utility)
|
||||
|
||||
const nonIntrusiveSurvey = await getNonIntrusiveSurvey(propertyMeta.uprn);
|
||||
|
||||
|
|
@ -148,10 +151,15 @@ export default async function PreAssessmentReport(
|
|||
|
||||
const heatingDemand = formatHeatDemandFeatures(conditionReportData);
|
||||
|
||||
// If total floor area is missing, we have a problem
|
||||
if (conditionReportData.totalFloorArea == null) {
|
||||
console.error("Total floor area is missing");
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="leading-loose tracking-wider">
|
||||
<div className="flex py-8 text-lg">Pre Assessment Report</div>
|
||||
<div className="text-gray-700 text-sm">
|
||||
<div className="text-gray-700 text-sm mt-4">
|
||||
Last updated: {formatDateTime(propertyMeta.updatedAt)}
|
||||
</div>
|
||||
<div className="flex flex-col items-stretch mb-4">
|
||||
|
|
@ -169,6 +177,10 @@ export default async function PreAssessmentReport(
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
Object.keys(conditionReportMeta).length > 0 && <ConditionReport conditionReport={conditionReport} totalFloorArea={conditionReportData.totalFloorArea}/>
|
||||
}
|
||||
|
||||
{nonIntrusiveSurvey && (
|
||||
<div>
|
||||
<div className="flex py-8 text-lg">Non-Intrusive Survey</div>
|
||||
|
|
@ -2,16 +2,15 @@ import { getPropertyMeta } from "@/app/portfolio/[slug]/building-passport/[prope
|
|||
import { eq } from "drizzle-orm";
|
||||
import { DocumentsTable } from "./DocumentsTable";
|
||||
import { surveyDB } from "@/app/db/surveyDB/connection";
|
||||
import { uploaded_files } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
import { uploadedFiles } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
import { type getUploadedFiles } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
import { EmptyObject } from "react-hook-form";
|
||||
|
||||
|
||||
async function getDocuments(
|
||||
uprn: number
|
||||
): Promise< getUploadedFiles> {
|
||||
const result = surveyDB.query.uploaded_files.findMany({
|
||||
where: eq(uploaded_files.uprn, String(uprn)),
|
||||
const result = surveyDB.query.uploadedFiles.findMany({
|
||||
where: eq(uploadedFiles.uprn, String(uprn)),
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Toolbar } from "@/app/components/building-passport/Toolbar";
|
||||
import { getPropertyMeta } from "./utils";
|
||||
import { getPropertyMeta, getDocument } from "./utils";
|
||||
import BackToPortfolioButton from "@/app/components/building-passport/BackToPortfolioButton";
|
||||
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
|
|
@ -30,6 +30,11 @@ export default async function DashboardLayout(
|
|||
|
||||
// The layout is a server component by default so we can fetch meta data here
|
||||
const propertyMeta = await getPropertyMeta(params.propertyId);
|
||||
// We check if we have an uploaded condition report and if so, we show the condition tab. Otherwise, we
|
||||
// don't show it
|
||||
const conditionReport = await getDocument(
|
||||
{ uprn: String(propertyMeta.uprn), documentType: "ECO_CONDITION_REPORT" }
|
||||
);
|
||||
|
||||
if (!propertyId && propertyId !== "0") {
|
||||
throw Error("Invalid propertyId");
|
||||
|
|
@ -53,7 +58,7 @@ export default async function DashboardLayout(
|
|||
<p className="text-xl text-gray-700">{propertyMeta.postcode}</p>
|
||||
</div>
|
||||
<div className="col-span-12 justify-center bg-gray-50 py-2 rounded-md">
|
||||
<Toolbar propertyId={propertyId} portfolioId={portfolioId} />
|
||||
<Toolbar propertyId={propertyId} portfolioId={portfolioId} conditionReport={conditionReport}/>
|
||||
</div>
|
||||
{propertyMeta.detailsEpc.estimated && <EstimatedDataNotification />}
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import S3 from "aws-sdk/clients/s3";
|
||||
import {
|
||||
Recommendation,
|
||||
planRecommendations,
|
||||
|
|
@ -5,6 +6,7 @@ import {
|
|||
Plan,
|
||||
} from "@/app/db/schema/recommendations";
|
||||
import { db } from "@/app/db/db";
|
||||
import { surveyDB } from "@/app/db/surveyDB/connection";
|
||||
import {
|
||||
Feature,
|
||||
GeneralFeature,
|
||||
|
|
@ -17,7 +19,7 @@ import {
|
|||
nonInstrusiveSurvey,
|
||||
} from "@/app/db/schema/property";
|
||||
import { getRating } from "@/app/utils";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { eq, desc, and } from "drizzle-orm";
|
||||
import {
|
||||
energyAssessment,
|
||||
EnergyAssessment,
|
||||
|
|
@ -26,9 +28,38 @@ import {
|
|||
} from "@/app/db/schema/energy_assessments";
|
||||
import {
|
||||
fundingPackage,
|
||||
FundingPackage,
|
||||
FundingPackageWithMeasures
|
||||
} from "@/app/db/schema/funding";
|
||||
import { getUploadedFile, uploadedFiles, DB_REPORT_TYPES } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
|
||||
|
||||
export async function getEnergyAssessmentFromS3(s3Uri: string): Promise<any> {
|
||||
const url = new URL(s3Uri);
|
||||
|
||||
const bucketMatch = url.hostname.match(/^(.+)\.s3/);
|
||||
const bucket = bucketMatch?.[1];
|
||||
const key = url.pathname.startsWith("/") ? url.pathname.slice(1) : url.pathname;
|
||||
|
||||
if (!bucket || !key) {
|
||||
throw new Error("Could not extract bucket or key from URI");
|
||||
}
|
||||
|
||||
const s3 = new S3({
|
||||
region: "eu-west-2",
|
||||
accessKeyId: process.env.RETROFIT_ENERGY_ASSESSMENTS_AWS_ACCESS_KEY,
|
||||
secretAccessKey: process.env.ENERGY_ASSESSMENTS_AWS_SECRET,
|
||||
});
|
||||
|
||||
const result = await s3
|
||||
.getObject({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
})
|
||||
.promise();
|
||||
|
||||
const body = result.Body?.toString("utf-8");
|
||||
return body ? JSON.parse(body) : null;
|
||||
}
|
||||
|
||||
type RecommendationList = {
|
||||
recommendation: Recommendation;
|
||||
|
|
@ -49,6 +80,21 @@ export async function getPlanFunding(planId: string): Promise<FundingPackageWith
|
|||
return data;
|
||||
}
|
||||
|
||||
export async function getDocument(
|
||||
{uprn, documentType}: {uprn: string; documentType: typeof DB_REPORT_TYPES[number]}
|
||||
): Promise<getUploadedFile> {
|
||||
// We get the latest entry for the given UPRN and document type, by s3JsonUploadTimestamp
|
||||
const data = await surveyDB.query.uploadedFiles.findFirst({
|
||||
where: and(eq(uploadedFiles.uprn, String(uprn)), eq(uploadedFiles.docType, documentType)),
|
||||
orderBy: (uploadedFiles, { desc }) => [desc(uploadedFiles.s3JsonUploadTimestamp)]
|
||||
});
|
||||
// We may not have an uploaded document so we return an empty array
|
||||
if (!data) {
|
||||
return {} as getUploadedFile;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getEnergyAssessment(
|
||||
uprn: number
|
||||
): Promise<EnergyAssessment> {
|
||||
|
|
|
|||
58
src/app/shadcn_components/ui/accordion.tsx
Normal file
58
src/app/shadcn_components/ui/accordion.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AccordionItem.displayName = "AccordionItem"
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
|
|
@ -12,193 +12,227 @@ module.exports = {
|
|||
"./node_modules/@tremor/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
transparent: "transparent",
|
||||
current: "currentColor",
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
colors: {
|
||||
// Tremor light mode
|
||||
tremor: {
|
||||
brand: {
|
||||
faint: colors.blue[50],
|
||||
muted: colors.blue[200],
|
||||
subtle: colors.blue[400],
|
||||
DEFAULT: colors.blue[500],
|
||||
emphasis: colors.blue[700],
|
||||
inverted: colors.white,
|
||||
},
|
||||
background: {
|
||||
muted: colors.gray[50],
|
||||
subtle: colors.gray[100],
|
||||
DEFAULT: colors.white,
|
||||
emphasis: colors.gray[700],
|
||||
},
|
||||
border: {
|
||||
DEFAULT: colors.gray[200],
|
||||
},
|
||||
ring: {
|
||||
DEFAULT: colors.gray[200],
|
||||
},
|
||||
content: {
|
||||
subtle: colors.gray[400],
|
||||
DEFAULT: colors.gray[500],
|
||||
emphasis: colors.gray[700],
|
||||
strong: colors.gray[900],
|
||||
inverted: colors.white,
|
||||
},
|
||||
},
|
||||
// Tremor dark mode
|
||||
// dark mode
|
||||
"dark-tremor": {
|
||||
brand: {
|
||||
faint: "#0B1229",
|
||||
muted: colors.blue[950],
|
||||
subtle: colors.blue[800],
|
||||
DEFAULT: colors.blue[500],
|
||||
emphasis: colors.blue[400],
|
||||
inverted: colors.blue[950],
|
||||
},
|
||||
background: {
|
||||
muted: "#131A2B",
|
||||
subtle: colors.gray[800],
|
||||
DEFAULT: colors.gray[900],
|
||||
emphasis: colors.gray[300],
|
||||
},
|
||||
border: {
|
||||
DEFAULT: colors.gray[800],
|
||||
},
|
||||
ring: {
|
||||
DEFAULT: colors.gray[800],
|
||||
},
|
||||
content: {
|
||||
subtle: colors.gray[600],
|
||||
DEFAULT: colors.gray[500],
|
||||
emphasis: colors.gray[200],
|
||||
strong: colors.gray[50],
|
||||
inverted: colors.gray[950],
|
||||
},
|
||||
},
|
||||
epc_a: "#117d58",
|
||||
epc_b: "#2da55c",
|
||||
epc_c: "#8dbd40",
|
||||
epc_d: "#f7cd14",
|
||||
epc_e: "#f3a96a",
|
||||
epc_f: "#ef8026",
|
||||
epc_g: "#e41e3b",
|
||||
brandblue: "#14163d",
|
||||
hoverblue: "#3e4073",
|
||||
brandtan: "#d3b488",
|
||||
hovertan: "#947750",
|
||||
brandgold: "#f1bb06",
|
||||
hovergold: "#c79d12",
|
||||
brandbrown: "#c4a47c",
|
||||
brandmidblue: "#3943b7",
|
||||
brandlightblue: "#00a9f4",
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
textColor: {
|
||||
brandblue: "#14163d",
|
||||
hoverblue: "#3e4073",
|
||||
brandtan: "#d3b488",
|
||||
hovertan: "#947750",
|
||||
brandbrown: "#c4a47c",
|
||||
brandmidblue: "#3943b7",
|
||||
brandlightblue: "#00a9f4",
|
||||
},
|
||||
borderRadius: {
|
||||
lg: `var(--radius)`,
|
||||
md: `calc(var(--radius) - 2px)`,
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
maxWidth: {
|
||||
"8xl": "90rem",
|
||||
},
|
||||
boxShadow: {
|
||||
// light
|
||||
"tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
||||
"tremor-card":
|
||||
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
|
||||
"tremor-dropdown":
|
||||
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
|
||||
// dark
|
||||
"dark-tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
||||
"dark-tremor-card":
|
||||
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
|
||||
"dark-tremor-dropdown":
|
||||
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
|
||||
},
|
||||
borderRadius: {
|
||||
"tremor-small": "0.375rem",
|
||||
"tremor-default": "0.5rem",
|
||||
"tremor-full": "9999px",
|
||||
},
|
||||
fontSize: {
|
||||
"tremor-label": ["0.75rem", { lineHeight: "1rem" }],
|
||||
"tremor-default": ["0.875rem", { lineHeight: "1.25rem" }],
|
||||
"tremor-title": ["1.125rem", { lineHeight: "1.75rem" }],
|
||||
"tremor-metric": ["1.875rem", { lineHeight: "2.25rem" }],
|
||||
},
|
||||
},
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
container: {
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
screens: {
|
||||
'2xl': '1400px'
|
||||
}
|
||||
},
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
|
||||
},
|
||||
colors: {
|
||||
tremor: {
|
||||
brand: {
|
||||
faint: 'colors.blue[50]',
|
||||
muted: 'colors.blue[200]',
|
||||
subtle: 'colors.blue[400]',
|
||||
DEFAULT: 'colors.blue[500]',
|
||||
emphasis: 'colors.blue[700]',
|
||||
inverted: 'colors.white'
|
||||
},
|
||||
background: {
|
||||
muted: 'colors.gray[50]',
|
||||
subtle: 'colors.gray[100]',
|
||||
DEFAULT: 'colors.white',
|
||||
emphasis: 'colors.gray[700]'
|
||||
},
|
||||
border: {
|
||||
DEFAULT: 'colors.gray[200]'
|
||||
},
|
||||
ring: {
|
||||
DEFAULT: 'colors.gray[200]'
|
||||
},
|
||||
content: {
|
||||
subtle: 'colors.gray[400]',
|
||||
DEFAULT: 'colors.gray[500]',
|
||||
emphasis: 'colors.gray[700]',
|
||||
strong: 'colors.gray[900]',
|
||||
inverted: 'colors.white'
|
||||
}
|
||||
},
|
||||
'dark-tremor': {
|
||||
brand: {
|
||||
faint: '#0B1229',
|
||||
muted: 'colors.blue[950]',
|
||||
subtle: 'colors.blue[800]',
|
||||
DEFAULT: 'colors.blue[500]',
|
||||
emphasis: 'colors.blue[400]',
|
||||
inverted: 'colors.blue[950]'
|
||||
},
|
||||
background: {
|
||||
muted: '#131A2B',
|
||||
subtle: 'colors.gray[800]',
|
||||
DEFAULT: 'colors.gray[900]',
|
||||
emphasis: 'colors.gray[300]'
|
||||
},
|
||||
border: {
|
||||
DEFAULT: 'colors.gray[800]'
|
||||
},
|
||||
ring: {
|
||||
DEFAULT: 'colors.gray[800]'
|
||||
},
|
||||
content: {
|
||||
subtle: 'colors.gray[600]',
|
||||
DEFAULT: 'colors.gray[500]',
|
||||
emphasis: 'colors.gray[200]',
|
||||
strong: 'colors.gray[50]',
|
||||
inverted: 'colors.gray[950]'
|
||||
}
|
||||
},
|
||||
epc_a: '#117d58',
|
||||
epc_b: '#2da55c',
|
||||
epc_c: '#8dbd40',
|
||||
epc_d: '#f7cd14',
|
||||
epc_e: '#f3a96a',
|
||||
epc_f: '#ef8026',
|
||||
epc_g: '#e41e3b',
|
||||
brandblue: '#14163d',
|
||||
hoverblue: '#3e4073',
|
||||
brandtan: '#d3b488',
|
||||
hovertan: '#947750',
|
||||
brandgold: '#f1bb06',
|
||||
hovergold: '#c79d12',
|
||||
brandbrown: '#c4a47c',
|
||||
brandmidblue: '#3943b7',
|
||||
brandlightblue: '#00a9f4',
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
}
|
||||
},
|
||||
textColor: {
|
||||
brandblue: '#14163d',
|
||||
hoverblue: '#3e4073',
|
||||
brandtan: '#d3b488',
|
||||
hovertan: '#947750',
|
||||
brandbrown: '#c4a47c',
|
||||
brandmidblue: '#3943b7',
|
||||
brandlightblue: '#00a9f4'
|
||||
},
|
||||
borderRadius: {
|
||||
'tremor-small': '0.375rem',
|
||||
'tremor-default': '0.5rem',
|
||||
'tremor-full': '9999px'
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'var(--font-sans)',
|
||||
...fontFamily.sans
|
||||
]
|
||||
},
|
||||
keyframes: {
|
||||
'accordion-down': {
|
||||
from: {
|
||||
height: 0
|
||||
},
|
||||
to: {
|
||||
height: 'var(--radix-accordion-content-height)'
|
||||
}
|
||||
},
|
||||
'accordion-up': {
|
||||
from: {
|
||||
height: 'var(--radix-accordion-content-height)'
|
||||
},
|
||||
to: {
|
||||
height: 0
|
||||
}
|
||||
},
|
||||
'accordion-down': {
|
||||
from: {
|
||||
height: '0'
|
||||
},
|
||||
to: {
|
||||
height: 'var(--radix-accordion-content-height)'
|
||||
}
|
||||
},
|
||||
'accordion-up': {
|
||||
from: {
|
||||
height: 'var(--radix-accordion-content-height)'
|
||||
},
|
||||
to: {
|
||||
height: '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out'
|
||||
},
|
||||
maxWidth: {
|
||||
'8xl': '90rem'
|
||||
},
|
||||
boxShadow: {
|
||||
'tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
'tremor-card': '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
'tremor-dropdown': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
'dark-tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
'dark-tremor-card': '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
'dark-tremor-dropdown': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
|
||||
},
|
||||
fontSize: {
|
||||
'tremor-label': [
|
||||
'0.75rem',
|
||||
{
|
||||
lineHeight: '1rem'
|
||||
}
|
||||
],
|
||||
'tremor-default': [
|
||||
'0.875rem',
|
||||
{
|
||||
lineHeight: '1.25rem'
|
||||
}
|
||||
],
|
||||
'tremor-title': [
|
||||
'1.125rem',
|
||||
{
|
||||
lineHeight: '1.75rem'
|
||||
}
|
||||
],
|
||||
'tremor-metric': [
|
||||
'1.875rem',
|
||||
{
|
||||
lineHeight: '2.25rem'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
variants: {
|
||||
extend: {
|
||||
|
|
@ -234,28 +268,90 @@ module.exports = {
|
|||
/^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
|
||||
},
|
||||
// This enables the EPC colours for tremor. They're listed from EPC G -> A
|
||||
...[
|
||||
"[#e41e3b]",
|
||||
"[#ef8026]",
|
||||
"[#f3a96a]",
|
||||
"[#f7cd14]",
|
||||
"[#8dbd40]",
|
||||
"[#2da55c]",
|
||||
"[#117d58]",
|
||||
].flatMap((customColor) => [
|
||||
`bg-${customColor}`,
|
||||
`border-${customColor}`,
|
||||
`hover:bg-${customColor}`,
|
||||
`hover:border-${customColor}`,
|
||||
`hover:text-${customColor}`,
|
||||
`fill-${customColor}`,
|
||||
`ring-${customColor}`,
|
||||
`stroke-${customColor}`,
|
||||
`text-${customColor}`,
|
||||
`ui-selected:bg-${customColor}`,
|
||||
`ui-selected:border-${customColor}`,
|
||||
`ui-selected:text-${customColor}`,
|
||||
]),
|
||||
"bg-[#e41e3b]",
|
||||
"border-[#e41e3b]",
|
||||
"hover:bg-[#e41e3b]",
|
||||
"hover:border-[#e41e3b]",
|
||||
"hover:text-[#e41e3b]",
|
||||
"fill-[#e41e3b]",
|
||||
"ring-[#e41e3b]",
|
||||
"stroke-[#e41e3b]",
|
||||
"text-[#e41e3b]",
|
||||
"ui-selected:bg-[#e41e3b]",
|
||||
"ui-selected:border-[#e41e3b]",
|
||||
"ui-selected:text-[#e41e3b]",
|
||||
"bg-[#ef8026]",
|
||||
"border-[#ef8026]",
|
||||
"hover:bg-[#ef8026]",
|
||||
"hover:border-[#ef8026]",
|
||||
"hover:text-[#ef8026]",
|
||||
"fill-[#ef8026]",
|
||||
"ring-[#ef8026]",
|
||||
"stroke-[#ef8026]",
|
||||
"text-[#ef8026]",
|
||||
"ui-selected:bg-[#ef8026]",
|
||||
"ui-selected:border-[#ef8026]",
|
||||
"ui-selected:text-[#ef8026]",
|
||||
"bg-[#f3a96a]",
|
||||
"border-[#f3a96a]",
|
||||
"hover:bg-[#f3a96a]",
|
||||
"hover:border-[#f3a96a]",
|
||||
"hover:text-[#f3a96a]",
|
||||
"fill-[#f3a96a]",
|
||||
"ring-[#f3a96a]",
|
||||
"stroke-[#f3a96a]",
|
||||
"text-[#f3a96a]",
|
||||
"ui-selected:bg-[#f3a96a]",
|
||||
"ui-selected:border-[#f3a96a]",
|
||||
"ui-selected:text-[#f3a96a]",
|
||||
"bg-[#f7cd14]",
|
||||
"border-[#f7cd14]",
|
||||
"hover:bg-[#f7cd14]",
|
||||
"hover:border-[#f7cd14]",
|
||||
"hover:text-[#f7cd14]",
|
||||
"fill-[#f7cd14]",
|
||||
"ring-[#f7cd14]",
|
||||
"stroke-[#f7cd14]",
|
||||
"text-[#f7cd14]",
|
||||
"ui-selected:bg-[#f7cd14]",
|
||||
"ui-selected:border-[#f7cd14]",
|
||||
"ui-selected:text-[#f7cd14]",
|
||||
"bg-[#8dbd40]",
|
||||
"border-[#8dbd40]",
|
||||
"hover:bg-[#8dbd40]",
|
||||
"hover:border-[#8dbd40]",
|
||||
"hover:text-[#8dbd40]",
|
||||
"fill-[#8dbd40]",
|
||||
"ring-[#8dbd40]",
|
||||
"stroke-[#8dbd40]",
|
||||
"text-[#8dbd40]",
|
||||
"ui-selected:bg-[#8dbd40]",
|
||||
"ui-selected:border-[#8dbd40]",
|
||||
"ui-selected:text-[#8dbd40]",
|
||||
"bg-[#2da55c]",
|
||||
"border-[#2da55c]",
|
||||
"hover:bg-[#2da55c]",
|
||||
"hover:border-[#2da55c]",
|
||||
"hover:text-[#2da55c]",
|
||||
"fill-[#2da55c]",
|
||||
"ring-[#2da55c]",
|
||||
"stroke-[#2da55c]",
|
||||
"text-[#2da55c]",
|
||||
"ui-selected:bg-[#2da55c]",
|
||||
"ui-selected:border-[#2da55c]",
|
||||
"ui-selected:text-[#2da55c]",
|
||||
"bg-[#117d58]",
|
||||
"border-[#117d58]",
|
||||
"hover:bg-[#117d58]",
|
||||
"hover:border-[#117d58]",
|
||||
"hover:text-[#117d58]",
|
||||
"fill-[#117d58]",
|
||||
"ring-[#117d58]",
|
||||
"stroke-[#117d58]",
|
||||
"text-[#117d58]",
|
||||
"ui-selected:bg-[#117d58]",
|
||||
"ui-selected:border-[#117d58]",
|
||||
"ui-selected:text-[#117d58]",
|
||||
],
|
||||
plugins: [
|
||||
function ({ addVariant }) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue