mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
added filtering to filter on lodged rating rather than modelled
This commit is contained in:
parent
7514267e3d
commit
4bbd973b6b
3 changed files with 98 additions and 11 deletions
|
|
@ -69,6 +69,8 @@ export async function GET(
|
|||
const sid = BigInt(scenarioId);
|
||||
const hideNonCompliant =
|
||||
request.nextUrl.searchParams.get("hideNonCompliant") === "true";
|
||||
const useOriginalBaseline =
|
||||
request.nextUrl.searchParams.get("useOriginalBaseline") === "true";
|
||||
|
||||
/* ----------------------------------------------------------
|
||||
Query 0 — scenario definition
|
||||
|
|
@ -129,7 +131,12 @@ export async function GET(
|
|||
END
|
||||
)::float AS total_sap_uplift
|
||||
FROM latest_plans lp
|
||||
JOIN property p ON p.id = lp.property_id;
|
||||
JOIN property p ON p.id = lp.property_id
|
||||
WHERE (
|
||||
${useOriginalBaseline} = false
|
||||
OR ${minSap}::float IS NULL
|
||||
OR p.original_sap_points < ${minSap}::float
|
||||
);
|
||||
`);
|
||||
|
||||
const scenarioAgg = scenarioMetricsResult.rows[0] as ScenarioAggregates;
|
||||
|
|
@ -162,8 +169,14 @@ export async function GET(
|
|||
COALESCE(fp.total_uplift, 0)
|
||||
)::float AS total_funding
|
||||
FROM latest_plans lp
|
||||
JOIN property p ON p.id = lp.property_id
|
||||
LEFT JOIN funding_package fp ON fp.plan_id = lp.id
|
||||
WHERE lp.cost_of_works > 0;
|
||||
WHERE lp.cost_of_works > 0
|
||||
AND (
|
||||
${useOriginalBaseline} = false
|
||||
OR ${minSap}::float IS NULL
|
||||
OR p.original_sap_points < ${minSap}::float
|
||||
);
|
||||
`);
|
||||
|
||||
const upgraded = upgradedResult.rows[0] as UpgradedAggregates;
|
||||
|
|
@ -223,6 +236,11 @@ export async function GET(
|
|||
AND plan.post_sap_points >= ${minSap}::float
|
||||
)
|
||||
)
|
||||
AND (
|
||||
${useOriginalBaseline} = false
|
||||
OR ${minSap}::float IS NULL
|
||||
OR p.original_sap_points < ${minSap}::float
|
||||
)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
) lp ON true
|
||||
|
|
@ -256,6 +274,11 @@ export async function GET(
|
|||
AND plan.post_sap_points >= ${minSap}::float
|
||||
)
|
||||
)
|
||||
AND (
|
||||
${useOriginalBaseline} = false
|
||||
OR ${minSap}::float IS NULL
|
||||
OR p.original_sap_points < ${minSap}::float
|
||||
)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
) lp ON true
|
||||
|
|
|
|||
|
|
@ -39,13 +39,16 @@ async function fetchScenarioReport({
|
|||
portfolioId,
|
||||
scenarioId,
|
||||
hideNonCompliant,
|
||||
useOriginalBaseline,
|
||||
}: {
|
||||
portfolioId: number;
|
||||
scenarioId: number | "default";
|
||||
hideNonCompliant: boolean;
|
||||
useOriginalBaseline: boolean;
|
||||
}) {
|
||||
const params = new URLSearchParams({
|
||||
hideNonCompliant: String(hideNonCompliant),
|
||||
useOriginalBaseline: String(useOriginalBaseline),
|
||||
});
|
||||
|
||||
const path = `/api/portfolio/${portfolioId}/scenario/${scenarioId}/metrics`;
|
||||
|
|
@ -89,6 +92,8 @@ export function ReportingClientArea({
|
|||
const [measuresOpen, setMeasuresOpen] = useState<boolean>(false);
|
||||
const [appliedHideNonCompliant, setAppliedHideNonCompliant] =
|
||||
useState<boolean>(false);
|
||||
const [appliedUseOriginalBaseline, setAppliedUseOriginalBaseline] =
|
||||
useState<boolean>(false);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
|
||||
const drawerOpen = Boolean(selectedScenarioId);
|
||||
|
|
@ -107,12 +112,14 @@ export function ReportingClientArea({
|
|||
portfolioId,
|
||||
selectedScenarioId,
|
||||
appliedHideNonCompliant,
|
||||
appliedUseOriginalBaseline,
|
||||
],
|
||||
queryFn: () =>
|
||||
fetchScenarioReport({
|
||||
portfolioId,
|
||||
scenarioId: selectedScenarioId!,
|
||||
hideNonCompliant: appliedHideNonCompliant,
|
||||
useOriginalBaseline: appliedUseOriginalBaseline,
|
||||
}),
|
||||
enabled: selectedScenarioId !== null, // only run when scenario selected or default selected
|
||||
keepPreviousData: true, // keep showing old data while loading new scenario or applying filter
|
||||
|
|
@ -238,12 +245,14 @@ export function ReportingClientArea({
|
|||
|
||||
<ReportingFunctionalityButtons
|
||||
hideNonCompliant={appliedHideNonCompliant}
|
||||
useOriginalBaseline={appliedUseOriginalBaseline}
|
||||
disabled={scenarioBusy}
|
||||
canFilterNonCompliant={
|
||||
selectedScenarioId !== null && selectedScenarioId !== "default"
|
||||
}
|
||||
onApply={async (value) => {
|
||||
setAppliedHideNonCompliant(value);
|
||||
onApply={async ({ hideNonCompliant, useOriginalBaseline }) => {
|
||||
setAppliedHideNonCompliant(hideNonCompliant);
|
||||
setAppliedUseOriginalBaseline(useOriginalBaseline);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,33 +13,45 @@ export interface ReportingFunctionalityButtonsProps {
|
|||
/** Currently applied value */
|
||||
hideNonCompliant: boolean;
|
||||
|
||||
/** Currently applied value */
|
||||
useOriginalBaseline: boolean;
|
||||
|
||||
/**
|
||||
* Explicit user action.
|
||||
* Parent decides what "apply" means (refetch, mutate, etc).
|
||||
*/
|
||||
onApply: (value: boolean) => Promise<void> | void;
|
||||
onApply: (options: {
|
||||
hideNonCompliant: boolean;
|
||||
useOriginalBaseline: boolean;
|
||||
}) => Promise<void> | void;
|
||||
|
||||
disabled?: boolean;
|
||||
|
||||
/* Whether hideNonCompliant filter is available */
|
||||
/* Whether filters are available (only for specific non-default scenarios) */
|
||||
canFilterNonCompliant?: boolean;
|
||||
}
|
||||
|
||||
export function ReportingFunctionalityButtons({
|
||||
hideNonCompliant,
|
||||
useOriginalBaseline,
|
||||
onApply,
|
||||
disabled = false,
|
||||
canFilterNonCompliant = true,
|
||||
}: ReportingFunctionalityButtonsProps) {
|
||||
const [draftHideNonCompliant, setDraftHideNonCompliant] =
|
||||
useState<boolean>(hideNonCompliant);
|
||||
const [draftUseOriginalBaseline, setDraftUseOriginalBaseline] =
|
||||
useState<boolean>(useOriginalBaseline);
|
||||
|
||||
const [isApplying, setIsApplying] = useState(false);
|
||||
|
||||
async function handleApply() {
|
||||
try {
|
||||
setIsApplying(true);
|
||||
await onApply(draftHideNonCompliant);
|
||||
await onApply({
|
||||
hideNonCompliant: draftHideNonCompliant,
|
||||
useOriginalBaseline: draftUseOriginalBaseline,
|
||||
});
|
||||
} finally {
|
||||
setIsApplying(false);
|
||||
}
|
||||
|
|
@ -50,7 +62,8 @@ export function ReportingFunctionalityButtons({
|
|||
// reset the filter and trigger the fetch
|
||||
setIsApplying(true);
|
||||
setDraftHideNonCompliant(false);
|
||||
await onApply(false);
|
||||
setDraftUseOriginalBaseline(false);
|
||||
await onApply({ hideNonCompliant: false, useOriginalBaseline: false });
|
||||
} finally {
|
||||
setIsApplying(false);
|
||||
}
|
||||
|
|
@ -61,6 +74,7 @@ export function ReportingFunctionalityButtons({
|
|||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
setDraftHideNonCompliant(hideNonCompliant);
|
||||
setDraftUseOriginalBaseline(useOriginalBaseline);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
@ -72,7 +86,7 @@ export function ReportingFunctionalityButtons({
|
|||
className={`
|
||||
relative flex items-center gap-2
|
||||
${
|
||||
hideNonCompliant
|
||||
hideNonCompliant || useOriginalBaseline
|
||||
? "border-brandmidblue/40 bg-brandlightblue/40"
|
||||
: ""
|
||||
}
|
||||
|
|
@ -81,7 +95,7 @@ export function ReportingFunctionalityButtons({
|
|||
{/* Filter icon */}
|
||||
<svg
|
||||
className={`h-4 w-4 ${
|
||||
hideNonCompliant ? "text-brandmidblue" : "text-gray-500"
|
||||
hideNonCompliant || useOriginalBaseline ? "text-brandmidblue" : "text-gray-500"
|
||||
}`}
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
|
|
@ -89,7 +103,7 @@ export function ReportingFunctionalityButtons({
|
|||
<path d="M3 4a1 1 0 011-1h12a1 1 0 01.8 1.6l-4.8 6.4V16a1 1 0 01-1.447.894l-2-1A1 1 0 018 14v-2.999L3.2 5.6A1 1 0 013 4z" />
|
||||
</svg>
|
||||
Filter options
|
||||
{hideNonCompliant && (
|
||||
{(hideNonCompliant || useOriginalBaseline) && (
|
||||
<span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-brandmidblue" />
|
||||
)}
|
||||
</Button>
|
||||
|
|
@ -140,6 +154,47 @@ export function ReportingFunctionalityButtons({
|
|||
</label>
|
||||
</div>
|
||||
|
||||
{/* Use original SAP points */}
|
||||
<div
|
||||
className={`flex items-start gap-4 ${
|
||||
!canFilterNonCompliant ? "opacity-50 pointer-events-none" : ""
|
||||
}`}
|
||||
>
|
||||
<Checkbox
|
||||
id="use-original-baseline"
|
||||
checked={draftUseOriginalBaseline}
|
||||
disabled={!canFilterNonCompliant}
|
||||
onCheckedChange={(checked) =>
|
||||
setDraftUseOriginalBaseline(Boolean(checked))
|
||||
}
|
||||
className="mt-1"
|
||||
/>
|
||||
|
||||
<label
|
||||
htmlFor="use-original-baseline"
|
||||
className="cursor-pointer space-y-1"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-900 leading-snug">
|
||||
<svg
|
||||
className="h-4 w-4 text-gray-400"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Use lodged SAP points
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 leading-relaxed">
|
||||
Base metrics on properties below the EPC target using their lodged SAP rating, rather than the current modelled rating
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-2 pt-4 border-t">
|
||||
<Button
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue