Untitled

mail@pastecode.io avatar
unknown
javascript
3 years ago
12 kB
2
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>
  );
}