Skip to content

Commit

Permalink
add 'delete proposal' button to the Overview Panel, add an "export" c…
Browse files Browse the repository at this point in the history
…ommon button, some layout and style changes, include hack to rerender the app on proposal deletion
  • Loading branch information
DJWalker42 committed Nov 26, 2024
1 parent 1038ccb commit 597d6cc
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 78 deletions.
28 changes: 18 additions & 10 deletions src/main/webui/src/App2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
useContext,
ReactElement,
SyntheticEvent,
Context, StrictMode
Context, StrictMode, useReducer
} from 'react';
import {
QueryClient,
Expand Down Expand Up @@ -134,13 +134,17 @@ function App2(): ReactElement {

const GRAY = theme.colors.gray[6];

// hack to force the left-hand navbar to update after proposal deletion in Overview panel
// more precisely, when called, "forceUpdate" will make the entire App rerender - use sparingly!!
const [,forceUpdate] = useReducer(x => x + 1, 0);

// the paths to route to.
const router = createBrowserRouter(
[
{
path: "/manager",
element: <PSTManager />,
errorElement: <ErrorPage />,
errorElement: <ErrorPage />,
children: [
{index: true, element: <PSTManagerStart />},
{
Expand Down Expand Up @@ -203,7 +207,7 @@ function App2(): ReactElement {
{
path: "/",
element: <PSTEditor/>,
errorElement: <ErrorPage />,
errorElement: <ErrorPage />,
children: [
{index: true, element: <PSTStart/>} ,
{
Expand All @@ -218,7 +222,8 @@ function App2(): ReactElement {
},
{
path: "proposal/:selectedProposalCode",
element: <OverviewPanel />,
//'forceUpdate' is called following a proposal deletion in Overview panel
element: <OverviewPanel forceUpdate={forceUpdate}/>,
errorElement: <ErrorPage />,
},
{
Expand Down Expand Up @@ -429,15 +434,18 @@ function App2(): ReactElement {
</Container>

<AddButton toolTipLabel={"new proposal"}
label={"Create a new proposal"}
label={"Create new proposal"}
onClickEvent={handleAddNew}/>
<FileButton onChange={handleUploadZip}
accept={".zip"}>
{(props) => <UploadButton
toolTipLabel="select a file from disk to upload"
label={"Import"}
onClick={props.onClick}/>}
</FileButton>
{(props) =>
<UploadButton
toolTipLabel="select a file from disk to upload"
label={"Import existing proposal"}
onClick={props.onClick}
/>
}
</FileButton>
</AppShell.Section>
<AppShell.Section component={ScrollArea}>
<ProposalListWrapper
Expand Down
183 changes: 125 additions & 58 deletions src/main/webui/src/ProposalEditorView/proposal/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useParams } from 'react-router-dom'
import {useNavigate, useParams} from 'react-router-dom'
import {
fetchProposalResourceDeleteObservingProposal,
useProposalResourceGetObservingProposal,
useSupportingDocumentResourceGetSupportingDocuments,
} from 'src/generated/proposalToolComponents';
import {
Accordion,
Avatar,
Box,
Container,
Container, Fieldset,
Group,
List,
List, Stack,
Table,
Text
} from '@mantine/core';
Expand All @@ -21,13 +22,18 @@ import {
} from 'src/generated/proposalToolSchemas.ts';
import { IconNorthStar } from '@tabler/icons-react';
import { ReactElement, useRef } from 'react';
import { SaveButton } from 'src/commonButtons/save.tsx';
import downloadProposal from './downloadProposal.tsx';
import { DIMMED_FONT_WEIGHT, JSON_SPACES } from 'src/constants.tsx';
import { TargetTable } from '../targets/TargetTable.tsx';
import { TechnicalGoalsTable } from '../technicalGoals/technicalGoalTable.tsx';
import {PreviewJustification} from "../justifications/justification.preview.tsx";
import {ContextualHelpButton} from "../../commonButtons/contextualHelp.tsx"
import {notifyError, notifySuccess} from "../../commonPanel/notifications.tsx";
import getErrorMessage from "../../errorHandling/getErrorMessage.tsx";
import DeleteButton from "../../commonButtons/delete.tsx";
import {PanelFrame, PanelHeader} from "../../commonPanel/appearance.tsx";
import {ExportButton} from "../../commonButtons/export.tsx";
import {modals} from "@mantine/modals";

/*
title -- string
Expand Down Expand Up @@ -219,9 +225,11 @@ function ObservationAccordionContent(
* @return {ReactElement} the html of the overview panel.
* @constructor
*/
function OverviewPanel(): ReactElement {
function OverviewPanel(props: {forceUpdate: () => void}): ReactElement {
const { selectedProposalCode } = useParams();

const navigate = useNavigate();

const {data} =
useSupportingDocumentResourceGetSupportingDocuments(
{pathParams: {proposalCode: Number(selectedProposalCode)},},
Expand All @@ -245,36 +253,6 @@ function OverviewPanel(): ReactElement {
);
}

/**
* generates the overview pdf and saves it to the users disk.
*
* code extracted from: https://www.robinwieruch.de/react-component-to-pdf/
* @return {Promise<void>} promise that the pdf will be saved at some point.
*/
const handleDownloadPdf = (): void => {
// get the overview page to print as well as the proposal data.
const element = printRef.current;

// ensure there is a rendered overview.
if(element !== null && proposalsData !== undefined &&
selectedProposalCode !== undefined && data !== undefined) {
downloadProposal(
element, proposalsData, data, selectedProposalCode).then();
} else {
// something failed in the rendering of the overview react element or
// extracting the proposal data.
if (element === null) {
console.error(
'Tried to download a Overview that had not formed ' +
'correctly.');
} else {
console.error(
'Tried to download the proposal data and that had not ' +
'formed correctly.');
}
}
};

/**
* handles the title display panel.
* @return {ReactElement} the html for the title display panel.
Expand Down Expand Up @@ -534,48 +512,137 @@ function OverviewPanel(): ReactElement {
)
}


/**
* generates the overview pdf and saves it to the users disk.
*
* code extracted from: https://www.robinwieruch.de/react-component-to-pdf/
* @return {Promise<void>} promise that the pdf will be saved at some point.
*/
const handleDownloadPdf = (): void => {
// get the overview page to print as well as the proposal data.
const element = printRef.current;

// ensure there is a rendered overview.
if(element !== null && proposalsData !== undefined &&
selectedProposalCode !== undefined && data !== undefined) {
downloadProposal(
element, proposalsData, data, selectedProposalCode).then();
} else {
// something failed in the rendering of the overview react element or
// extracting the proposal data.
if (element === null) {
console.error(
'Tried to download a Overview that had not formed ' +
'correctly.');
} else {
console.error(
'Tried to download the proposal data and that had not ' +
'formed correctly.');
}
}
};


/**
* add download button for the proposal to be extracted as a tar ball.
*
* @return {ReactElement} the html which contains the download button.
* @constructor
*/
const DownloadButton = (): ReactElement => {
return SaveButton(
const ExportProposal = (): ReactElement => {
return ExportButton(
{
toolTipLabel: `Export to a file for download`,
disabled: false,
onClick: handleDownloadPdf,
label: "Export",
label: "Export Proposal",
variant: "filled",
toolTipLabelPosition: "top"
});
}

const DeleteProposal = () : ReactElement => {
return DeleteButton(
{
toolTipLabel: "Removes this proposal permanently",
disabled: false,
onClick: confirmDeleteProposal,
label: "Delete Proposal",
variant: "outline"
}
)
}


const confirmDeleteProposal = () : void => {
modals.openConfirmModal({
title: "Confirm Proposal Deletion",
centered: true,
children: (
<Stack>
<Text size={"sm"}>
Are you sure you want to permanently remove the proposal '{proposalsData?.title!}'?
</Text>
<Text size={"sm"} c={"yellow.7"}>
This action cannot be undone.
</Text>
</Stack>

),
labels: {confirm: "Yes, delete this proposal", cancel: "No, do not delete!"},
confirmProps: {color: "red"},
onConfirm: () => handleDeleteProposal()
})
}


const handleDeleteProposal = () => {
fetchProposalResourceDeleteObservingProposal({
pathParams: {proposalCode: Number(selectedProposalCode)}
})
.then(()=> notifySuccess(
"Deletion successful",
"Proposal: '" + proposalsData?.title! + "' has been removed"))
.then(()=> navigate("/"))
.then(() => props.forceUpdate())
.catch(error => notifyError("Deletion failed", getErrorMessage(error)))
}


/**
* returns the HTML structure for the overview page.
*/
return (
<>
{
proposalsIsLoading ? 'Loading...' :
<Container fluid>
<DownloadButton/>
<div ref={printRef}>
<DisplayTitle/>
<ContextualHelpButton messageId="Overview" />
<DisplayInvestigators/>
<DisplaySummary/>
<DisplayKind/>
<DisplayScientificJustification/>
<DisplayTechnicalJustification/>
<DisplayObservations/>
<DisplaySupportingDocuments/>
<DisplayRelatedProposals/>
</div>
</Container>
}
</>
<PanelFrame>
<PanelHeader
itemName={proposalsData?.title!}
panelHeading={"Overview"}
isLoading={proposalsIsLoading}
/>
<Container fluid>
<ContextualHelpButton messageId="Overview" />
<Fieldset legend={"Proposal Services"}>
<Group grow>
<ExportProposal/>
<DeleteProposal/>
</Group>
</Fieldset>
<Fieldset legend={"Proposal Overview"}>
<div ref={printRef}>
<DisplayTitle/>
<DisplayInvestigators/>
<DisplaySummary/>
<DisplayKind/>
<DisplayScientificJustification/>
<DisplayTechnicalJustification/>
<DisplayObservations/>
<DisplaySupportingDocuments/>
<DisplayRelatedProposals/>
</div>
</Fieldset>
</Container>
</PanelFrame>
);

}
Expand Down
2 changes: 1 addition & 1 deletion src/main/webui/src/commonButtons/delete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function DeleteButton(props: ClickButtonInterfaceProps): ReactElement {
>
<Button
rightSection={<IconTrash size={ICON_SIZE}/>}
color={"red.5"}
color={"red.7"}
variant={props.variant ?? "subtle"}
onClick={props.onClick ?? props.onClickEvent}
disabled={props.disabled}
Expand Down
25 changes: 25 additions & 0 deletions src/main/webui/src/commonButtons/export.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {ClickButtonInterfaceProps} from "./buttonInterfaceProps.tsx";
import {ReactElement} from "react";
import {Button, Tooltip} from "@mantine/core";
import {CLOSE_DELAY, ICON_SIZE, OPEN_DELAY} from "../constants.tsx";
import {IconDownload} from "@tabler/icons-react";

export
function ExportButton(props: ClickButtonInterfaceProps) : ReactElement {
return (
<Tooltip position={props.toolTipLabelPosition ?? "left"}
label={props.toolTipLabel}
openDelay={OPEN_DELAY}
closeDelay={CLOSE_DELAY}
>
<Button
rightSection={<IconDownload size={ICON_SIZE}/>}
color={"blue.7"}
variant={props.variant ?? "subtle"}
onClick={props.onClick === undefined? props.onClickEvent : props.onClick}
disabled={props.disabled}>
{props.label === undefined? 'Export' : props.label}
</Button>
</Tooltip>
)
}
18 changes: 9 additions & 9 deletions src/main/webui/src/commonButtons/save.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ function SubmitButton(props: BasicButtonInterfaceProps): ReactElement {
*/
export function SaveButton(props: ClickButtonInterfaceProps): ReactElement {
return (
<Tooltip position={"left"}
<Tooltip position={props.toolTipLabelPosition ?? "left"}
label={props.toolTipLabel}
openDelay={OPEN_DELAY}
closeDelay={CLOSE_DELAY}>
<Button rightSection={<IconDeviceFloppy size={ICON_SIZE}/>}
color={"violet.5"}
variant={"subtle"}
onClick={props.onClick === undefined?
props.onClickEvent :
props.onClick}
disabled={props.disabled}>
closeDelay={CLOSE_DELAY}
>
<Button
rightSection={<IconDeviceFloppy size={ICON_SIZE}/>}
color={"blue.7"}
variant={props.variant ?? "subtle"}
onClick={props.onClick === undefined? props.onClickEvent : props.onClick}
disabled={props.disabled}>
{props.label === undefined? 'Save' : props.label}
</Button>
</Tooltip>
Expand Down

0 comments on commit 597d6cc

Please sign in to comment.