Add UI for instructors to add and edit classes

This commit is contained in:
2020-01-16 01:32:52 +00:00
parent 561e3f99d0
commit df0d83983d
10 changed files with 225 additions and 17 deletions

View File

@@ -147,14 +147,14 @@ function App() {
</Route>
<Route path='/courses/:id'>
<CourseDetail token={token} />
<CourseDetail token={token} user={user} />
</Route>
<Route path='/courses'>
<Courses token={token} user={user} />
</Route>
<Route path='/classes/:id'>
<ClassDetail token={token} />
<ClassDetail token={token} user={user} />
</Route>
<Route path='/classes'>
<Classes token={token} />

View File

@@ -3,8 +3,9 @@ import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-r
import './light.css';
import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
import moment from 'moment';
import { BasicTable, requester } from './utils.js';
import { isInstructor, BasicTable, requester } from './utils.js';
import { NotFound, PleaseLogin } from './Misc.js';
import { InstructorClassDetail } from './InstructorClasses.js';
function ClassTable(props) {
const { classes } = props;
@@ -86,7 +87,7 @@ export function Classes(props) {
export function ClassDetail(props) {
const [clazz, setClass] = useState(false);
const [error, setError] = useState(false);
const { token } = props;
const { token, user } = props;
const { id } = useParams();
useEffect(() => {
@@ -107,6 +108,10 @@ export function ClassDetail(props) {
<div>
<Header size='large'>Class Details</Header>
{isInstructor(user) && <Segment padded>
<InstructorClassDetail clazz={clazz} setClass={setClass} {...props} />
</Segment>}
<BasicTable>
<Table.Body>
<Table.Row>

View File

@@ -5,7 +5,8 @@ import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Me
import moment from 'moment';
import { isInstructor, requester } from './utils.js';
import { NotFound, PleaseLogin } from './Misc.js';
import { InstructorCourseList, InstructorCourseDetail } from './Instructor.js';
import { InstructorCourseList, InstructorCourseDetail } from './InstructorCourses.js';
import { InstructorClassList } from './InstructorClasses.js';
export function Courses(props) {
const [courses, setCourses] = useState(false);
@@ -25,7 +26,7 @@ export function Courses(props) {
<Container>
<Header size='large'>Courses</Header>
{isInstructor && <Segment padded>
{isInstructor(user) && <Segment padded>
<InstructorCourseList courses={courses} setCourses={setCourses} {...props} />
</Segment>}
@@ -62,7 +63,7 @@ export function Courses(props) {
export function CourseDetail(props) {
const [course, setCourse] = useState(false);
const [error, setError] = useState(false);
const { token } = props;
const { token, user } = props;
const { id } = useParams();
useEffect(() => {
@@ -83,7 +84,7 @@ export function CourseDetail(props) {
<div>
<Header size='large'>{course.name}</Header>
{isInstructor && <Segment padded>
{isInstructor(user) && <Segment padded>
<InstructorCourseDetail course={course} setCourse={setCourse} {...props} />
</Segment>}
@@ -97,6 +98,11 @@ export function CourseDetail(props) {
}
<Header size='medium'>Classes</Header>
{isInstructor(user) && <Segment padded>
<InstructorClassList course={course} setCourse={setCourse} {...props} />
</Segment>}
<Table basic='very'>
<Table.Header>
<Table.Row>

View File

@@ -0,0 +1,170 @@
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom';
import * as Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import moment from 'moment';
import './light.css';
import { Button, Container, Checkbox, Divider, Dropdown, Form, Grid, Header, Icon, Image, Label, Menu, Message, Segment, Table } from 'semantic-ui-react';
import { BasicTable, staticUrl, requester } from './utils.js';
function InstructorClassEditor(props) {
const { input, setInput, error, editing } = props;
const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] });
const handleChange = (e) => handleValues(e, e.currentTarget);
const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked });
const handleDatetime = (v) => setInput({ ...input, datetime: v.utc().format() });
const makeProps = (name) => ({
name: name,
onChange: handleChange,
value: input[name] || '',
error: error[name],
});
return (
<div className='class-editor'>
<Form.Field>
<label>Time and Date</label>
<Datetime
timeConstraints={{ minutes: { step: 15 } }}
value={ input.datetime ? moment.utc(input.datetime).local() : (new Date()).setMinutes(0) }
onChange={handleDatetime}
/>
{error.datetime &&
<Label pointing prompt>
{error.datetime}
</Label>
}
</Form.Field>
<Form.Input
label='Cost ($)'
{...makeProps('cost')}
/>
<Form.Input
label='Max Number of Students'
{...makeProps('max_students')}
/>
{editing && <Form.Field>
<label>Is the class cancelled?</label>
<Checkbox
label='Yes'
name='is_cancelled'
onChange={handleCheck}
checked={input.is_cancelled}
/>
</Form.Field>}
</div>
);
}
export function InstructorClassDetail(props) {
const { clazz, setClass, token } = props;
const [open, setOpen] = useState(false);
const [input, setInput] = useState(clazz);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const { id } = useParams();
const handleSubmit = (e) => {
setLoading(true);
setSuccess(false);
requester('/sessions/'+id+'/', 'PUT', token, input)
.then(res => {
setSuccess(true);
setLoading(false);
setError(false);
setOpen(false);
setClass(res);
})
.catch(err => {
setLoading(false);
console.log(err);
setError(err.data);
});
};
return (
<div>
<Header size='medium'>Instructor Panel</Header>
{!open && success && <p>Saved!</p>}
{open ?
<Form onSubmit={handleSubmit}>
<Header size='small'>Edit Class</Header>
<InstructorClassEditor editing input={input} setInput={setInput} error={error} />
<Form.Button loading={loading} error={error.non_field_errors}>
Submit
</Form.Button>
</Form>
:
<Button onClick={() => setOpen(true)}>
Edit Class
</Button>
}
</div>
);
};
export function InstructorClassList(props) {
const { course, setCourse, token, user } = props;
const [open, setOpen] = useState(false);
const [input, setInput] = useState({});
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = (e) => {
setLoading(true);
setSuccess(false);
const data = { ...input, instructor: user.id, course: course.id };
requester('/sessions/', 'POST', token, data)
.then(res => {
setSuccess(res.id);
setInput({});
setLoading(false);
setError(false);
setOpen(false);
setCourse({ ...course, sessions: [ res, ...course.sessions ] });
})
.catch(err => {
setLoading(false);
console.log(err);
setError(err.data);
});
};
return (
<div>
<Header size='medium'>Instructor Panel</Header>
{!open && success && <p>Added! <Link to={'/classes/'+success}>View the class.</Link></p>}
{open ?
<Form onSubmit={handleSubmit}>
<Header size='small'>Add a Class</Header>
<InstructorClassEditor input={input} setInput={setInput} error={error} />
<Form.Button loading={loading} error={error.non_field_errors}>
Submit
</Form.Button>
</Form>
:
<Button onClick={() => setOpen(true)}>
Add a Class
</Button>
}
</div>
);
};

View File

@@ -44,7 +44,7 @@ function InstructorCourseEditor(props) {
/>
<Form.Field>
<label>Description {input.description ? input.description.length : 0}/6000</label>
<label>Description {input.description ? input.description.length : 0} / 6000</label>
<ReactQuill
value={input.description || ''}
modules={modules}
@@ -77,13 +77,13 @@ export function InstructorCourseDetail(props) {
setLoading(true);
setSuccess(false);
const data = { ...input, is_old: false };
requester('/courses/'+id+'/', 'PUT', props.token, data)
requester('/courses/'+id+'/', 'PUT', token, data)
.then(res => {
setSuccess(true);
setLoading(false);
setError(false);
setOpen(false);
props.setCourse({...course, ...res});
setCourse({...course, ...res});
})
.catch(err => {
setLoading(false);
@@ -129,14 +129,14 @@ export function InstructorCourseList(props) {
setLoading(true);
setSuccess(false);
const data = { ...input, is_old: false };
requester('/courses/', 'POST', props.token, data)
requester('/courses/', 'POST', token, data)
.then(res => {
setSuccess(res.id);
setInput({});
setLoading(false);
setError(false);
setOpen(false);
props.setCourses([ ...courses, res ]);
setCourses([ ...courses, res ]);
})
.catch(err => {
setLoading(false);
@@ -169,4 +169,3 @@ export function InstructorCourseList(props) {
</div>
);
};

View File

@@ -63,7 +63,7 @@ body {
padding-bottom: 24rem;
}
.course-editor {
.course-editor, .class-editor {
margin-bottom: 1rem;
}
@@ -87,6 +87,11 @@ body {
margin-bottom: 1rem !important;
}
.rdtTimeToggle {
font-size: 2rem;
padding-top: 1rem;
}
.footer {
margin-top: -20rem;