diff --git a/apiserver/apiserver/api/models.py b/apiserver/apiserver/api/models.py
index 7b9b20e..d01391f 100644
--- a/apiserver/apiserver/api/models.py
+++ b/apiserver/apiserver/api/models.py
@@ -117,6 +117,7 @@ class Course(models.Model):
name = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
is_old = models.BooleanField(default=False)
+ tags = models.CharField(max_length=128, blank=True)
history = HistoricalRecords()
diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py
index 966afbd..8a27db9 100644
--- a/apiserver/apiserver/api/serializers.py
+++ b/apiserver/apiserver/api/serializers.py
@@ -449,7 +449,7 @@ class StudentTrainingSerializer(TrainingSerializer):
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = models.Course
- fields = ['id', 'name', 'is_old', 'description']
+ fields = ['id', 'name', 'is_old', 'description', 'tags']
class SessionSerializer(serializers.ModelSerializer):
student_count = serializers.SerializerMethodField()
diff --git a/webclient/src/Classes.js b/webclient/src/Classes.js
index 0109656..d639e57 100644
--- a/webclient/src/Classes.js
+++ b/webclient/src/Classes.js
@@ -1,12 +1,13 @@
import React, { useState, useEffect, useReducer } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
import './light.css';
-import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { Label, Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
import moment from 'moment-timezone';
import { isAdmin, isInstructor, getInstructor, BasicTable, requester } from './utils.js';
import { NotFound, PleaseLogin } from './Misc.js';
import { InstructorClassDetail, InstructorClassAttendance } from './InstructorClasses.js';
import { PayPalPayNow } from './PayPal.js';
+import { tags } from './Courses.js';
function ClassTable(props) {
const { classes } = props;
@@ -92,6 +93,13 @@ function NewClassTable(props) {
+ {!!x.course.tags && x.course.tags.split(',').map(name =>
+
+ )}
+
+
@@ -140,6 +148,7 @@ function NewClassTable(props) {
let classesCache = false;
let sortCache = true;
+let tagFilterCache = false;
export function ClassFeed(props) {
const [classes, setClasses] = useState(classesCache);
@@ -181,6 +190,7 @@ export function ClassFeed(props) {
export function Classes(props) {
const [classes, setClasses] = useState(classesCache);
const [sortByCourse, setSortByCourse] = useState(sortCache);
+ const [tagFilter, setTagFilter] = useState(tagFilterCache);
const { token, user } = props;
useEffect(() => {
@@ -194,8 +204,11 @@ export function Classes(props) {
});
}, []);
- const isTeaching = (x) => x.instructor_id === user.member.id;
- const sortDate = (a, b) => a.datetime > b.datetime ? 1 : -1;
+ const byTeaching = (x) => x.instructor_id === user.member.id;
+ const byDate = (a, b) => a.datetime > b.datetime ? 1 : -1;
+ const byTag = (x) => tagFilter ? x.course_data.tags.includes(tagFilter) : true;
+
+ console.log(tagFilter);
return (
@@ -203,38 +216,70 @@ export function Classes(props) {
Click here to view the list of all courses.
- {!!user && !!classes.length && !!classes.filter(isTeaching).length &&
+ {!!user && !!classes.length && !!classes.filter(byTeaching).length &&
<>
-
+
>
}
-
+
+
+
+
+
+
+
+ Filter by tag:
+
+ {Object.entries(tags).map(([name, color]) =>
+
+ )}
+
+
+
+ {tagFilter && }
+
-
{classes.length ?
sortByCourse ?
-
+
:
-
+
:
Loading...
}
diff --git a/webclient/src/Courses.js b/webclient/src/Courses.js
index 27c05fb..0aaf90f 100644
--- a/webclient/src/Courses.js
+++ b/webclient/src/Courses.js
@@ -1,17 +1,35 @@
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
import './light.css';
-import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { Button, Label, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
import moment from 'moment-timezone';
import { isInstructor, getInstructor, requester } from './utils.js';
import { NotFound, PleaseLogin } from './Misc.js';
import { InstructorCourseList, InstructorCourseDetail } from './InstructorCourses.js';
import { InstructorClassList } from './InstructorClasses.js';
+export const tags = {
+ Protospace: 'black',
+ Laser: 'red',
+ CNC: 'orange',
+ Niche: 'yellow',
+ //name: 'olive',
+ Electronics: 'green',
+ Computers: 'teal',
+ Metal: 'blue',
+ //name: 'violet',
+ Event: 'purple',
+ Outing: 'pink',
+ Wood: 'brown',
+ Misc: 'grey',
+};
+
let courseCache = false;
+let tagFilterCache = false;
export function Courses(props) {
const [courses, setCourses] = useState(courseCache);
+ const [tagFilter, setTagFilter] = useState(tagFilterCache);
const { token, user } = props;
useEffect(() => {
@@ -25,6 +43,8 @@ export function Courses(props) {
});
}, []);
+ const byTag = (x) => tagFilter ? x.tags.includes(tagFilter) : true;
+
return (
@@ -33,6 +53,35 @@ export function Courses(props) {
}
+
+ Filter by tag:
+
+ {Object.entries(tags).map(([name, color]) =>
+
+ )}
+
+
+
+ {tagFilter && }
+
+
{courses ?
@@ -43,10 +92,17 @@ export function Courses(props) {
{courses.length ?
- courses.map(x =>
+ courses.filter(byTag).map(x =>
{x.name}
+
+ {!!x.tags && x.tags.split(',').map(name =>
+
+ )}
+
)
diff --git a/webclient/src/light.css b/webclient/src/light.css
index f07fdfb..fd7be49 100644
--- a/webclient/src/light.css
+++ b/webclient/src/light.css
@@ -129,6 +129,10 @@ body {
margin: 0 1.5em 1em !important;
}
+.coursetags .ui.tag.label {
+ margin-top: 1rem;
+}
+
.footer {
margin-top: -20rem;
background: black;