uploade files show visually in frontend

This commit is contained in:
Jun-te kim 2025-08-21 14:35:27 +00:00
parent f8db2da9e6
commit 0393299936
4 changed files with 133 additions and 25 deletions

View file

@ -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(),
});
});
export type getUploadedFile = typeof uploaded_files.$inferSelect
export type getUploadedFiles = getUploadedFile[];

View file

@ -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<Props> = ({ reportType, uprn }) => {
const [showUploadModal, setShowUploadModal] = useState(false);
export const DocumentSection: React.FC<Props> = ({ reportType, uprn, files }) => {
const [showUploadModal, setShowUploadModal] = React.useState(false);
const router = useRouter();
const latestFile = React.useMemo<getUploadedFile | null>(() => {
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<Props> = ({ reportType, uprn }) => {
{title}
</TableCell>
<TableCell className="px-6 py-4 text-sm text-gray-500" />
<TableCell className="px-6 py-4 text-sm text-gray-500">
{latestFile ? (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-3">
<a
href={latestFile.s3FileUri}
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:no-underline"
>
View latest file
</a>
<span className="text-xs text-gray-400">
uploaded {formatWhen(latestFile.s3FileUploadTimestamp)}
</span>
</div>
<span className="text-xs text-gray-500">
{count} file{count !== 1 && "s"} on record
</span>
</div>
) : (
<span className="text-gray-400">No files uploaded yet</span>
)}
</TableCell>
<TableCell className="px-6 py-4 text-sm text-right w-1/6">
<BrandButton
@ -31,11 +76,13 @@ export const DocumentSection: React.FC<Props> = ({ reportType, uprn }) => {
onClick={() => setShowUploadModal(true)}
backgroundColor="brandblue"
/>
<UploadModal
open={showUploadModal}
onClose={() => setShowUploadModal(false)}
documentType={reportType} // <- strong ReportType
onClose={() => {
setShowUploadModal(false);
router.refresh();
}}
documentType={reportType}
uprn={uprn}
/>
</TableCell>

View file

@ -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<Props> = ({ uprn, uploadedFilesData }) => {
const filesByType = React.useMemo(() => {
const map: Partial<Record<ReportType, getUploadedFile[]>> = {};
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<Props> = ({ uprn }) => {
return (
<Table className="min-w-full table-fixed divide-y divide-gray-200 shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<TableBody className="bg-white divide-y divide-gray-200">
{REPORT_TYPES.map((rt) => (
<React.Fragment key={rt}>
<DocumentSection
reportType={rt as ReportType}
uprn={uprn}
/>
<TableRow className="hover:bg-transparent">
<TableCell colSpan={3} className="h-3 p-0" />
</TableRow>
</React.Fragment>
))}
{REPORT_TYPES.map((reportType) => {
const filesForType = filesByType[reportType] ?? [];
return (
<React.Fragment key={reportType}>
<DocumentSection
reportType={reportType}
uprn={uprn}
files={filesForType} // array of rows
/>
<TableRow className="hover:bg-transparent">
<TableCell colSpan={3} className="h-3 p-0" />
</TableRow>
</React.Fragment>
);
})}
</TableBody>
</Table>
);
};
};

View file

@ -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<getUploadedFiles> {
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(
<div className="py-4">
<DocumentsTable
uprn={propertyMeta.uprn.toString()}
uploadedFilesData={uploadedFiles}
/>
</div>