diff --git a/backend/ohq/migrations/0021_queue_question_timer_enabled_and_more.py b/backend/ohq/migrations/0021_queue_question_timer_enabled_and_more.py new file mode 100644 index 00000000..e72bb68b --- /dev/null +++ b/backend/ohq/migrations/0021_queue_question_timer_enabled_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.3 on 2024-10-11 21:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ohq", "0020_auto_20240326_0226"), + ] + + operations = [ + migrations.AddField( + model_name="queue", + name="question_timer_enabled", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="queue", + name="question_timer_start_time", + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/backend/ohq/models.py b/backend/ohq/models.py index 316951c1..31b59e9a 100644 --- a/backend/ohq/models.py +++ b/backend/ohq/models.py @@ -75,6 +75,7 @@ class Course(models.Model): invite_only = models.BooleanField(default=False) members = models.ManyToManyField(User, through="Membership", through_fields=("course", "user")) + # MAX_NUMBER_COURSE_USERS = 1000 class Meta: @@ -215,6 +216,9 @@ class Queue(models.Model): rate_limit_questions = models.IntegerField(blank=True, null=True) rate_limit_minutes = models.IntegerField(blank=True, null=True) + question_timer_enabled = models.BooleanField(default=False) + question_timer_start_time = models.IntegerField(blank=True, null=True) + video_chat_setting = models.CharField( max_length=8, choices=VIDEO_CHOICES, default=VIDEO_OPTIONAL ) diff --git a/backend/ohq/serializers.py b/backend/ohq/serializers.py index d43e2752..1ae87c58 100644 --- a/backend/ohq/serializers.py +++ b/backend/ohq/serializers.py @@ -157,6 +157,8 @@ class Meta: "rate_limit_length", "rate_limit_questions", "rate_limit_minutes", + "question_timer_enabled", + "question_timer_start_time", "video_chat_setting", "pin", "pin_enabled", diff --git a/frontend/components/Course/CourseWrapper.tsx b/frontend/components/Course/CourseWrapper.tsx index 17a00c98..e0a9e63d 100644 --- a/frontend/components/Course/CourseWrapper.tsx +++ b/frontend/components/Course/CourseWrapper.tsx @@ -46,8 +46,9 @@ const CourseWrapper = ({ render, ...props }: CourseProps) => { useEffect(() => setSupportsNotifs(browserSupportsNotifications()), []); const toggleNotifs = () => { - setNotifs(!notifs); - localStorage.setItem("notifs", !notifs ? "true" : "false"); + const newNotifs = !notifs; + setNotifs(newNotifs); + localStorage.setItem("notifs", newNotifs ? "true" : "false"); document.body.focus(); }; diff --git a/frontend/components/Course/InstructorQueuePage/CreateQueue/CreateQueue.tsx b/frontend/components/Course/InstructorQueuePage/CreateQueue/CreateQueue.tsx deleted file mode 100644 index 436d1921..00000000 --- a/frontend/components/Course/InstructorQueuePage/CreateQueue/CreateQueue.tsx +++ /dev/null @@ -1,334 +0,0 @@ -import { useState, useMemo } from "react"; -import { Grid, Segment, Header, Form, Button } from "semantic-ui-react"; -import { mutateResourceListFunction } from "@pennlabs/rest-hooks/dist/types"; -import Snackbar from "@material-ui/core/Snackbar"; -import Alert from "@material-ui/lab/Alert"; -import { createQueue } from "../../../../hooks/data-fetching/course"; -import { Queue, VideoChatSetting } from "../../../../types"; - -interface CreateQueueProps { - courseId: number; - backFunc: () => void; - successFunc: () => void; - mutate: mutateResourceListFunction; -} - -interface QueueFormInput { - name: string; - description: string; - questionTemplate: string; - videoChatSetting: VideoChatSetting; - pinEnabled: boolean; - rateLimitEnabled: boolean; - rateLimitLength?: number; - rateLimitQuestions?: number; - rateLimitMinutes?: number; -} - -enum RateLimitFields { - RATE_LIMIT_QUESTIONS = "rateLimitQuestions", - RATE_LIMIT_MINUTES = "rateLimitMinutes", - RATE_LIMIT_LENGTH = "rateLimitLength", -} - -const castInt = (n: string): number | undefined => { - let casted: number | undefined = parseInt(n, 10); - if (isNaN(casted)) { - casted = undefined; - } - - return casted; -}; - -const CreateQueue = (props: CreateQueueProps) => { - const { courseId, backFunc, successFunc, mutate } = props; - /* STATE */ - const [error, setError] = useState(false); - const [input, setInput] = useState({ - name: "", - description: "", - questionTemplate: "", - videoChatSetting: VideoChatSetting.DISABLED, - pinEnabled: false, - rateLimitEnabled: false, - rateLimitLength: undefined, - rateLimitQuestions: undefined, - rateLimitMinutes: undefined, - }); - - const handleVideoChatInputChange = (e, { name }) => { - switch (name) { - case "videoChatRequired": - input.videoChatSetting = VideoChatSetting.REQUIRED; - break; - case "videoChatOptional": - input.videoChatSetting = VideoChatSetting.OPTIONAL; - break; - case "videoChatDisabled": - input.videoChatSetting = VideoChatSetting.DISABLED; - break; - } - setInput({ ...input }); - }; - - const handlePinInputChange = (e, { name }) => { - switch (name) { - case "pinEnabled": - input.pinEnabled = true; - break; - case "pinDisabled": - input.pinEnabled = false; - break; - } - setInput({ ...input }); - }; - - const isDisabled = useMemo(() => { - let invalidInps = !input.name || !input.description; - if (input.rateLimitEnabled) { - invalidInps = - invalidInps || - input.rateLimitLength === undefined || - (input.rateLimitLength !== undefined && - input.rateLimitLength < 0); - invalidInps = - invalidInps || - input.rateLimitQuestions === undefined || - (input.rateLimitQuestions !== undefined && - input.rateLimitQuestions <= 0); - invalidInps = - invalidInps || - input.rateLimitMinutes === undefined || - (input.rateLimitMinutes !== undefined && - input.rateLimitMinutes <= 0); - } - return invalidInps; - }, [input]); - - const [validQuestionRate, setValidQuestionRate] = useState(true); - const [validMinsRate, setValidMinsRate] = useState(true); - const [validLenRate, setValidLenRate] = useState(true); - const [mutateLoading, setRefetchLoading] = useState(false); - - /* HANDLER FUNCTIONS */ - const handleInputChange = (e, { name, value }) => { - input[name] = value; - if (name === RateLimitFields.RATE_LIMIT_QUESTIONS) { - input[name] = castInt(input[name]); - setValidQuestionRate(input[name] > 0); - } - - if (name === RateLimitFields.RATE_LIMIT_MINUTES) { - input[name] = castInt(input[name]); - setValidMinsRate(input[name] > 0); - } - - if (name === RateLimitFields.RATE_LIMIT_LENGTH) { - input[name] = castInt(input[name]); - setValidLenRate(input[name] >= 0); - } - setInput({ ...input }); - }; - - const onSubmit = async () => { - try { - await createQueue(courseId, input); - setRefetchLoading(true); - await mutate(-1, null); - setRefetchLoading(false); - backFunc(); - successFunc(); - } catch (e) { - setError(true); - } - }; - - return ( - - - -
Create New Queue
-
-
- - -
- - - - - - - - - - - - - - - setInput({ - ...input, - rateLimitEnabled: - !input.rateLimitEnabled, - }) - } - /> - - {input.rateLimitEnabled && ( - - - - - - - - - )} - - - - - - - - - - - - - - - -
- setOpen(true)}> - Archive - - } - > - Archive Queue - - You are about to archive this queue:{" "} - {queue.name}. - - - + setOpen(true)}> + Archive + + } + > + Archive Queue + + You are about to archive this queue:{" "} + {queue.name}. + + +