From 2b02e3714bed14941de5548bd6df2e9ea0c50651 Mon Sep 17 00:00:00 2001 From: Jun-te kim Date: Thu, 21 Aug 2025 11:30:23 +0100 Subject: [PATCH 1/4] save --- cypress/support/component.ts | 2 +- drizzle.config.ts | 1 + src/app/components/Navbar.tsx | 2 +- .../components/portfolio/summary/SelectComparisonModal.tsx | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cypress/support/component.ts b/cypress/support/component.ts index 37f59edb..bd8ff018 100644 --- a/cypress/support/component.ts +++ b/cypress/support/component.ts @@ -19,7 +19,7 @@ import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') -import { mount } from 'cypress/react18' +import { mount } from 'cypress/react' // Augment the Cypress namespace to include type definitions for // your custom command. diff --git a/drizzle.config.ts b/drizzle.config.ts index d0f4098d..dfba60b8 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -3,4 +3,5 @@ import type { Config } from "drizzle-kit"; export default { schema: "./src/app/db/schema/*", out: "./src/app/db/migrations", + dialect: "postgresql", } satisfies Config; diff --git a/src/app/components/Navbar.tsx b/src/app/components/Navbar.tsx index 746bc404..e01038fc 100644 --- a/src/app/components/Navbar.tsx +++ b/src/app/components/Navbar.tsx @@ -108,7 +108,7 @@ function Nav({ userImage }: { userImage: string }) { > {(ref) => (
-
+
} className="px-2 pt-2 pb-3 space-y-1 sm:px-3"> - + {/* This element is to trick the browser into centering the modal contents. */} From f8db2da9e65f04b481e3d22ce6f2c5d8896a4d2e Mon Sep 17 00:00:00 2001 From: Jun-te kim Date: Thu, 21 Aug 2025 11:30:51 +0000 Subject: [PATCH 2/4] addded changes to let build work --- run_build.sh | 1 + src/app/db/schema/solar.ts | 2 +- src/app/db/surveyDB/utils/utility.ts | 5 ++-- .../components/RemoteAssessmentDropdowns.tsx | 2 +- .../components/RemoteAssessmentModal.tsx | 28 ++++++++++++------- .../[slug]/components/UploadCsvModal.tsx | 24 ++++++++++------ src/app/shadcn_components/ui/dialog.tsx | 13 +++++++-- 7 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 run_build.sh diff --git a/run_build.sh b/run_build.sh new file mode 100644 index 00000000..d6cb2884 --- /dev/null +++ b/run_build.sh @@ -0,0 +1 @@ +npm run build diff --git a/src/app/db/schema/solar.ts b/src/app/db/schema/solar.ts index ea7ca22e..94088a41 100644 --- a/src/app/db/schema/solar.ts +++ b/src/app/db/schema/solar.ts @@ -10,7 +10,7 @@ import { boolean, } from "drizzle-orm/pg-core"; import { InferModel } from "drizzle-orm"; -import { C } from "drizzle-orm/db.d-cf0abe10"; +// import { C } from "drizzle-orm/db.d-cf0abe10"; export const solar = pgTable("solar", { id: bigserial("id", { mode: "bigint" }).primaryKey(), diff --git a/src/app/db/surveyDB/utils/utility.ts b/src/app/db/surveyDB/utils/utility.ts index 5fbd0c8b..adeeaf00 100644 --- a/src/app/db/surveyDB/utils/utility.ts +++ b/src/app/db/surveyDB/utils/utility.ts @@ -1,8 +1,9 @@ // insertUploadedFile.ts -import { uploaded_files } from "@/app/db/surveyDB/schema/surveyDB"; +import { uploaded_files, docTypeEnum } from "@/app/db/surveyDB/schema/surveyDB"; import { surveyDB } from "../connection"; import type { ReportType, ReportTypeSchema} from "../schema/documents"; import { reportTypeToDbLabel } from "../schema/documents"; +type DbDocType = (typeof docTypeEnum.enumValues)[number]; export interface UploadedFileInput { s3JsonUri?: string; // optional @@ -19,7 +20,7 @@ export async function insertUploadedFile(data: UploadedFileInput) { .values({ s3JsonUri: data.s3JsonUri ?? null, // Pass null if missing s3FileUri: data.s3FileUri, - docType: reportTypeToDbLabel[data.docType], // map UI value -> DB enum NAME + docType: reportTypeToDbLabel[data.docType] as DbDocType, // map UI value -> DB enum NAME s3FileUploadTimestamp: data.s3FileUploadTimestamp, s3JsonUploadTimestamp: data.s3JsonUploadTimestamp ?? null, // Pass null if missing uprn: data.uprn, diff --git a/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx b/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx index 1da5392f..706e2720 100644 --- a/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx +++ b/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx @@ -2,7 +2,7 @@ import { Menu, Transition } from "@headlessui/react"; import { Fragment } from "react"; import { Button } from "@/app/shadcn_components/ui/button"; import { PlusIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; -import { Float } from "@headlessui-float/react"; +// import { Float } from "@headlessui-float/react"; export type Option = { label: string; value: string; disabled?: boolean }; diff --git a/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx b/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx index e838e087..b3ea2a98 100644 --- a/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx +++ b/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx @@ -1,6 +1,14 @@ "use client"; -import { Dialog, Transition } from "@headlessui/react"; +import { + Dialog, + DialogBackdrop, + DialogPanel, + DialogTitle, + Transition, + TransitionChild, +} from "@headlessui/react"; + import { Fragment, useMemo } from "react"; import { Input } from "@/app/shadcn_components/ui/input"; import { Button } from "@/app/shadcn_components/ui/button"; @@ -542,7 +550,7 @@ export default function RemoteAssessmentModal({ onClose={() => setIsOpen(false)} >
- - - + + {/* Spacer for centering */} - -
- + + Remote Assessment Details - +
@@ -929,8 +937,8 @@ export default function RemoteAssessmentModal({ )}
-
-
+ +
diff --git a/src/app/portfolio/[slug]/components/UploadCsvModal.tsx b/src/app/portfolio/[slug]/components/UploadCsvModal.tsx index 5033b3cb..f2093121 100644 --- a/src/app/portfolio/[slug]/components/UploadCsvModal.tsx +++ b/src/app/portfolio/[slug]/components/UploadCsvModal.tsx @@ -1,6 +1,14 @@ "use client"; -import { Dialog, Transition } from "@headlessui/react"; +import { + Dialog, + DialogBackdrop, + DialogPanel, + DialogTitle, + Transition, + TransitionChild, +} from "@headlessui/react"; + import { Fragment, useMemo, useState } from "react"; import { useMutation } from "@tanstack/react-query"; import { InputFile } from "@/app/portfolio/[slug]/components/InputFile"; @@ -427,7 +435,7 @@ export default function UploadCsvModal({ onClose={() => setIsOpen(false)} >
- - - + + -
- + Upload Property Data - +
-
+
diff --git a/src/app/shadcn_components/ui/dialog.tsx b/src/app/shadcn_components/ui/dialog.tsx index 7ac0d44b..abaf2568 100644 --- a/src/app/shadcn_components/ui/dialog.tsx +++ b/src/app/shadcn_components/ui/dialog.tsx @@ -14,9 +14,16 @@ const DialogPortal = ({ className, children, ...props -}: DialogPrimitive.DialogPortalProps) => ( - -
+}: React.ComponentPropsWithoutRef & { + className?: string +}) => ( + +
{children}
From 0393299936b33fcc12712270741f4fd3d2ba0821 Mon Sep 17 00:00:00 2001 From: Jun-te kim Date: Thu, 21 Aug 2025 14:35:27 +0000 Subject: [PATCH 3/4] uploade files show visually in frontend --- src/app/db/surveyDB/schema/surveyDB.ts | 7 +- .../documents/DocumentSection.tsx | 65 ++++++++++++++++--- .../[propertyId]/documents/DocumentsTable.tsx | 64 +++++++++++++----- .../[propertyId]/documents/page.tsx | 22 +++++++ 4 files changed, 133 insertions(+), 25 deletions(-) diff --git a/src/app/db/surveyDB/schema/surveyDB.ts b/src/app/db/surveyDB/schema/surveyDB.ts index 1c16a7ab..f8b89da4 100644 --- a/src/app/db/surveyDB/schema/surveyDB.ts +++ b/src/app/db/surveyDB/schema/surveyDB.ts @@ -1,5 +1,6 @@ import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core"; import { pgEnum } from "drizzle-orm/pg-core"; + export const DB_REPORT_TYPES = [ "ECO_CONDITION_REPORT", @@ -23,4 +24,8 @@ export const uploaded_files = pgTable("uploaded_files", { s3JsonUploadTimestamp: timestamp("s3_json_upload_timestamp", { withTimezone: true }), uprn: text("uprn").notNull(), -}); \ No newline at end of file +}); + +export type getUploadedFile = typeof uploaded_files.$inferSelect + +export type getUploadedFiles = getUploadedFile[]; diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx index 3cf21d85..7d8a1e45 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx @@ -1,20 +1,42 @@ "use client"; - -import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import React from "react"; import { TableCell, TableRow } from "@/app/shadcn_components/ui/table"; import { BrandButton } from "@/app/components/Buttons"; import { UploadModal } from "./UploadModal"; import { documentTypeTitles, type ReportType } from "@/app/db/surveyDB/schema/documents"; +import type { getUploadedFiles, getUploadedFile } from "@/app/db/surveyDB/schema/surveyDB"; type Props = { - reportType: ReportType; // <- the only type selector needed + reportType: ReportType; uprn: string; + files: getUploadedFiles; }; -export const DocumentSection: React.FC = ({ reportType, uprn }) => { - const [showUploadModal, setShowUploadModal] = useState(false); +export const DocumentSection: React.FC = ({ reportType, uprn, files }) => { + const [showUploadModal, setShowUploadModal] = React.useState(false); + const router = useRouter(); + + const latestFile = React.useMemo(() => { + if (!files?.length) return null; + return files.reduce((acc, cur) => { + const accTime = new Date(acc.s3FileUploadTimestamp as any).getTime(); + const curTime = new Date(cur.s3FileUploadTimestamp as any).getTime(); + return curTime > accTime ? cur : acc; + }, files[0]); + }, [files]); + + const formatWhen = (d: string | Date) => + new Intl.DateTimeFormat(undefined, { + year: "numeric", + month: "short", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(d)); const title = documentTypeTitles[reportType]; + const count = files.length; return ( <> @@ -23,7 +45,30 @@ export const DocumentSection: React.FC = ({ reportType, uprn }) => { {title} - + + {latestFile ? ( +
+
+ + View latest file + + + uploaded {formatWhen(latestFile.s3FileUploadTimestamp)} + +
+ + {count} file{count !== 1 && "s"} on record + +
+ ) : ( + No files uploaded yet + )} + = ({ reportType, uprn }) => { onClick={() => setShowUploadModal(true)} backgroundColor="brandblue" /> - setShowUploadModal(false)} - documentType={reportType} // <- strong ReportType + onClose={() => { + setShowUploadModal(false); + router.refresh(); + }} + documentType={reportType} uprn={uprn} /> diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx index 9661527d..d81acab3 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx @@ -2,26 +2,60 @@ import React from "react"; import { Table, TableBody, TableRow, TableCell } from "@/app/shadcn_components/ui/table"; import { DocumentSection } from "./DocumentSection"; -import { type ReportType, REPORT_TYPES, documentTypeFileTypes, documentTypeTitles } from "@/app/db/surveyDB/schema/documents"; +import { + type ReportType, + REPORT_TYPES, + dbLabelToReportType, // <-- import the map +} from "@/app/db/surveyDB/schema/documents"; +import type { getUploadedFile } from "@/app/db/surveyDB/schema/surveyDB"; -type Props = { uprn: string }; +type Props = { + uprn: string; + uploadedFilesData: getUploadedFile[]; +}; + +export const DocumentsTable: React.FC = ({ uprn, uploadedFilesData }) => { + const filesByType = React.useMemo(() => { + const map: Partial> = {}; + + for (const file of uploadedFilesData ?? []) { + const uiKey = dbLabelToReportType[file.docType]; // map DB → UI + if (!uiKey) continue; // unknown/legacy type? skip safely + + (map[uiKey] ??= []).push(file); + } + + // newest first within each group + Object.values(map).forEach(arr => + arr!.sort( + (a, b) => + new Date(b.s3FileUploadTimestamp as any).getTime() - + new Date(a.s3FileUploadTimestamp as any).getTime() + ) + ); + + return map; + }, [uploadedFilesData]); -export const DocumentsTable: React.FC = ({ uprn }) => { return ( - {REPORT_TYPES.map((rt) => ( - - - - - - - ))} + {REPORT_TYPES.map((reportType) => { + const filesForType = filesByType[reportType] ?? []; + return ( + + + + + + + ); + })}
); -}; \ No newline at end of file +}; diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx index d7698701..df910e07 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx @@ -1,8 +1,28 @@ import { getPropertyMeta } from "@/app/portfolio/[slug]/building-passport/[propertyId]/utils"; 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 { type getUploadedFiles } from "@/app/db/surveyDB/schema/surveyDB"; +import { EmptyObject } from "react-hook-form"; +export async function fetchDocuments(uprn: number): Promise { + return surveyDB.query.uploaded_files.findMany({ + where: eq(uploaded_files.uprn, String(uprn)), + }); +} + +async function getDocuments( + uprn: number +): Promise< getUploadedFiles> { + const result = surveyDB.query.uploaded_files.findMany({ + where: eq(uploaded_files.uprn, String(uprn)), + }); + + return result; +} + export default async function DocumentsPage( props: { params: Promise<{ slug: string; propertyId: string }>; @@ -16,6 +36,7 @@ export default async function DocumentsPage( } const propertyMeta = await getPropertyMeta(propertyId); + const uploadedFiles = await getDocuments(propertyMeta.uprn); return ( <> @@ -26,6 +47,7 @@ export default async function DocumentsPage(
From 0a93f0b743a0065dd5d897f5b41bf67304a96977 Mon Sep 17 00:00:00 2001 From: Jun-te kim Date: Thu, 21 Aug 2025 14:57:25 +0000 Subject: [PATCH 4/4] addded page.tsx --- .../building-passport/[propertyId]/documents/page.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx index df910e07..f860078b 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx @@ -7,12 +7,6 @@ import { type getUploadedFiles } from "@/app/db/surveyDB/schema/surveyDB"; import { EmptyObject } from "react-hook-form"; -export async function fetchDocuments(uprn: number): Promise { - return surveyDB.query.uploaded_files.findMany({ - where: eq(uploaded_files.uprn, String(uprn)), - }); -} - async function getDocuments( uprn: number ): Promise< getUploadedFiles> {