Skip to content

Commit

Permalink
Merge 7ae43ad into 96e52be
Browse files Browse the repository at this point in the history
  • Loading branch information
mhauru committed Mar 24, 2022
2 parents 96e52be + 7ae43ad commit c44cfad
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 8 deletions.
116 changes: 109 additions & 7 deletions frontend/src/components/CaseContainer.js
@@ -1,12 +1,13 @@
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import { Grid, Box, DropButton, Heading, Layer, Button } from "grommet";
import { useNavigate, useParams } from "react-router-dom";
import { Grid, Box, DropButton, Layer, Button, Text } from "grommet";
import { FormClose, ZoomIn, ZoomOut } from "grommet-icons";

import MermaidChart from "./Mermaid";
import configData from "../config.json";

import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import EditableText from "./EditableText.js";
import ItemViewer from "./ItemViewer.js";
import ItemEditor from "./ItemEditor.js";
import ItemCreator from "./ItemCreator.js";
Expand All @@ -20,6 +21,7 @@ class CaseContainer extends Component {
showViewLayer: false,
showEditLayer: false,
showCreateLayer: false,
showConfirmDeleteLayer: false,
loading: true,
assurance_case: {
id: 0,
Expand All @@ -46,6 +48,15 @@ class CaseContainer extends Component {
this.setState({ loading: false });
};

deleteCurrentCase() {
const id = this.state.assurance_case.id;
const backendURL = `${configData.BASE_URL}/cases/${id}/`;
const requestOptions = {
method: "DELETE",
};
return fetch(backendURL, requestOptions);
}

componentDidMount() {
const id = this.props.params.caseSlug;
this.setState({ id: id });
Expand Down Expand Up @@ -171,6 +182,11 @@ class CaseContainer extends Component {
this.setState({ showCreateLayer: true });
}

showConfirmDeleteLayer(event) {
event.preventDefault();
this.setState({ showConfirmDeleteLayer: true });
}

hideViewLayer() {
this.setState({ showViewLayer: false });
}
Expand All @@ -187,6 +203,12 @@ class CaseContainer extends Component {
});
}

hideConfirmDeleteLayer() {
this.setState({
showConfirmDeleteLayer: false,
});
}

viewLayer() {
return (
<Box>
Expand Down Expand Up @@ -257,6 +279,21 @@ class CaseContainer extends Component {
);
}

submitCaseChange(field, value) {
// Send to the backend a PUT request, changing the `field` of the current case to be
// `value`.
const id = this.state.assurance_case.id;
const backendURL = `${configData.BASE_URL}/cases/${id}/`;
const changeObj = {};
changeObj[field] = value;
const requestOptions = {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(changeObj),
};
fetch(backendURL, requestOptions);
}

createLayer() {
return (
<Box>
Expand Down Expand Up @@ -290,6 +327,43 @@ class CaseContainer extends Component {
</Box>
);
}
confirmDeleteLayer() {
return (
<Box>
<Layer
position="center"
onEsc={this.hideConfirmDeleteLayer.bind(this)}
onClickOutside={this.hideConfirmDeleteLayer.bind(this)}
>
<Box pad="medium" gap="small" fill>
<Text>Are you sure you want to permanently delete this case?</Text>
<Box direction="row" justify="end" fill={true}>
<Button
label="No"
margin="small"
onClick={this.hideConfirmDeleteLayer.bind(this)}
/>
<Button
label="Yes"
margin="small"
onClick={() => {
this.deleteCurrentCase().then((response) => {
if (response.status === 204) {
this.props.navigate("/");
} else {
// Something seems to have gone wrong.
// TODO How should we handle this?
this.hideConfirmDeleteLayer();
}
});
}}
/>
</Box>
</Box>
</Layer>
</Box>
);
}

render() {
// don't try to render the chart until we're sure we have the full JSON from the DB
Expand Down Expand Up @@ -321,27 +395,53 @@ class CaseContainer extends Component {
this.state.createItemType &&
this.state.createItemParentId &&
this.createLayer()}
{this.state.showConfirmDeleteLayer && this.confirmDeleteLayer()}

<Box
gridArea="header"
direction="column"
gap="small"
pad={{
horizontal: "small",
top: "small",
top: "medium",
bottom: "none",
}}
>
<Heading level={2}>{this.state.assurance_case.name}</Heading>
<EditableText
initialValue={this.state.assurance_case.name}
textsize="xlarge"
style={{
height: 0,
}}
onSubmit={(value) => this.submitCaseChange("name", value)}
/>
<EditableText
initialValue={this.state.assurance_case.description}
size="small"
style={{
height: 0,
}}
onSubmit={(value) =>
this.submitCaseChange("description", value)
}
/>
</Box>

<Box
gridArea="topright"
direction="column"
gap="small"
pad={{
horizontal: "small",
top: "small",
bottom: "small",
bottom: "none",
}}
gridArea="topright"
>
<Button
label="Delete Case"
secondary
onClick={this.showConfirmDeleteLayer.bind(this)}
/>
<DropButton
label="Add Goal"
dropAlign={{ top: "bottom", right: "right" }}
Expand Down Expand Up @@ -407,4 +507,6 @@ class CaseContainer extends Component {
}
}

export default (props) => <CaseContainer {...props} params={useParams()} />;
export default (props) => (
<CaseContainer {...props} params={useParams()} navigate={useNavigate()} />
);
48 changes: 48 additions & 0 deletions frontend/src/components/EditableText.js
@@ -0,0 +1,48 @@
import { Form, TextInput } from "grommet";
import React, { Component } from "react";
import { useParams } from "react-router-dom";

class EditableText extends Component {
// A text input box component that renders as an ordinary looking text box, but is
// editable. An onSubmit function can be provided as a prop, and it will be called with
// the value of the text box when the user hits enter or the text box loses focus.

constructor(props) {
super(props);
this.textInputRef = React.createRef();
this.state = {
value: props.initialValue,
};
}

onChange(event) {
this.setState({ value: event.target.value });
}

onSubmit(event) {
// Make the TextInput component lose focus, if it has focus.
this.textInputRef.current.blur();
this.props.onSubmit(this.state.value);
}

render() {
return (
<Form
onSubmit={this.onSubmit.bind(this)}
// Any time the form loses focus, we should submit, to avoid losing edits.
onBlur={this.onSubmit.bind(this)}
>
<TextInput
style={this.props.style}
ref={this.textInputRef}
plain={true}
size={this.props.textsize}
onChange={this.onChange.bind(this)}
value={this.state.value}
/>
</Form>
);
}
}

export default (props) => <EditableText {...props} params={useParams()} />;
8 changes: 7 additions & 1 deletion frontend/src/components/tests/CaseContainer.test.js
Expand Up @@ -4,6 +4,12 @@ import React from "react";
import "@testing-library/jest-dom";
import CaseContainer from "../CaseContainer.js";

const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate,
}));

global.fetch = jest.fn(() =>
Promise.resolve({
json: () =>
Expand All @@ -20,6 +26,6 @@ test("renders loading screen", () => {
test("renders case view", async () => {
render(<CaseContainer id="1" />);
await waitFor(() =>
expect(screen.getByText("Test case")).toBeInTheDocument()
expect(screen.getByDisplayValue("Test case")).toBeInTheDocument()
);
});

0 comments on commit c44cfad

Please sign in to comment.