Set up page for eco spreadsheet

This commit is contained in:
Khalim Conn-Kowlessar 2023-10-13 00:18:29 +08:00
parent 99ed655ec8
commit ba3bdcb7e3
3 changed files with 257 additions and 1 deletions

View file

@ -43,6 +43,7 @@ function Nav({ userImage }: { userImage: string }) {
<div className="ml-10 flex items-baseline space-x-4">
{makeLink("/home", "Home")}
{makeLink("/due-considerations", "Due Considerations")}
{makeLink("/eco-spreadsheet", "Eco Spreadsheet")}
{makeLink("/help", "Help")}
<div className="flex-grow"></div>
</div>

View file

@ -4,7 +4,6 @@ import { useMemo, useState } from "react";
import { SelectFolder } from "../components/due-considerations/SelectFolder";
import { Button } from "../shadcn_components/ui/button";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { Input } from "../shadcn_components/ui/input";

View file

@ -0,0 +1,256 @@
"use client";
import { useMemo, useState } from "react";
import { SelectFolder } from "../components/due-considerations/SelectFolder";
import { Button } from "../shadcn_components/ui/button";
import { useSession } from "next-auth/react";
import { useMutation } from "@tanstack/react-query";
import { Input } from "../shadcn_components/ui/input";
const Spinner = () => {
return (
<div className="w-16 h-16 border-t-4 border-brandgold border-solid rounded-full animate-spin"></div>
);
};
function generateEcoSpreadsheetS3Folder(userId: string) {
const timestamp = new Date().toISOString().replace(/[:.-]/g, "");
const key = `${userId}/${timestamp}/`;
return key;
}
async function postEcoSpreadsheet(userId: string, folderKey: string) {
// Triggers the eco spreadsheet process
const body = JSON.stringify({
userId: userId,
folderKey: folderKey,
});
try {
const response = await fetch(`/api/eco-spreadsheet`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: body,
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
// Handle the response as needed
const data = await response.json();
return data;
} catch (error) {
console.error(error);
// Handle the error appropriately
}
}
const useUploadFiles = ({
files,
userId,
setDownloadUrl,
}: {
files: File[];
userId: string;
setDownloadUrl: React.Dispatch<React.SetStateAction<string>>;
}) => {
const { mutate: mutateUploadFiles, isLoading: isUploadLoading } = useMutation(
uploadFilesToS3,
{
onSuccess: async () => {
console.log("Trigger the eco spreadsheet process");
console.log("Folder key: ", folderKey);
const data = await postEcoSpreadsheet(userId, folderKey);
setDownloadUrl(data.download_url);
},
onError: (error) => {
console.error(error);
},
}
);
const { mutate, isLoading: isGeneratingUrlLoading } = useMutation(
generatePresignedUrls,
{
onSuccess: (data) => {
try {
const response = mutateUploadFiles({
presignedUrls: data.urls,
files: files,
});
return response;
} catch (error) {
console.error(error);
}
},
onError: (error) => {
console.error(error);
},
}
);
const [folderKey, setFolderKey] = useState("");
const handleUpload = (newFolderKey: string) => {
setFolderKey(newFolderKey);
mutate({ folderKey: newFolderKey, files: files });
};
return {
handleUpload,
isGeneratingUrlLoading,
isUploadLoading,
};
};
async function generatePresignedUrls({
folderKey,
files,
}: {
folderKey: string;
files: File[];
}) {
const body = JSON.stringify({
files: files.map((file) => ({
fileKey: folderKey + file.name,
contentType: file.type,
})),
});
const presignedResponse = await fetch("/api/upload/eco-spreadsheet", {
method: "POST",
body: body,
});
if (!presignedResponse.ok) {
throw new Error("Network response was not ok");
}
const presignedUrls = await presignedResponse.json();
return presignedUrls;
}
async function uploadFilesToS3({
presignedUrls,
files,
}: {
presignedUrls: string[];
files: File[];
}) {
await Promise.all(
files.map((file, index) => {
return fetch(presignedUrls[index], {
method: "PUT",
headers: {
"Content-Type": file.type,
},
body: file,
});
})
);
}
export default function EcoSpreadsheetHome() {
const [files, setFiles] = useState<File[]>([]);
const [buttonDisabled, setButtonDisabled] = useState(true);
const [uploadMessage, setUploadMessage] = useState("");
const [downloadUrl, setDownloadUrl] = useState("");
const session = useSession();
const userId = String(session.data?.user.dbId);
const { handleUpload, isGeneratingUrlLoading, isUploadLoading } =
useUploadFiles({
files,
userId,
setDownloadUrl,
});
const initiateUpload = () => {
setDownloadUrl("");
const newFolderKey = generateEcoSpreadsheetS3Folder(userId);
handleUpload(newFolderKey);
};
function handleOnChange(e: React.ChangeEvent<HTMLInputElement>) {
if (e.target.files && e.target.files.length === 3) {
const filesArray = Array.from(e.target.files);
const extensions = filesArray.map((file) =>
file.name.split(".").pop()?.toLowerCase()
);
const names = filesArray.map((file) => file.name.toLowerCase());
if (
extensions.includes("xml") &&
extensions.includes(".pdf") &&
names.includes("epr") &&
names.includes("ventilation")
) {
setFiles(filesArray);
setButtonDisabled(false);
setUploadMessage("");
} else {
setUploadMessage(
"Please select the .xml, the epr and the ventilation and condition report"
);
setButtonDisabled(true);
}
} else {
setUploadMessage("Please select exactly 3 files.");
setButtonDisabled(true);
}
}
return (
<div className="flex flex-col items-center mt-20 tracking-wider leading-loose">
<div className="text-center">
<div className="mb-4">Please select the following files:</div>
<ul className="list-disc list-inside text-left ml-8 mb-8">
<li>An xml</li>
<li>EPR pdf</li>
<li>Ventilation and Condition pdf</li>
</ul>
<div className="mb-4">
Additionally, you can upload an optional ECO spreadsheet if you wish
to add new records to an already populated spreadsheet
</div>
<div className="mb-4">
Make sure these documents all relate to the same property
</div>
<div className="mb-5">
<SelectFolder handleOnChange={handleOnChange} />
</div>
<div className="mb-4"></div>
<div className="flex justify-between w-full">
<div>{uploadMessage}</div>
<Button
disabled={
buttonDisabled || isGeneratingUrlLoading || isUploadLoading
}
className="bg-brandblue hover:bg-hoverblue"
onClick={initiateUpload}
>
{isGeneratingUrlLoading || isUploadLoading
? "Loading..."
: "Upload"}
</Button>
</div>
<div className="flex items-center justify-center">
{isGeneratingUrlLoading || isUploadLoading ? (
<Spinner />
) : downloadUrl ? (
<a href={downloadUrl} className="text-blue-500 hover:underline">
Download Eco Spreadsheet
</a>
) : null}
</div>
</div>
</div>
);
}