Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/components/Schedule.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 (
<div className="bg-blue-100 rounded-xs sm:rounded-sm border-[0.5px] border-gray-200 p-1 sm:p-3 mb-1 sm:mb-2">
<p className="text-blue-700 text-[0.75rem] sm:text-[0.875rem] text-balance">{classObj.startTime || "N/A"}-{classObj.endTime || "N/A"}</p>
<p className="text-blue-700 text-[0.75rem] sm:text-[0.875rem] text-balance">
{to12HourFormat(convertedStartTimeEST.time)}-{to12HourFormat(convertedEndTimeEST.time)} (EST)
</p>
<p className="text-blue-700 text-[0.75rem] sm:text-[0.875rem] text-balance">
{convertedStartTimeIST.time}-{convertedEndTimeIST.time} (Ist)
</p>
<p
title={t('level_num', {
num: typeof classObj.level === 'number'
Expand Down
37 changes: 25 additions & 12 deletions src/components/UserItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ import { useState, useEffect } from "react";
import { LuPencil } from "react-icons/lu";
import { toTitleCase } from '@/utils/formatters';

const UserItem = ({ userData, classes = [], privilege, isShowClass }) => {
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 <></>;
Expand All @@ -38,13 +47,17 @@ const UserItem = ({ userData, classes = [], privilege, isShowClass }) => {
<div>
{userData.privilege !== "instructor" && isShowClass && (
<p className="text-gray-500 text-sm">
{/* 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"
)}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/dashboards/InstructorView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 1 addition & 5 deletions src/pages/dashboards/admin/AdminInstructors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -75,7 +71,7 @@ const AdminInstructors = () => {
{allowRender
? filteredUsers.map((userData, userIndex) => (
<Link key={userIndex} href={`/admin/user/${encodeURIComponent(userData._id)}`}>
<UserItem userData={userData} privilege="admin" classes={classes} />
<UserItem userData={userData} privilege="admin" />
</Link>
))
: showSkeleton && <SkeletonUser count={9} />}
Expand Down
3 changes: 1 addition & 2 deletions src/pages/dashboards/admin/AdminSchedule.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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}`}
</button>
))}
</Dropdown>
Expand Down
42 changes: 27 additions & 15 deletions src/pages/dashboards/admin/AdminStudents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand All @@ -44,29 +44,23 @@ 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) => {
const classRef = await getStudentsClasses(student._id);
const classes = await Promise.all(
(classRef.enrolledClasses || []).map(async (classID) => {
const classData = await getClassById(classID);
allLevels.add(classData.level);
return classData;
})
);

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);
}

Expand Down Expand Up @@ -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 =>
Expand Down Expand Up @@ -141,15 +136,32 @@ const AdminStudents = () => {
All Levels
</button>

{/* Level filter for numbered levels */}
{levels.map((level) => (
<button
key={level}
className={`w-full text-left px-4 py-2 text-base font-normal text-black hover:bg-gray-100 ${currFilter === level ? 'text-blue-500 bg-gray-50' : 'text-gray-700'}`}
onClick={() => handleOptionClick(level)} /* Handle click here */
className={`w-full text-left px-4 py-2 text-base font-normal text-black hover:bg-gray-100 ${currFilter === level.level ? 'text-blue-500 bg-gray-50' : 'text-gray-700'}`}
onClick={() => handleOptionClick(level.level)}
>
Level {level}
Level {level.level}
</button>
))}

{/* Level filter for supp classes */}
<button
key="conversation"
className={`w-full text-left px-4 py-2 text-base font-normal text-black hover:bg-gray-100 ${currFilter === "conversation" ? 'text-blue-500 bg-gray-50' : 'text-gray-700'}`}
onClick={() => handleOptionClick("conversation")}
>
Conversation
</button>
<button
key="ietls"
className={`w-full text-left px-4 py-2 text-base font-normal text-black hover:bg-gray-100 ${currFilter === "ielts" ? 'text-blue-500 bg-gray-50' : 'text-gray-700'}`}
onClick={() => handleOptionClick("ielts")}
>
IELTS
</button>
</Dropdown>
</div>
<div className="text-indigo-900 inline-flex items-center gap-x-2">
Expand All @@ -160,7 +172,7 @@ const AdminStudents = () => {
{allowRender
? filteredUsers.map((userData) => (
<Link key={userData._id} to={`/admin/user/${encodeURIComponent(userData._id)}`}>
<UserItem key={userData._id} privilege="admin" userData={userData} classes={classes} isShowClass />
<UserItem key={userData._id} privilege="admin" userData={userData} isShowClass />
</Link>
))
: showSkeleton && <SkeletonUser count={12} />}
Expand Down
9 changes: 2 additions & 7 deletions src/pages/dashboards/admin/editPages/EditClass.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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: '',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -364,7 +360,6 @@ const EditClass = () => {
<UserItem
userData={student}
privilege="admin"
classes={classes}
/>
</Link>
))
Expand Down
Loading