Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions src/main/webui/Add_@types.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--- proposalToolSchemas.ts.orig 2025-10-08 10:03:18
+++ proposalToolSchemas.ts 2025-10-21 16:30:20
--- proposalToolSchemas.ts.orig 2025-11-13 16:17:25
+++ proposalToolSchemas.ts 2025-11-13 16:51:35
@@ -56,6 +56,8 @@
* A block of resources that have been allocated
*/
Expand Down Expand Up @@ -183,11 +183,10 @@
* Defines collection of resources and proposals for a particular observing season
*/
export type ProposalCycle = {
- /**
+ _id?: number
/**
- * An organisation that can perform astronomical observations
- */
+ _id?: number
+ /**
+ * An organisation that can perform astronomical observations
+ */
observatory?: Observatory;
Expand Down Expand Up @@ -345,15 +344,3 @@
note?: string;
isAvoidConstraint?: boolean;
/**
@@ -1862,11 +1915,6 @@
/**
* @format binary
*/
-export type UploadLatexResourceSchema = Blob;
-
-/**
- * @format binary
- */
export type UploadTargetList = Blob;

export type UserConsentRepresentation = {
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import AvailableResourcesTable from "./availableResourcesTable.tsx";
import {useProposalToolContext} from "../../generated/proposalToolContext.ts";
import {CLOSE_DELAY, ICON_SIZE, OPEN_DELAY} from "../../constants.tsx";
import {IconMail} from "@tabler/icons-react";
import {HaveRole} from "../../auth/Roles.tsx";


//ASSUMES input string is ISO date-time at GMT+0
Expand Down Expand Up @@ -167,6 +168,7 @@ export default function CycleOverviewPanel() : ReactElement {
{SubmittedProposalsTable(submittedProposals.data ?? [])}
</ScrollArea.Autosize>
<Space h={'xl'}/>
{HaveRole(["tac_admin"]) &&
<Group justify={"center"}>
<Tooltip
label={checkReviewsLocked.data ?
Expand All @@ -190,7 +192,7 @@ export default function CycleOverviewPanel() : ReactElement {
Send TAC Results
</Button>
</Tooltip>
</Group>
</Group>}
</Fieldset>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,158 @@
import {ReactElement, useEffect, useState} from "react";
import {Table, Loader, Modal, TextInput, Stack} from "@mantine/core";
import {ObjectIdentifier} from "../../generated/proposalToolSchemas.ts";
import {
ObjectIdentifier,
SubmittedProposal,
} from "../../generated/proposalToolSchemas.ts";
import {
fetchJustificationsResourceCreateTACAdminPDF,
fetchJustificationsResourceDownloadLatexPdf,
fetchSupportingDocumentResourceDownloadSupportingDocument,
fetchSupportingDocumentResourceGetSupportingDocuments,
useSubmittedProposalResourceGetSubmittedProposal,
useSubmittedProposalResourceReplaceCode
} from "../../generated/proposalToolComponents.ts";
import {useParams} from "react-router-dom";
import EditButton from "../../commonButtons/edit.tsx";
import {useForm} from "@mantine/form";
import {FormSubmitButton} from "../../commonButtons/save.tsx";
import {notifyError, notifySuccess} from "../../commonPanel/notifications.tsx";
import {notifyError, notifyInfo, notifySuccess} from "../../commonPanel/notifications.tsx";
import getErrorMessage from "../../errorHandling/getErrorMessage.tsx";
import {useQueryClient} from "@tanstack/react-query";
import {MAX_CHARS_FOR_INPUTS} from "../../constants.tsx";
import {JSON_FILE_NAME, MAX_CHARS_FOR_INPUTS} from "../../constants.tsx";
import * as JSZip from "jszip";
import {useProposalToolContext} from "../../generated/proposalToolContext.ts";
import {ExportButton} from "../../commonButtons/export.tsx";
import {HaveRole} from "../../auth/Roles.tsx";

/*
We will likely want to add metadata about submitted proposals, the most useful of this being the
submitted proposals current "status" e.g., under-review, reviewed - success/fail, allocated
*/

const populateSupportingDocuments = (
zip: JSZip,
supportingDocumentData: ObjectIdentifier[] | undefined,
proposalCode: number,
authToken: string | undefined
): Array<Promise<void>> => {
if(supportingDocumentData === undefined) {
return [];
}
return supportingDocumentData.map(async (item: ObjectIdentifier) => {
if (item.dbid !== undefined && item.name !== undefined) {
// have to destructure this, as otherwise risk of being undefined
// detected later.
let docTitle = item.name;

// ensure that if the file exists already, that it's renamed to
// avoid issues of overwriting itself in the zip.
while (zip.files[docTitle]) {
docTitle = docTitle + "1"
}

// extract the document and save into the zip.
await fetchSupportingDocumentResourceDownloadSupportingDocument({
headers: {authorization: `${authToken}`},
pathParams: {
proposalCode: proposalCode,
id: item.dbid
}
})
.then((blob) => {
// ensure we got some data back.
if (blob !== undefined) {
zip.file(docTitle, blob)
}
})
.catch((err) => {
notifyError("Download supporting documents", getErrorMessage(err));});
}
});
}

function prepareToDownloadProposal(
fullProposal: SubmittedProposal,
authToken: string | undefined,
): void {
if (fullProposal !== undefined && fullProposal._id !== undefined) {
fetchJustificationsResourceCreateTACAdminPDF({
pathParams: {proposalCode: fullProposal._id},
headers: {authorization: `${authToken}`},
})
.then(() => {
//Pdf should now be generated, next get the supporting documents
fetchSupportingDocumentResourceGetSupportingDocuments({
pathParams: {proposalCode: fullProposal._id!},
headers: {authorization: `${authToken}`},
})
.then(documentList => {
// can go for a download of everything
downloadProposal(fullProposal, documentList, authToken)
})
.catch(e => {notifyError("Download Error", getErrorMessage(e));});
})
.catch((e) => {notifyError("Unable to compile pdf", getErrorMessage(e))});
}
else {
notifyError("Download Error", "Submitted proposal is empty!");
}
}

function downloadProposal(
submittedProposal: SubmittedProposal,
supportingDocuments: ObjectIdentifier[] | undefined,
authToken: string | undefined,
): void {

notifyInfo("Submitted Proposal Export Started",
"An export has started and the download will begin shortly");

// build the zip object and populate with the corresponding documents.
let zip = new JSZip();

// Fudge in the .json file
zip = zip.file(JSON_FILE_NAME, JSON.stringify(submittedProposal,null, 2));

// add supporting documents to the zip.
const promises = populateSupportingDocuments(
zip, supportingDocuments, submittedProposal._id!, authToken,
);

promises.push(
fetchJustificationsResourceDownloadLatexPdf({
pathParams: {proposalCode: submittedProposal._id!},
headers: {authorization: `${authToken}`},
}).then((blob) => {
if (blob !== undefined) {
zip.file(`${submittedProposal.proposalCode} ${submittedProposal.title?.replace(/\s/g, "").substring(0, 30)}.pdf`, blob)
}
})
)

// ensure all supporting docs populated before making zip.
Promise.all(promises).then(
() => {
// generate the zip file.
zip.generateAsync({type: "blob"})
.then((zipData: Blob | MediaSource) => {
// Create a download link for the zip file
const link = document.createElement("a");
link.href = window.URL.createObjectURL(zipData);
link.download=submittedProposal.proposalCode + " " + submittedProposal.title?.replace(/\s/g,"").substring(0,30)+".zip";
link.click();
})
.then(()=>
notifySuccess("Proposal Export Complete", "proposal exported and downloaded")
)
.catch((error:Error) =>
notifyError("Proposal Export Failed", getErrorMessage(error))
)
}
)
}


type SubmittedTableRowProps = {
cycleCode: number,
submittedProposalId: number,
Expand All @@ -27,13 +161,15 @@ type SubmittedTableRowProps = {

function SubmittedProposalTableRow(rowProps: SubmittedTableRowProps) : ReactElement {
const codeMutation = useSubmittedProposalResourceReplaceCode();
const {fetcherOptions} = useProposalToolContext();

const submittedProposal =
useSubmittedProposalResourceGetSubmittedProposal({
pathParams: {
cycleCode: rowProps.cycleCode,
submittedProposalId: rowProps.submittedProposalId
}
},
headers: fetcherOptions.headers,
})

const [reviewsCompleteAndLocked, setReviewsCompleteAndLocked] = useState(false)
Expand Down Expand Up @@ -130,13 +266,21 @@ function SubmittedProposalTableRow(rowProps: SubmittedTableRowProps) : ReactElem
</form>
</Modal>
<Table.Td>
<EditButton
{HaveRole(["tac_admin"]) ? (<EditButton
toolTipLabel={'Change proposal code'}
label={submittedProposal.data?.proposalCode}
onClick={() => setEditModalOpen(true)}
/>
/>) : (submittedProposal.data?.proposalCode) }
</Table.Td>
<Table.Td>{submittedProposal.data?.title}</Table.Td>
{HaveRole(["tac_admin"]) && (<Table.Td>
<ExportButton
onClick={() => prepareToDownloadProposal(submittedProposal.data!, fetcherOptions.headers?.authorization)}
toolTipLabel={"Download a zip file of the PDF proposal and it's supporting documents"}
label={"Zip"}
>
</ExportButton>
</Table.Td>)}
<Table.Td c={proposalAccepted ? "green" : reviewsCompleteAndLocked ? "red" : "blue"}>
{
proposalAccepted ? "accepted" :
Expand All @@ -159,7 +303,8 @@ function SubmittedProposalsTable(submittedProposals: ObjectIdentifier[]) : React
<Table.Thead>
<Table.Tr>
<Table.Th>Code</Table.Th>
<Table.Th>Proposal Title</Table.Th>
<Table.Th>Title</Table.Th>
{HaveRole(["tac_admin"]) && (<Table.Th>Download</Table.Th>)}
<Table.Th>Current Status</Table.Th>
</Table.Tr>
</Table.Thead>
Expand Down
49 changes: 48 additions & 1 deletion src/main/webui/src/ProposalManagerView/reviews/reviews.form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import {Button, Grid, Group, NumberInput, Space, Stack, Switch, Text, Textarea,
import AddButton from "../../commonButtons/add.tsx";
import {ReviewsProps} from "./ReviewsPanel.tsx";
import {
fetchJustificationsResourceCreateReviewPDF,
fetchJustificationsResourceDownloadLatexPdf,
fetchProposalReviewResourceUpdateReviewComment,
fetchProposalReviewResourceUpdateReviewFeasibility,
fetchProposalReviewResourceUpdateReviewScore,
useProposalReviewResourceAddReview,
useProposalReviewResourceConfirmReviewComplete,
useReviewerResourceGetReviewer
} from "../../generated/proposalToolComponents.ts";
import {notifyError, notifySuccess} from "../../commonPanel/notifications.tsx";
import {notifyError, notifyInfo, notifySuccess} from "../../commonPanel/notifications.tsx";
import getErrorMessage from "../../errorHandling/getErrorMessage.tsx";
import {useQueryClient} from "@tanstack/react-query";
import {useForm} from "@mantine/form";
Expand All @@ -25,6 +27,7 @@ import {IconSquareRoundedCheck} from "@tabler/icons-react";
import {modals} from "@mantine/modals";
import {useToken} from "../../App2.tsx";
import {useMediaQuery} from "@mantine/hooks";
import {ExportButton} from "../../commonButtons/export.tsx";

export default
function ReviewsForm(props: ReviewsProps) : ReactElement {
Expand Down Expand Up @@ -296,12 +299,56 @@ function ReviewsForm(props: ReviewsProps) : ReactElement {
})
}

function downloadReviewPDF() {
fetchJustificationsResourceDownloadLatexPdf({
pathParams: {proposalCode: props.proposal?._id!},
headers: {authorization: `Bearer ${authToken}`}
})
.then((reviewPDF) => {
if (reviewPDF) {
const link = document.createElement("a");
link.href = window.URL.createObjectURL(reviewPDF);
link.download = props.proposal?.proposalCode + " "
+ props.proposal?.title?.replace(/\s/g, "")
.substring(0,30)
+ ".pdf";
link.click();
} else
notifyError("Failed to download review", "The file is empty")
})
.then(() => notifySuccess("Download review", "The PDF has downloaded"))
.catch(error => notifyError("Failed to download review", getErrorMessage(error)))
}

function prepareToDownloadReview() {
if(props.proposal?._id !== undefined) {
notifyInfo("Download review", "PDF compile has begun, please wait");
fetchJustificationsResourceCreateReviewPDF(
{
pathParams: {proposalCode: props.proposal._id},
headers: {authorization: `Bearer ${authToken}`}
})
.then(() => downloadReviewPDF())
.catch(error =>
notifyError("Failed to compile justifications pdf",
getErrorMessage(error)))
} else {
notifyError("Download review", "Unable to identify submitted proposal")
}

}

return (
<>
{theReview ?
<form onSubmit={form.onSubmit(handleSubmit)}>
<Grid columns={10} gutter={"xl"}>
<Grid.Col span={{base: 10, lg: 6}}>
<ExportButton
onClick={() => prepareToDownloadReview()}
toolTipLabel={"Download a PDF of this proposal"}
label={"Download pdf"}
/>
{commentInput()}
</Grid.Col>
<Grid.Col span={{base: 10, lg: 4}}>
Expand Down
Loading
Loading