Untitled
unknown
javascript
3 years ago
12 kB
4
Indexable
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> ); }
Editor is loading...