diff --git a/src/Routes.js b/src/Routes.js index 10a5342..b3af5e4 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -5,9 +5,10 @@ import Register from './components/Register' import Dashboard from './components/Dashboard' import Forms from './components/Forms' import Submission from './components/Submission' +import SubmissionProfile from './components/SubmissionProfile' import Questions from './components/Questions' -import { login, register, dashboard, forms, upload, submission, urlBaseFrontend } from './urls' -import Upload from './components/Upload' +import Preview from './components/Preview' +import { login, register, dashboard, forms, submission, urlBaseFrontend, urlBaseBackend } from './urls' import {PrivateRoute} from './PrivateRoute' import { AuthRoute } from './AuthRoute' @@ -19,10 +20,11 @@ export default class Routes extends Component { + + - } /> diff --git a/src/actions/form.js b/src/actions/form.js index 9497fbe..14e8195 100644 --- a/src/actions/form.js +++ b/src/actions/form.js @@ -5,6 +5,7 @@ import { urlFormId } from '../urls' import { + GET_ALL_FORMS, GET_PUBLISHED_FORMS, GET_UNPUBLISHED_FORMS, GET_FORM, @@ -18,6 +19,27 @@ import { FORM_ERRORS } from './types' +export const getAllForms = () => async dispatch => { + try { + const config = { + headers: { + Authorization: `Bearer ${localStorage.token}`, + } + } + const res = await axios.get(urlPostForm(), config); + dispatch({ + type: GET_ALL_FORMS, + payload: res.data + }); + } + catch (err) { + dispatch({ + type: FORM_ERRORS, + payload: err.response.data + }) + } +} + export const getPublishedForm = (status) => async dispatch => { try { const config = { diff --git a/src/actions/info.js b/src/actions/info.js index e0b348f..efa1798 100644 --- a/src/actions/info.js +++ b/src/actions/info.js @@ -1,6 +1,7 @@ import axios from 'axios' import { urlInfo, + urlInfoId, urlPatchInfo } from '../urls' import { @@ -31,6 +32,27 @@ export const getInfo = () => async dispatch => { } } +export const getInfoId = (id) => async dispatch => { + try { + const config = { + headers: { + Authorization: `Bearer ${localStorage.token}`, + } + } + const res = await axios.get(urlInfoId(id), config); + dispatch({ + type: GET_USER_INFO, + payload: res.data + }); + } + catch (err) { + dispatch({ + type: USER_INFO_ERRORS, + payload: err.response.data + }) + } +} + export const postInfo = (data, callback) => async dispatch => { try { const config = { diff --git a/src/actions/submission.js b/src/actions/submission.js new file mode 100644 index 0000000..09aa69d --- /dev/null +++ b/src/actions/submission.js @@ -0,0 +1,100 @@ +import axios from 'axios' +import { + urlFormsFilled, + urlSubmissions, + urlFormsFilledId, + urlFormsFilledUser +} from '../urls' +import { + GET_FORMS, + GET_SUBMISSIONS, + UPDATE_SUBMISSION, + SUBMISSION_ERROR +} from './types' + +export const getFormsFilled = () => async dispatch => { + try { + const config = { + headers: { + Authorization: `Bearer ${localStorage.token}`, + } + } + const res = await axios.get(urlFormsFilled(), config); + dispatch({ + type: GET_FORMS, + payload: res.data + }); + } + catch (err) { + dispatch({ + type: SUBMISSION_ERROR, + payload: err.response.data + }) + } +} + +export const getFormsFilledUser = (id) => async dispatch => { + try { + const config = { + headers: { + Authorization: `Bearer ${localStorage.token}`, + } + } + const res = await axios.get(urlFormsFilledUser(id), config); + dispatch({ + type: GET_FORMS, + payload: res.data + }); + } + catch (err) { + dispatch({ + type: SUBMISSION_ERROR, + payload: err.response.data + }) + } +} + +export const getAllSubmissions = (user_name, form_id) => async dispatch => { + try { + const config = { + headers: { + Authorization: `Bearer ${localStorage.token}`, + } + } + const res = await axios.get(urlSubmissions(user_name, form_id), config); + dispatch({ + type: GET_SUBMISSIONS, + payload: res.data + }); + } + catch (err) { + dispatch({ + type: SUBMISSION_ERROR, + payload: err.response.data + }) + } +} + +export const updateSubmission = (id, data, callback) => async dispatch => { + try { + const config = { + headers: { + Authorization: `Bearer ${localStorage.token}`, + } + } + const res = await axios.patch(urlFormsFilledId(id), data, config); + dispatch({ + type: UPDATE_SUBMISSION, + payload: res.data + }); + callback() + } + catch (err) { + console.log(err) + dispatch({ + type: SUBMISSION_ERROR, + payload: err.response.data + }) + callback() + } +} diff --git a/src/actions/types.js b/src/actions/types.js index 28955d4..09a0150 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -23,3 +23,9 @@ export const FORM_ERRORS = 'FORM_ERRORS'; export const GET_QUESTIONS = 'GET_QUESTIONS'; export const POST_QUESTIONS = 'POST_QUESTIONS'; export const QUESTION_ERROR = 'QUESTION_ERROR'; +export const GET_FORMS = 'GET_FORMS'; +export const GET_ALL_FORMS = 'GET_ALL_FORMS'; +export const GET_SUBMISSIONS = 'GET_SUBMISSIONS'; +export const UPDATE_SUBMISSION = 'UPDATE_SUBMISSION'; +export const SUBMISSION_ERROR = 'SUBMISSION_ERROR'; + diff --git a/src/components/Dashboard.js b/src/components/Dashboard.js index c4c86cc..8f82045 100644 --- a/src/components/Dashboard.js +++ b/src/components/Dashboard.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import '../styles/Dashboard.css' import { Item, Card, Header, Divider, Icon } from 'semantic-ui-react' import Profile from './Profile' +import FormsFilled from './FormsFilled' export default class Dashboard extends Component { constructor(props) { @@ -27,11 +28,16 @@ export default class Dashboard extends Component {
Profile - + { + this.props.id ? + null + : + } +
- + @@ -39,6 +45,7 @@ export default class Dashboard extends Component {
Forms Filled
+
diff --git a/src/components/FormsFilled.js b/src/components/FormsFilled.js new file mode 100644 index 0000000..6e88361 --- /dev/null +++ b/src/components/FormsFilled.js @@ -0,0 +1,82 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router-dom' +import { getFormsFilled, getFormsFilledUser } from '../actions/submission' +import PropTypes from 'prop-types' +import { preview } from '../urls' +import { Card, Button, Message, Modal, Icon, Header, Form, TextArea, Checkbox } from 'semantic-ui-react' +import moment from 'moment' +import '../styles/Dashboard.css' + +class FormsFilled extends Component { + constructor(props) { + super(props) + } + + componentDidMount() { + if(this.props.id){ + this.props.getFormsFilledUser(this.props.id) + } + else{ + this.props.getFormsFilled() + } + } + + render() { + console.log(this.props.formsfilled) + const { formsfilled } = this.props + return( +
+ + { + formsfilled && formsfilled.length !== 0 ? + formsfilled.map(formsfilled => + + + {formsfilled.form.name} + {formsfilled.form.description} +
+
+
+ Published Status: {formsfilled.form.published_status.toUpperCase()} + Fields: {formsfilled.form.questions.length} +
+
+ Target User: {formsfilled.form.target_user.toUpperCase()} +
+
+ Created: {moment(new Date(formsfilled.form.created_on), "YYYYMMDD").fromNow()} + Updated: {moment(new Date(formsfilled.form.updated_on), "YYYYMMDD").fromNow()} +
+
+
+
+ + + +
+ ) + : No forms filled yet. + } +
+
+ ) + } +} + +FormsFilled.propTypes = { + formsfilled: PropTypes.array.isRequired +}; + +const mapStateToProps = state => ({ + formsfilled: state.submission.formsfilled, + submissionerror: state.submission.submissionerror +}) + +export default connect( + mapStateToProps, + { getFormsFilled, getFormsFilledUser } +)(FormsFilled) diff --git a/src/components/Preview.js b/src/components/Preview.js index 211b549..e684a28 100644 --- a/src/components/Preview.js +++ b/src/components/Preview.js @@ -1,10 +1,12 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { getQuestions } from '../actions/question' +import { Link } from 'react-router-dom' import PropTypes from 'prop-types' import '../styles/Questions.css' import { DateInput, TimeInput } from 'semantic-ui-calendar-react'; -import { Form, TextArea, Divider } from 'semantic-ui-react' +import { Form, TextArea, Divider, Item, Icon } from 'semantic-ui-react' +import { submissionprofile } from '../urls' class Preview extends Component { constructor(props) { @@ -17,7 +19,12 @@ class Preview extends Component { } async componentDidMount() { - await this.props.getQuestions(this.props.id) + if(this.props.match.params.form_id){ + await this.props.getQuestions(this.props.match.params.form_id) + } + else{ + await this.props.getQuestions(this.props.id) + } } handleChange = (event, {name, value}) => { @@ -30,7 +37,17 @@ class Preview extends Component { const { questions } = this.props const { value } = this.state return ( -
+
+ { + this.props.id ? + null + : + + + Back to Profile + + } + {/* question type view based on each data type */} { questions && questions.length !== 0 ? @@ -167,6 +184,7 @@ class Preview extends Component { : null } +
) } } diff --git a/src/components/Profile.js b/src/components/Profile.js index ae42476..46a0a75 100644 --- a/src/components/Profile.js +++ b/src/components/Profile.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import { connect } from 'react-redux' -import { getInfo, postInfo, patchInfo } from '../actions/info' +import { getInfo, postInfo, patchInfo, getInfoId } from '../actions/info' import PropTypes from 'prop-types' import { Form, Icon, Message } from 'semantic-ui-react' import '../styles/Profile.css' @@ -17,7 +17,12 @@ class Profile extends Component { } componentDidMount() { - this.props.getInfo() + if(this.props.id){ + this.props.getInfoId(this.props.id) + } + else{ + this.props.getInfo() + } } submitInfo = () => { @@ -65,7 +70,7 @@ class Profile extends Component { { userinfo && userinfo.length === 1 ? userinfo.map(userinfo => - <> +
{ this.props.edit === true ?
@@ -98,7 +103,7 @@ class Profile extends Component { Role: {userinfo.user_type}
} - + ) : <> @@ -137,7 +142,6 @@ class Profile extends Component { } Profile.propTypes = { - userinfo: PropTypes.func.isRequired, postInfo: PropTypes.func.isRequired, patchInfo: PropTypes.func.isRequired }; @@ -150,5 +154,5 @@ const mapStateToProps = state => ({ export default connect( mapStateToProps, - { getInfo, postInfo, patchInfo } + { getInfo, postInfo, patchInfo, getInfoId } )(Profile) diff --git a/src/components/Submission.js b/src/components/Submission.js index 2ce7ba0..31bf8cc 100644 --- a/src/components/Submission.js +++ b/src/components/Submission.js @@ -1,11 +1,150 @@ import React, { Component } from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router-dom' +import { getAllSubmissions, updateSubmission } from '../actions/submission' +import { getInfo } from '../actions/info' +import { getAllForms } from '../actions/form' +import PropTypes from 'prop-types' +import { submissionprofile, submission } from '../urls' +import { Grid, Dropdown, Input } from 'semantic-ui-react' +import moment from 'moment' +import '../styles/Submission.css' -export default class Submissions extends Component { +const options = [ + { key: 'accepted', text: 'Accepted', value: 'accepted'}, + { key: 'rejected', text: 'Rejected', value: 'rejected'}, + { key: 'waitlisted', text: 'Waitlisted', value: 'waitlisted'}, + { key: 'pending', text: 'Pending', value: 'pending'}, +] + +class Submission extends Component { + constructor(props) { + super(props) + this.state = { + form_id: undefined, + user_name: undefined, + } + } + + componentDidMount() { + this.props.getInfo() + this.props.getAllForms() + this.props.getAllSubmissions(this.state.user_name, this.state.form_id) + } + + onSearchChange = (e) => { + if(e.target.value === ''){ + this.props.getAllSubmissions(undefined, this.state.form_id) + } + else { + this.props.getAllSubmissions(e.target.value, this.state.form_id) + } + this.setState({ + user_name: e.target.value, + }) + } + + formChange = (e, data) => { + if(data.value.length === 0){ + this.props.getAllSubmissions(this.state.user_name, undefined) + } + else { + this.props.getAllSubmissions(this.state.user_name, data.value) + } + this.setState({ + form_id: data.value.length !== 0 ? data.value : undefined + }) + } + + statusChange = (e, dt, id) => { + const data = { + acceptance_status: dt.value + } + console.log(data) + this.props.updateSubmission(id, data, this.callback) + } + + callback = () => { + this.setState({ + error: this.props.submissionerror?true:false + }) + setTimeout(() => { + this.setState({ + error: null + }) + }, 5000) + } + render() { - return ( -
- submissions + const { submissions } = this.props + const formOptions = this.props.forms.map(form => ({ + key: form.id, + text: form.name, + value: form.id + })) + console.log(submissions) + return( +
+
+ + +
+
+ { + submissions && submissions.length !== 0 ? + + { + submissions.map(submission => + + {submission.user.user_name[0] ? submission.user.user_name[0] : submission.user.username} + {submission.form.name} + {submission.user.username} + + this.statusChange(event, data, submission.id)} + className='green' + /> + + + + ) + } + + : No submissions yet. + } +
) } } + +Submission.propTypes = { + userinfo: PropTypes.func.isRequired, + submissions: PropTypes.array.isRequired, + forms: PropTypes.array.isRequired, +}; + +const mapStateToProps = state => ({ + userinfo: state.info.userinfo, + submissions: state.submission.submissions, + forms: state.form.form, + submissionerror: state.submission.submissionerror +}) + +export default connect( + mapStateToProps, + { getAllSubmissions, getInfo, getAllForms, updateSubmission } +)(Submission) diff --git a/src/components/SubmissionProfile.js b/src/components/SubmissionProfile.js new file mode 100644 index 0000000..880a2e8 --- /dev/null +++ b/src/components/SubmissionProfile.js @@ -0,0 +1,25 @@ +import React, { Component } from 'react' +import '../styles/Submission.css' +import { Link } from 'react-router-dom' +import { submission } from '../urls' +import { Item, Icon } from 'semantic-ui-react' +import Dashboard from './Dashboard' + +export default class SubmissionProfile extends Component { + constructor(props){ + super(props) + } + render() { + return ( +
+ + + Back to all Submissions + +
+ +
+
+ ) + } +} diff --git a/src/reducers/form.js b/src/reducers/form.js index c469ed7..cf09e48 100644 --- a/src/reducers/form.js +++ b/src/reducers/form.js @@ -1,4 +1,5 @@ import { + GET_ALL_FORMS, GET_PUBLISHED_FORMS, GET_UNPUBLISHED_FORMS, GET_FORM, @@ -13,10 +14,10 @@ import { } from '../actions/types'; const initialState = { + form: [], publishedform: [], unpublishedform: [], postform: [], - form: [], updatepublished: [], updateunpublished: [], formerror: null, @@ -25,6 +26,11 @@ const initialState = { const formReducer = (state = initialState, action) => { switch(action.type) { + case GET_ALL_FORMS: + return { + ...state, + form: action.payload, + }; case GET_PUBLISHED_FORMS: return { ...state, diff --git a/src/reducers/index.js b/src/reducers/index.js index 0c8066b..1f9e97c 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -4,13 +4,15 @@ import infoReducer from './info' import userReducer from './user' import formReducer from './form' import questionReducer from './question' +import submissionReducer from './submission' const rootReducers = combineReducers ({ login: loginReducer, info: infoReducer, user: userReducer, form: formReducer, - questions: questionReducer + questions: questionReducer, + submission: submissionReducer }) export default rootReducers diff --git a/src/reducers/submission.js b/src/reducers/submission.js new file mode 100644 index 0000000..2967733 --- /dev/null +++ b/src/reducers/submission.js @@ -0,0 +1,44 @@ +import { + GET_FORMS, + GET_SUBMISSIONS, + UPDATE_SUBMISSION, + SUBMISSION_ERROR +} from '../actions/types'; +import { submission } from '../urls'; + +const initialState = { + formsfilled: [], + submissions: [], + submissionerror: null +} + +const submissionReducer = (state = initialState, action) => { + switch(action.type) { + case GET_FORMS: + return { + ...state, + formsfilled: action.payload, + }; + case GET_SUBMISSIONS: + return { + ...state, + submissions: action.payload, + }; + case UPDATE_SUBMISSION: + return { + ...state, + submissions: [action.payload, ...state.submissions.filter( + submission => submission.id !== action.payload.id + )], + }; + case SUBMISSION_ERROR: + return { + ...state, + submissionerror: action.payload, + } + default: + return state + } +} + +export default submissionReducer diff --git a/src/styles/Dashboard.css b/src/styles/Dashboard.css index b5eb896..180376b 100644 --- a/src/styles/Dashboard.css +++ b/src/styles/Dashboard.css @@ -24,5 +24,15 @@ .right { display: flex; - flex-direction: column-reverse; + flex-direction: column; + max-height: 50vh; + padding-left: 0.2em; +} + +.formsfilled { + overflow-y: auto; + height: 60vh; + padding-left: 0.2em; + padding-right: 0.5em; + padding-top: 0.2em; } diff --git a/src/styles/Questions.css b/src/styles/Questions.css index b493c92..06f9f6b 100644 --- a/src/styles/Questions.css +++ b/src/styles/Questions.css @@ -44,3 +44,8 @@ .preview { margin-right: 0.5em; } + +.adminpreview { + margin-left: 5vw; + margin-right: 5vw; +} diff --git a/src/styles/Submission.css b/src/styles/Submission.css new file mode 100644 index 0000000..369e3b4 --- /dev/null +++ b/src/styles/Submission.css @@ -0,0 +1,27 @@ +.submission { + margin-left: 5vw; + margin-right: 5vw; +} + +.submissionlist { + margin-top: 1em; + padding-right: 1em; + padding-left: 0.2em; + overflow-y: auto; + height: 65vh; +} + +.searches { + display: flex; + flex-direction: row; +} + +.last { + margin-top: 0em !important; + margin-bottom: 0em !important; +} + +.displaydashboard { + margin-left: -5vw; + margin-right: -5vw; +} diff --git a/src/urls.js b/src/urls.js index 4adebae..871d912 100644 --- a/src/urls.js +++ b/src/urls.js @@ -23,10 +23,18 @@ export function form(id) { return `${urlBaseFrontend()}form/${id}` } +export function preview(form_id, user_id) { + return `${urlBaseFrontend()}preview/${form_id}/${user_id}` +} + export function submission() { return `${urlBaseFrontend()}submission` } +export function submissionprofile(id) { + return `${urlBaseFrontend()}submission/${id}` +} + export function upload() { return `${urlBaseFrontend()}upload` } @@ -51,6 +59,10 @@ export function urlInfo() { return `${urlBaseBackend()}/info/` } +export function urlInfoId(user_id) { + return `${urlBaseBackend()}/info/?user_id=${user_id}` +} + export function urlPatchInfo(id) { return `${urlBaseBackend()}/info/${id}/` } @@ -75,4 +87,25 @@ export function urlPatchQuestions() { return `${urlBaseBackend()}/questions/` } +export function urlFormsFilled() { + return `${urlBaseBackend()}/feedback/` +} + +export function urlFormsFilledUser(user_id) { + return `${urlBaseBackend()}/feedback/?user_id=${user_id}` +} +export function urlFormsFilledId(id) { + return `${urlBaseBackend()}/feedback/${id}/` +} + +export function urlSubmissions(user_name, form_id) { + if(user_name === undefined && form_id === undefined) + return `${urlBaseBackend()}/feedback/` + else if(user_name === undefined) + return `${urlBaseBackend()}/feedback/?form_id=${form_id}` + else if(form_id === undefined) + return `${urlBaseBackend()}/feedback/?user_name=${user_name}` + else + return `${urlBaseBackend()}/feedback/?user_name=${user_name}&form_id=${form_id}` +}