mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
uploade files show visually in frontend
This commit is contained in:
parent
f8db2da9e6
commit
0393299936
4 changed files with 133 additions and 25 deletions
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue