import React, { useState, useEffect } from "react";
import {TextField, MenuItem, Button, AlertTitle} from "@mui/material";
import MuiAlert from '@mui/material/Alert';
import './issuepage.css'
import SaveIcon from '@mui/icons-material/Save';
import Snackbar from '@mui/material/Snackbar';
import {validStatuses, validCategories, sampleUsers, sampleProjects} from "./constants";
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import {MarkdownPreview} from './markdownpreview'
import Stack from '@mui/material/Stack';
import PropTypes from 'prop-types';
export const IssuePageForm = ({ issue_id, isEdit }) => {
// Special alert for SnackBar. To be displayed if the form has any errors.
const Alert = React.forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={10} ref={ref} variant="filled" {...props} />;
});
const [title, setTitle] = useState("")
const [description, setDescription] = useState("")
const [status, setStatus] = useState("")
const [category, setCategory] = useState("")
const [project, setProject] = useState("")
const [assigned_to, setAssign_to] = useState(null)
const [assigned_to_name, setAssign_to_name] = useState("")
const [titleValid, setTitleValid] = useState(true)
const [emptyProject, setEmptyProject] = useState(true)
const [hoverMsgAssign, setHoverMsgAssign] = useState(false)
const [userlist, setUserlist] = useState([])
const [projectlist, setProjectlist] = useState([])
const [tabIndex, setTabIndex] = useState(0)
// Error handling for conditional alert.
const [hasErrors, setHasError] = useState(false)
const [errorText, setErrorText] = useState("")
const [httpVerb, setHttpVerb] = useState("POST")
const [url, setUrl] = useState("http://localhost:8000/issues/create/")
const set_initial_state = (res) => {
setTitle(res.title)
setDescription(res.description)
setStatus(res.status)
setCategory(res.category)
setAssign_to_name(res.assigned_to)
setProject(res.project)
if(res.project) {
setEmptyProject(false)
}
setHttpVerb('PUT')
setUrl(`http://localhost:8000/issues/${issue_id}/`)
}
const http_fetch = async (issue) => {
const res = await fetch(url, {
method: httpVerb,
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(issue)
}).catch((e) => {
console.log(e)
setHasError(true)
setErrorText("Server error. Is the server running?")
})
await res.json()
.then( res => {
console.log(res)
// If we don't receive an id back, it means that is not unique.
if(!res.id) {
setHasError(true)
setErrorText(Object.keys(res)[0] + ": " + Object.values(res)[0]) // Title should contain error message upon bad request.
return
}
else{
window.location = "http://localhost:3000/issues/" + res.id
}
})
}
const handleSubmit = (e) => {
e.preventDefault()
/* Validation */
if(!title) {
setHasError(true)
setErrorText("Title cannot be empty")
return
}
else if(title.length > 50) {
setHasError(true)
setErrorText("Title cannot be more than 20 characters")
return
}
else if(status === "") {
setHasError(true)
setErrorText("Status cannot be empty!")
return
}
else if(category === "") {
setHasError(true)
setErrorText("Category cannot be empty!")
return
}
setHasError(false)
http_fetch({ title, description, status,
category, assigned_to, project })
}
const handleTitleChange = (e) => {
if(title.length > 20) {
setTitleValid(false)
}
else{
setTitleValid(true)
}
setTitle(e.target.value)
}
// Handling error popup alert
const handleSnackbar = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setHasError(false);
};
const handleTab = (e, newValue) => {
setTabIndex(newValue)
}
useEffect(() => {
// Fetch current state of the issue if it exists.
const fetch_issue = async () => {
const res = await fetch(`http://localhost:8000/issues/${issue_id}/`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
}).catch((e) => {
console.log(e)
setHasError(true)
setErrorText("Server error. Is the server running?")
})
await res.json()
.then(response => {
set_initial_state(response)
});
}
// Always fetch projects from backend so the user can choose.
const fetch_projects = async () => {
const res = await fetch(`http://localhost:8000/projects/`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
}).catch((e) => {
console.log(e)
})
await res.json()
.then(response => {
console.log(response)
setProjectlist(response)
});
}
fetch_projects();
if(issue_id) {
// Only fetch issue state if an issue id is present.
fetch_issue();
}
}, []);
useEffect(() => {
// Fetch users list here into new state based on project change.
const fetch_users = async () => {
const res = await fetch(`http://localhost:8000/projects/${project}/members/`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
}).catch((e) => {
console.log(e)
setHasError(true)
setErrorText("Server error. Is the server running?")
})
await res.json()
.then(response => {
console.log(response)
const response_array = Object.values(response) // End-point responds with an object list --> convert into object array to use .map() / .find()
setUserlist(response_array)
const correct_user = response_array.find(user => user.username === assigned_to_name)
if(correct_user !== undefined) {
setAssign_to(correct_user.id)
}
else if(correct_user === undefined && response_array.length === 1) {
setAssign_to(response_array[0].id)
setAssign_to_name(response_array[0].username)
}
})
}
if(project) {
fetch_users();
}
}, [project]);
return (
<div className='form-container'>
<form onSubmit={handleSubmit}>
<Stack>
<TextField label="Title" value={title} margin="normal"
InputLabelProps={{ shrink: true }}
color={`${titleValid ? '' : 'error'}`}
sx={{ width: '40%'}}
helperText="Maximum 20 characters allowed"
onChange={handleTitleChange} />
{/* Dropdown menu for status selection */}
<TextField id="outlined-select"
select fullWidth
sx={{ width: '40%'}}
label="Status"
value={status}
margin= "normal"
onChange={ (e) => setStatus(e.target.value) }>
{validStatuses.map( (option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
{/* Dropdown menu for category selection */}
<TextField id="outlined-select"
select fullWidth
sx={{ width: '40%'}}
label="Category"
value={category}
margin= "normal"
onChange={ (e) => setCategory(e.target.value) }>
{validCategories.map( (option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
{/* Dropdown menu for project selection */}
<TextField id="outlined-select"
select fullWidth sx={{ width: '40%'}}
label="Project"
value={project}
margin = "normal"
onChange={ (e) => {
setProject(e.target.value)
setEmptyProject(false)
}} >
{projectlist.map( (option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</TextField>
{/* Dropdown menu for assigned_to selection */}
<TextField id="outlined-select"
select fullWidth
sx={{ width: '40%'}}
label="Assigned to"
value={assigned_to_name}
margin = "normal"
onChange={ (e) => {
const correct_user = userlist.find(user => user.username === e.target.value)
setAssign_to_name(correct_user.username)
setAssign_to(correct_user.id)
}}
disabled={emptyProject}
onMouseEnter={ () => {
emptyProject ? setHoverMsgAssign(true): setHoverMsgAssign(false)
}}
onMouseLeave={ () => setHoverMsgAssign(false)}
>
{userlist.map( (option) => (
<MenuItem id={option.id} key={option.id} value={option.username}>
{option.username}
</MenuItem>
))}
</TextField>
</Stack>
{/* Tabs for toggling between description edit and markdown preview */}
<Box sx={{ width: '100%', textAlign: 'left' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider'}}>
<Tabs value={tabIndex} onChange={handleTab}>
<Tab label="Edit" />
<Tab label="Markdown Preview" />
</Tabs>
</Box>
<TabPanel value={tabIndex} index={0}>
<TextField fullWidth id="outlined" label="Description" value={description}
InputLabelProps={{ shrink: true }}
margin = "normal" multiline rows={8}
onChange={ (e) => setDescription(e.target.value) } />
</TabPanel>
<TabPanel value={tabIndex} index={1}>
<MarkdownPreview text={description}/>
</TabPanel>
</Box>
{/* Conditional render of Save or Create button */}
<Button variant="contained" fullWidth size="large"
type='submit' startIcon={<SaveIcon/>}
style={{ background: '#00838F'}}
sx={{mt: 4}}>
{(isEdit && <p>Save</p>) ||
(!isEdit && <p>Create issue</p>)}
</Button>
{/* Snackbar that displays Alert with error message if error in form
has been set. */}
<Snackbar
open={hasErrors}
autoHideDuration={6000}
onClose={handleSnackbar}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}>
<Alert
onClose={handleSnackbar}
severity="error">
<AlertTitle>Error</AlertTitle>
{errorText}
</Alert>
</Snackbar>
{/* Snackbar that displays Alert with information message if the user attempts to assign
a developer to an issue before a project has been chosen. */}
<Snackbar
open={hoverMsgAssign}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}>
<Alert
severity="info">
<AlertTitle>Choose project first</AlertTitle>
</Alert>
</Snackbar>
</form>
</div>
);
}
const TabPanel = ({index, value, children}) => {
return (
<div
role="tabpanel"
hidden={value !== index}>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}