diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..30cf57ed --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..0782aef9 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,58 @@ + + + + diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..0f7bc519 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + diff --git a/.idea/csm_web.iml b/.idea/csm_web.iml new file mode 100644 index 00000000..d7e41aab --- /dev/null +++ b/.idea/csm_web.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..cc5462da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..e6b072c7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..2e084d40 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..dcb6b8c4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/ci/requirements.txt b/ci/requirements.txt index 5c54b7cd..66bf4f95 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -57,7 +57,7 @@ social-auth-core==4.5.6 ; python_full_version >= "3.12.4" and python_full_versio sqlparse==0.5.3 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" stack-data==0.6.3 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" traitlets==5.14.3 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" -typing-extensions==4.12.2 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" +typing-extensions==4.16.0 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" tzdata==2025.1 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" urllib3==2.3.0 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" wcwidth==0.2.13 ; python_full_version >= "3.12.4" and python_full_version < "3.13.0" diff --git a/csm_web/frontend/src/components/enrollment_automation/coordinator/fakefile.py b/csm_web/frontend/src/components/enrollment_automation/coordinator/fakefile.py new file mode 100644 index 00000000..e69de29b diff --git a/csm_web/frontend/src/components/section/MentorSectionRoster.tsx b/csm_web/frontend/src/components/section/MentorSectionRoster.tsx index 4d2cd4de..150c4539 100644 --- a/csm_web/frontend/src/components/section/MentorSectionRoster.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionRoster.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import { useSectionStudents } from "../../utils/queries/sections"; +import { useSectionStudents, useSectionWaitlistedStudents } from "../../utils/queries/sections"; import LoadingSpinner from "../LoadingSpinner"; import CheckCircle from "../../../static/frontend/img/check_circle.svg"; @@ -13,6 +13,13 @@ interface MentorSectionRosterProps { export default function MentorSectionRoster({ id }: MentorSectionRosterProps) { const { data: students, isSuccess: studentsLoaded, isError: studentsLoadError } = useSectionStudents(id); const [emailsCopied, setEmailsCopied] = useState(false); + const { + data: wlStudents, + isSuccess: wlStudentsLoaded, + isError: wlStudentsLoadError + } = useSectionWaitlistedStudents(id); + const [wlEmailsCopied, wlSetEmailsCopied] = useState(false); + const handleCopyEmails = () => { if (studentsLoaded) { navigator.clipboard.writeText(students.map(({ email }) => email).join("\n")).then(() => { @@ -21,6 +28,14 @@ export default function MentorSectionRoster({ id }: MentorSectionRosterProps) { }); } }; + const wlHandleCopyEmails = () => { + if (wlStudentsLoaded) { + navigator.clipboard.writeText(wlStudents.map(({ email }) => email).join("\n")).then(() => { + wlSetEmailsCopied(true); + setTimeout(() => wlSetEmailsCopied(false), 1500); + }); + } + }; return (

Roster

@@ -50,6 +65,35 @@ export default function MentorSectionRoster({ id }: MentorSectionRosterProps) { ) : ( )} + {wlStudentsLoaded && wlStudents && wlStudents.length > 0 && ( + +
+

Waitlisted

+ + + + + + + + + + + {wlStudents.map(({ position, name, email, id }) => ( + + + + + + ))} + +
PositionNameEmail + + {wlEmailsCopied && } +
{position}{name}{email}
+ + )}{" "} + {wlStudentsLoadError &&

Waitlisted students could not be loaded

} ); } diff --git a/csm_web/frontend/src/utils/queries/sections.tsx b/csm_web/frontend/src/utils/queries/sections.tsx index b17948a2..96268d47 100644 --- a/csm_web/frontend/src/utils/queries/sections.tsx +++ b/csm_web/frontend/src/utils/queries/sections.tsx @@ -4,7 +4,7 @@ import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResult } from "@tanstack/react-query"; import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; -import { Attendance, RawAttendance, Section, Spacetime, Student } from "../types"; +import { Attendance, RawAttendance, Section, Spacetime, Student, WaitlistedStudent } from "../types"; import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; /* ===== Queries ===== */ @@ -71,8 +71,8 @@ export const useSectionStudents = (id: number): UseQueryResult => { - const queryResult = useQuery( +export const useSectionWaitlistedStudents = (id: number): UseQueryResult => { + const queryResult = useQuery( ["sections", id, "waitlisted-students"], async () => { if (isNaN(id)) { @@ -82,7 +82,7 @@ export const useSectionWaitlistedStudents = (id: number): UseQueryResult a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + return students.sort((a: WaitlistedStudent, b: WaitlistedStudent) => a.position > b.position); } // if (resp1.status === 404) { diff --git a/csm_web/frontend/src/utils/types.tsx b/csm_web/frontend/src/utils/types.tsx index 6bd5d130..b862c113 100644 --- a/csm_web/frontend/src/utils/types.tsx +++ b/csm_web/frontend/src/utils/types.tsx @@ -85,6 +85,13 @@ export interface Student { email: string; } +export interface WaitlistedStudent { + id: number; + name: string; + email: string; + position: number; +} + export enum AttendancePresence { EX = "EX", PR = "PR", diff --git a/csm_web/scheduler/factories.py b/csm_web/scheduler/factories.py index 86ae44f5..0e2cf5cf 100644 --- a/csm_web/scheduler/factories.py +++ b/csm_web/scheduler/factories.py @@ -603,12 +603,17 @@ def generate_test_data(preconfirm=False): ) user_objects.extend(waitlisted_users) waitlisted_students = [] + wl_position = 1 for waitlisted_user in waitlisted_users: waitlisted_students.append( WaitlistedStudentFactory.build( - section=section, course=course, user=waitlisted_user + section=section, + course=course, + user=waitlisted_user, + position=wl_position, ) ) + wl_position = wl_position + 1 waitlisted_student_objects.extend(waitlisted_students) # courses with many sections/students diff --git a/poetry.lock b/poetry.lock index 27bb7bfe..fe553658 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "asgiref" @@ -1673,14 +1673,14 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.16.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]]