handling incorrect aggregation due to timezones

This commit is contained in:
Khalim Conn-Kowlessar 2026-04-14 21:07:08 +00:00
parent 02282cbb90
commit 35aa72bce8
5 changed files with 55 additions and 10 deletions

View file

@ -48,7 +48,7 @@ export const hubspotDealData = pgTable("hubspot_deal_data", {
damnpMouldAndRepairComments: text("damp_mould_and_repairs_comments"),
confirmedSurveyDate: timestamp("confirmed_survey_date", { precision: 6, withTimezone: true }),
confirmedSurveyTime: text("confirmed_survey_time"),
SurveyedDate: timestamp("surveyed_date", { precision: 6, withTimezone: true }),
surveyedDate: timestamp("surveyed_date", { precision: 6, withTimezone: true }),
createdAt: timestamp("created_at", { precision: 6, withTimezone: true })
.defaultNow()

View file

@ -110,9 +110,9 @@ const DESIGN_TYPE_ORDER = [
function getMondayOfWeek(date: Date): string {
const d = new Date(date);
const day = d.getDay();
d.setDate(d.getDate() - (day === 0 ? 6 : day - 1));
d.setHours(0, 0, 0, 0);
const day = d.getUTCDay();
d.setUTCDate(d.getUTCDate() - (day === 0 ? 6 : day - 1));
d.setUTCHours(0, 0, 0, 0);
return d.toISOString().split("T")[0];
}
@ -132,7 +132,7 @@ function fillWeekGaps(keys: string[]): string[] {
const end = new Date(sorted[sorted.length - 1]);
while (current <= end) {
result.push(current.toISOString().split("T")[0]);
current.setDate(current.getDate() + 7);
current.setUTCDate(current.getUTCDate() + 7);
}
return result;
}
@ -314,14 +314,28 @@ export default function CompletionTrendsChart({
const isDesign = metric === "design";
const isStacked = isCoordination || isAssessments || isLodgement || isDesign;
// External assessments with no date
// Assessments (retrofit or EPC) with no date
const undatedAssessments = isAssessments
? deals.filter((d) => {
const o = d.outcome ?? "";
return (o === "Surveyed" || o === "Surveyed - Pending Upload") && !d.surveyedDate;
return (
(o === "Surveyed" || o === "Surveyed - Pending Upload" || o === "EPC Completed") &&
!d.surveyedDate
);
})
: [];
// Dated assessments broken down by type — used for summary badges
const retrofitDeals = isAssessments
? deals.filter((d) => {
const o = d.outcome ?? "";
return (o === "Surveyed" || o === "Surveyed - Pending Upload") && !!d.surveyedDate;
})
: [];
const epcDeals = isAssessments
? deals.filter((d) => d.outcome === "EPC Completed" && !!d.surveyedDate)
: [];
// Build chart data
let chartData: Record<string, string | number>[];
let categories: string[];
@ -363,7 +377,37 @@ export default function CompletionTrendsChart({
<Title className="text-brandblue text-lg font-bold">
Trends Over Time
</Title>
{totalCompleted !== null && (
{isAssessments ? (
<div className="flex flex-wrap gap-2">
{([
{ label: "Retrofit Assessments", dealList: retrofitDeals },
{ label: "EPCs", dealList: epcDeals },
] as const).filter(({ dealList }) => dealList.length > 0).map(({ label, dealList }) => (
<button
key={label}
type="button"
onClick={() =>
onOpenTable?.(
`Completed — ${label}`,
dealList,
["dealname", "landlordPropertyId", "outcome", "surveyedDate"],
{
dealname: "Address",
landlordPropertyId: "Property Ref.",
outcome: "Work Type",
surveyedDate: "Survey Date",
},
)
}
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-gradient-to-r from-brandmidblue/10 to-brandlightblue/50 border border-brandblue/20 shadow-sm hover:border-brandblue/40 hover:shadow-md transition-all cursor-pointer"
>
<span className="text-brandmidblue font-bold text-base leading-none">{dealList.length}</span>
<span className="text-xs text-brandblue font-medium">{label}</span>
<span className="text-brandmidblue text-xs leading-none"></span>
</button>
))}
</div>
) : totalCompleted !== null && (
<div className="inline-flex items-center gap-2 self-start px-3 py-1.5 rounded-full bg-gradient-to-r from-brandmidblue/10 to-brandlightblue/50 border border-brandblue/20 shadow-sm">
<span className="text-brandmidblue font-bold text-base leading-none" suppressHydrationWarning>{totalCompleted}</span>
<span className="text-xs text-brandblue font-medium">

View file

@ -5,7 +5,7 @@ import { AlertCircle } from "lucide-react";
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
import type { ClassifiedDeal } from "./types";
const SUCCESSFUL_OUTCOMES = new Set(["Surveyed", "Surveyed - Pending Upload"]);
const SUCCESSFUL_OUTCOMES = new Set(["Surveyed", "Surveyed - Pending Upload", "EPC Completed"]);
const COLUMNS: (keyof ClassifiedDeal)[] = [
"dealname",

View file

@ -53,7 +53,7 @@ function mapDbRowToHubspotDeal(row: DbDeal): HubspotDeal {
measuresLodgementDate: row.measuresLodgementDate,
fullLodgementDate: row.lodgementDate,
confirmedSurveyDate: row.confirmedSurveyDate,
surveyedDate: row.SurveyedDate,
surveyedDate: row.surveyedDate,
designType: row.dealType,
createdAt: row.createdAt,
updatedAt: row.updatedAt,

View file

@ -230,6 +230,7 @@ export type DocumentDrawerState = {
export const SURVEYOR_OUTCOMES = [
"Surveyed",
"Surveyed - Pending Upload",
"EPC Completed",
"Tenant Refusal",
"Other",
"Not Viable",