diff --git a/src/components/Schedule.jsx b/src/components/Schedule.jsx
index e55d77c..e4e5d34 100644
--- a/src/components/Schedule.jsx
+++ b/src/components/Schedule.jsx
@@ -4,6 +4,7 @@ import Button from '@/components/Button/Button';
import EditButton from '@/components/Button/EditButton';
import { useTranslation } from "react-i18next";
import { localizeNumber, toTitleCase, ensureHttps } from "@/utils/formatters";
+import { convertTime, to12HourFormat } from '@/utils/time-utils';
const Schedule = ({ privilege, classes, filters = [] }) => {
const [isMobile, setIsMobile] = useState(window.innerWidth <= 640);
@@ -121,9 +122,27 @@ const Schedule = ({ privilege, classes, filters = [] }) => {
const ScheduleClass = ({ privilege, classObj, isMobile }) => {
const { t, i18n } = useTranslation();
+ const convertedStartTimeEST = classObj?.startTime
+ ? convertTime(classObj.day, classObj.startTime, "Etc/UTC", "America/New_York")
+ : { time: '', day: '' };
+ const convertedEndTimeEST = classObj?.endTime
+ ? convertTime(classObj.day, classObj.endTime, "Etc/UTC", "America/New_York")
+ : { time: '', day: '' };
+ const convertedStartTimeIST = classObj?.startTime
+ ? convertTime(classObj.day, classObj.startTime, "Etc/UTC", "Europe/Istanbul")
+ : { time: '', day: '' };
+ const convertedEndTimeIST = classObj?.endTime
+ ? convertTime(classObj.day, classObj.endTime, "Etc/UTC", "Europe/Istanbul")
+ : { time: '', day: '' };
+
return (
-
{classObj.startTime || "N/A"}-{classObj.endTime || "N/A"}
+
+ {to12HourFormat(convertedStartTimeEST.time)}-{to12HourFormat(convertedEndTimeEST.time)} (EST)
+
+
+ {convertedStartTimeIST.time}-{convertedEndTimeIST.time} (Ist)
+
{
+const UserItem = ({ userData, privilege, isShowClass }) => {
const [highestClass, setHighestClass] = useState(undefined);
useEffect(() => {
- const filteredClasses = classes.filter(cls =>
- userData.enrolledClasses.some(enrolled => enrolled._id === cls._id));
+ const maxClass = userData.enrolledClasses.length > 0
+ ? userData.enrolledClasses
+ .slice()
+ .sort((a, b) => {
+ // order IELTS > numbers > conversation
+ const getPriority = (cls) => {
+ if (cls.level === "ielts") return 1000;
+ if (typeof cls.level === "number") return 500 + cls.level;
+ if (cls.level === "conversation") return 0;
+ return -1; // handle unknown levels
+ };
- const maxClass = filteredClasses.length > 0
- ? filteredClasses.reduce((prev, curr) => (curr.level > prev.level ? curr : prev))
+ return getPriority(b) - getPriority(a); // sort in descending order
+ })[0]
: null;
setHighestClass(maxClass);
- }, [classes, userData]);
+ }, [userData]);
if (!userData) {
return <>>;
@@ -38,13 +47,17 @@ const UserItem = ({ userData, classes = [], privilege, isShowClass }) => {
{userData.privilege !== "instructor" && isShowClass && (
+ {/* Level ${toTitleCase(highestClass.level)}: */}
{highestClass ? (
- highestClass.ageGroup === "all" ? (
- `Level ${highestClass.level}: All Ages`
- ) : (
- `Level ${highestClass.level}: ${highestClass.ageGroup.charAt(0).toUpperCase() +
- highestClass.ageGroup.slice(1)}'s Class`
- )
+ `Level ${highestClass.level === "ielts"
+ ? "IELTS"
+ : highestClass.level === "conversation"
+ ? "Conversation"
+ : highestClass.level}:
+ ${highestClass.ageGroup === "all"
+ ? "All Ages"
+ : `${highestClass.ageGroup.charAt(0).toUpperCase() +
+ highestClass.ageGroup.slice(1)}'s Class`}`
) : (
"No Enrollment"
)}
diff --git a/src/pages/dashboards/InstructorView.jsx b/src/pages/dashboards/InstructorView.jsx
index 0a71e35..69f755f 100644
--- a/src/pages/dashboards/InstructorView.jsx
+++ b/src/pages/dashboards/InstructorView.jsx
@@ -3,7 +3,7 @@ import { UserContext } from '@/contexts/UserContext.jsx';
import { useLocation } from 'wouter';
import { useAuth } from '@clerk/clerk-react'
import { updateUser } from '@/wrappers/user-wrapper';
-import { getClasses } from '@/wrappers/class-wrapper'
+import { getAllClasses } from '@/wrappers/class-wrapper'
import { IoCreateOutline } from "react-icons/io5";
import { useTranslation } from "react-i18next";
import Button from '@/components/Button/Button';
@@ -42,7 +42,7 @@ const InstructorView = () => {
useEffect(() => {
const fetchData = async () => {
if (user) {
- let instructorClasses = await getClasses(`instructor=${toTitleCase(user.firstName)}`); // TODO: should use first and last name
+ let instructorClasses = await getAllClasses(`instructor=${toTitleCase(user.firstName)}`); // TODO: should use first and last name
setClasses(instructorClasses);
setAllowRender(true);
}
diff --git a/src/pages/dashboards/admin/AdminInstructors.jsx b/src/pages/dashboards/admin/AdminInstructors.jsx
index b1ab16e..8cebb0c 100644
--- a/src/pages/dashboards/admin/AdminInstructors.jsx
+++ b/src/pages/dashboards/admin/AdminInstructors.jsx
@@ -4,7 +4,6 @@ import { useLocation, Link } from 'wouter';
import { useAuth } from '@clerk/clerk-react';
import { getUsers } from '@/wrappers/user-wrapper.js'
import { IoPersonOutline } from "react-icons/io5";
-import { getClasses } from '@/wrappers/class-wrapper';
import UserItem from '@/components/UserItem'
import SearchBar from '@/components/SearchBar';
import SkeletonUser from '@/components/Skeletons/SkeletonUser';
@@ -18,7 +17,6 @@ const AdminInstructors = () => {
const [, setLocation] = useLocation();
const { isSignedIn, isLoaded } = useAuth();
const [allowRender, setAllowRender] = useState(false);
- const [classes, setClasses] = useState([]);
const [users, setUsers] = useState([]);
const [searchInput, setSearchInput] = useState('');
const showSkeleton = useDelayedSkeleton(!allowRender);
@@ -37,8 +35,6 @@ const AdminInstructors = () => {
const fetchUsers = async () => {
const userData = await getUsers();
setUsers(userData.data.filter((user) => user.privilege === "instructor"));
- const classData = await getClasses();
- setClasses(classData);
setAllowRender(true);
}
@@ -75,7 +71,7 @@ const AdminInstructors = () => {
{allowRender
? filteredUsers.map((userData, userIndex) => (
-
+
))
: showSkeleton && }
diff --git a/src/pages/dashboards/admin/AdminSchedule.jsx b/src/pages/dashboards/admin/AdminSchedule.jsx
index 3200efd..bc1d98f 100644
--- a/src/pages/dashboards/admin/AdminSchedule.jsx
+++ b/src/pages/dashboards/admin/AdminSchedule.jsx
@@ -8,7 +8,6 @@ import Schedule from '@/components/Schedule';
import SkeletonSchedule from '@/components/Skeletons/SkeletonSchedule';
import useDelayedSkeleton from '@/hooks/useDelayedSkeleton';
import Unauthorized from "@/pages/Unauthorized";
-import { toTitleCase } from '@/utils/formatters';
const AdminSchedule = () => {
const { user } = useContext(UserContext);
@@ -69,7 +68,7 @@ const AdminSchedule = () => {
hover:bg-gray-50`}
onClick={() => handleAddFilter(level)}
>
- {typeof level === "string" ? toTitleCase(level) : `Level ${level}`}
+ {level === "ielts" ? "IELTS" : level === "conversation" ? "Conversation" : `Level ${level}`}
))}
diff --git a/src/pages/dashboards/admin/AdminStudents.jsx b/src/pages/dashboards/admin/AdminStudents.jsx
index 9f938f9..229d1d5 100644
--- a/src/pages/dashboards/admin/AdminStudents.jsx
+++ b/src/pages/dashboards/admin/AdminStudents.jsx
@@ -4,7 +4,8 @@ import { useLocation, Link } from 'wouter';
import { useAuth } from '@clerk/clerk-react';
import { IoPersonOutline } from "react-icons/io5";
import { getUsers, getStudentsForExport } from '@/wrappers/user-wrapper.js';
-import { getClasses, getClassById } from '@/wrappers/class-wrapper';
+import { getClassById } from '@/wrappers/class-wrapper';
+import { getLevels } from '@/wrappers/level-wrapper';
import { getStudentsClasses } from '@/wrappers/user-wrapper';
import Unauthorized from "@/pages/Unauthorized";
import Dropdown from '@/components/Dropdown/Dropdown';
@@ -23,7 +24,6 @@ const AdminStudents = () => {
const [, setLocation] = useLocation();
const { isSignedIn, isLoaded } = useAuth();
const [allowRender, setAllowRender] = useState(false);
- const [classes, setClasses] = useState([]);
const [users, setUsers] = useState([]);
const [levels, setLevels] = useState([]);
const [currFilter, setCurrFilter] = useState(null);
@@ -44,10 +44,6 @@ const AdminStudents = () => {
const fetchUsers = async () => {
const userData = await getUsers();
const students = userData.data.filter((user) => user.privilege === "student");
- const classData = await getClasses();
- setClasses(classData);
-
- let allLevels = new Set();
const studentsWithClasses = await Promise.all(
students.map(async (student) => {
@@ -55,7 +51,6 @@ const AdminStudents = () => {
const classes = await Promise.all(
(classRef.enrolledClasses || []).map(async (classID) => {
const classData = await getClassById(classID);
- allLevels.add(classData.level);
return classData;
})
);
@@ -63,10 +58,9 @@ const AdminStudents = () => {
return { ...student, enrolledClasses: classes };
})
);
-
- const uniqueLevels = Array.from(allLevels).sort((a, b) => a - b);
setUsers(studentsWithClasses);
- setLevels(uniqueLevels);
+ const levels = await getLevels();
+ setLevels(levels);
setAllowRender(true);
}
@@ -95,8 +89,9 @@ const AdminStudents = () => {
const matchesName =
fullName1.includes(search) || fullName2.includes(search);
+ const filter = isNaN(Number(currFilter)) ? currFilter : Number(currFilter);
const matchesLevel =
- !currFilter || user.enrolledClasses?.some(cls => cls.level === parseInt(currFilter));
+ !currFilter || user.enrolledClasses?.some(cls => cls.level === filter);
const matchesClass =
user.enrolledClasses?.some(cls =>
@@ -141,15 +136,32 @@ const AdminStudents = () => {
All Levels
+ {/* Level filter for numbered levels */}
{levels.map((level) => (
))}
+
+ {/* Level filter for supp classes */}
+
+
@@ -160,7 +172,7 @@ const AdminStudents = () => {
{allowRender
? filteredUsers.map((userData) => (
-
+
))
: showSkeleton && }
diff --git a/src/pages/dashboards/admin/editPages/EditClass.jsx b/src/pages/dashboards/admin/editPages/EditClass.jsx
index 44a818c..e2fbbd6 100644
--- a/src/pages/dashboards/admin/editPages/EditClass.jsx
+++ b/src/pages/dashboards/admin/editPages/EditClass.jsx
@@ -10,9 +10,8 @@ import DayDropdown from '@/components/Dropdown/DayDropdown';
import UserItem from "@/components/UserItem";
import Alert from '@/components/Alert';
import { IoAdd, IoTrashBinOutline, IoPersonOutline } from "react-icons/io5";
-import { updateClass, deleteClass, getClasses } from '@/wrappers/class-wrapper';
+import { updateClass, deleteClass, getClasses, getClassById } from '@/wrappers/class-wrapper';
import { getUser } from '@/wrappers/user-wrapper';
-import { convertTime } from "@/utils/time-utils";
import Unauthorized from "@/pages/Unauthorized";
import SkeletonUser from "@/components/Skeletons/SkeletonUser";
import useDelayedSkeleton from '@/hooks/useDelayedSkeleton';
@@ -28,7 +27,6 @@ const EditClass = () => {
const [isSaving, setIsSaving] = useState(false);
const params = useParams();
- const [classes, setClasses] = useState(null);
const [classObj, setClassObj] = useState(null);
const [classData, setClassData] = useState({
level: '',
@@ -61,9 +59,7 @@ const EditClass = () => {
const fetchClass = async () => {
if (user) {
- const data = await getClasses();
- setClasses(data);
- const classObj = data.find(c => c._id === params.classId);
+ const classObj = await getClassById(params.classId);
setClassObj(classObj);
setClassData({
level: classObj.level,
@@ -364,7 +360,6 @@ const EditClass = () => {
))
diff --git a/src/pages/dashboards/admin/editPages/EditConversation.jsx b/src/pages/dashboards/admin/editPages/EditConversation.jsx
index 0374146..7bf2a73 100644
--- a/src/pages/dashboards/admin/editPages/EditConversation.jsx
+++ b/src/pages/dashboards/admin/editPages/EditConversation.jsx
@@ -1,20 +1,24 @@
import { useContext, useEffect, useState } from "react";
import { UserContext } from '@/contexts/UserContext.jsx';
-import { useLocation, useParams } from 'wouter';
+import { useLocation, useParams, Link } from 'wouter';
import { useAuth } from '@clerk/clerk-react';
import FormInput from '@/components/Form/FormInput'
import Button from '@/components/Button/Button';
import DeleteButton from "@/components/Button/DeleteButton";
import DayDropdown from '@/components/Dropdown/DayDropdown';
import BackButton from "@/components/Button/BackButton";
+import UserItem from "@/components/UserItem";
import Alert from '@/components/Alert';
import SupplementaryClassPreview from "@/components/Class/SupplementaryClassPreview";
import ImagePicker from "@/components/ImagePicker";
import { levelImgs } from "@/constants/images";
import { getConversationById } from "@/wrappers/conversation-wrapper";
import { updateConversation, deleteConversation } from '@/wrappers/conversation-wrapper.js';
-import { IoAdd, IoTrashBinOutline } from "react-icons/io5";
+import { getUser } from '@/wrappers/user-wrapper';
+import { IoAdd, IoTrashBinOutline, IoPersonOutline } from "react-icons/io5";
import Unauthorized from "@/pages/Unauthorized";
+import SkeletonUser from "@/components/Skeletons/SkeletonUser";
+import useDelayedSkeleton from '@/hooks/useDelayedSkeleton';
const EditConversation = () => {
const { user } = useContext(UserContext);
@@ -39,7 +43,9 @@ const EditConversation = () => {
],
image: ''
});
+ const [students, setStudents] = useState([]);
const params = useParams();
+ const showSkeleton = useDelayedSkeleton(!allowRender);
useEffect(() => {
if (!params.id) {
@@ -72,6 +78,13 @@ const EditConversation = () => {
schedule: data.schedule
}))
}
+ const students = await Promise.all(
+ data.roster.map(async (studentId) => {
+ const studentRes = await getUser(`_id=${studentId}`);
+ return studentRes.data
+ })
+ );
+ setStudents(students);
setAllowRender(true);
} catch (error) {
console.error('Error fetching conversations:', error);
@@ -128,6 +141,21 @@ const EditConversation = () => {
}
}
+ const handleOpenOrCloseEnrollment = async () => {
+ try {
+ await updateConversation(conversationObj._id, {
+ isEnrollmentOpen: !conversationObj.isEnrollmentOpen
+ });
+ await fetchConversation();
+ } catch (error) {
+ console.error('Error changing enrollment status:', error);
+ setAlertMessage(`Error changing enrollment status`);
+ setTimeout(() => {
+ setAlertMessage("");
+ }, 4000);
+ }
+ }
+
const handleDeleteConversation = async () => {
try {
await deleteConversation(params.id);
@@ -307,6 +335,33 @@ const EditConversation = () => {
onClick={handleReset} />
+
+
+
List of Students
+ {allowRender && }
+
+
+
+
{students.length} enrolled
+
+
+ {allowRender
+ ? (students.map(student => (
+
+
+
+ ))
+ )
+ : showSkeleton && }
+
+
{isOpenImagePicker &&