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
93 changes: 93 additions & 0 deletions client/src/api/appointments/regularStudents.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { apiProtected as api } from "../api";
import {
Appointment,
WeeklyScheduleSlot,
} from "../../types/appointments.types";

export const setRegularStudentApi = async (
appointmentId: string,
): Promise<Appointment> => {
const response = await api.post(
`/api/appointments/${appointmentId}/set-regular`,
);
return response.data;
};

export const removeRegularStudentApi = async (
appointmentId: string,
): Promise<Appointment> => {
const response = await api.delete(
`/api/appointments/${appointmentId}/remove-regular`,
);
return response.data;
};

export const updateWeeklyScheduleApi = async (
appointmentId: string,
weeklySchedule: WeeklyScheduleSlot[],
): Promise<Appointment> => {
const response = await api.put(
`/api/appointments/${appointmentId}/weekly-schedule`,
{
weeklySchedule,
},
);
return response.data;
};

export const getRegularStudentsApi = async (
page?: number,
limit?: number,
): Promise<{
appointments: Appointment[];
total: number;
totalPages: number;
}> => {
const params = new URLSearchParams();
if (page) params.append("page", page.toString());
if (limit) params.append("limit", limit.toString());

const response = await api.get(
`/api/appointments/regular/students?${params.toString()}`,
);
return response.data;
};

export const getRegularTeachersApi = async (
page?: number,
limit?: number,
): Promise<{
appointments: Appointment[];
total: number;
totalPages: number;
}> => {
const params = new URLSearchParams();
if (page) params.append("page", page.toString());
if (limit) params.append("limit", limit.toString());

const response = await api.get(
`/api/appointments/regular/teachers?${params.toString()}`,
);
return response.data;
};

export const getAllRegularStudentsSlotsApi = async (): Promise<
WeeklyScheduleSlot[]
> => {
const response = await api.get(
`/api/appointments/regular/students?limit=1000`,
);
const appointments: Appointment[] = response.data.appointments;

const allSlots: WeeklyScheduleSlot[] = [];
appointments.forEach((appointment) => {
if (
appointment.weeklySchedule &&
Array.isArray(appointment.weeklySchedule)
) {
allSlots.push(...appointment.weeklySchedule);
}
});

return allSlots;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { useState } from "react";
import { Button } from "../ui/button/Button";
import Cross from "../icons/Cross";
import { SelectComponent } from "../ui/select/Select";
import { WeeklyScheduleSlot } from "../../types/appointments.types";

interface RegularStudentScheduleModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (schedule: WeeklyScheduleSlot[]) => void;
studentName: string;
initialSchedule?: WeeklyScheduleSlot[];
occupiedSlots?: WeeklyScheduleSlot[];
}

const DAYS = [
{ value: "Monday", label: "Monday" },
{ value: "Tuesday", label: "Tuesday" },
{ value: "Wednesday", label: "Wednesday" },
{ value: "Thursday", label: "Thursday" },
{ value: "Friday", label: "Friday" },
{ value: "Saturday", label: "Saturday" },
{ value: "Sunday", label: "Sunday" },
];

const HOURS = Array.from({ length: 17 }, (_, i) => ({
value: (i + 7).toString(),
label: `${i + 7}:00`,
}));

export const RegularStudentScheduleModal = ({
isOpen,
onClose,
onSave,
studentName,
initialSchedule = [],
occupiedSlots = [],
}: RegularStudentScheduleModalProps) => {
const [savedSlots, setSavedSlots] =
useState<WeeklyScheduleSlot[]>(initialSchedule);
const [currentDay, setCurrentDay] = useState<string>("Monday");
const [currentHour, setCurrentHour] = useState<number>(7);

if (!isOpen) return null;

const isDuplicate = savedSlots.some(
(slot) => slot.day === currentDay && slot.hour === currentHour,
);

const isOccupied = occupiedSlots.some(
(slot) => slot.day === currentDay && slot.hour === currentHour,
);

const cannotAdd = isDuplicate || isOccupied;

const handleAddLesson = () => {
if (cannotAdd) return;

const newSlot = { day: currentDay, hour: currentHour };
const updatedSlots = [...savedSlots, newSlot];
setSavedSlots(updatedSlots);
setCurrentDay("Monday");
setCurrentHour(7);
};

const handleRemoveSlot = (index: number) => {
setSavedSlots(savedSlots.filter((_, i) => i !== index));
};

const handleSave = () => {
onSave(savedSlots);
onClose();
};

const handleCancel = () => {
setSavedSlots(initialSchedule);
setCurrentDay("Monday");
setCurrentHour(7);
onClose();
};

return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-[#2A2433] rounded-2xl p-10 w-full max-w-5xl max-h-[85vh] flex flex-col">
<div className="flex items-center justify-between mb-10">
<h2 className="text-3xl font-bold text-white">
Weekly Schedule for {studentName}
</h2>
<button
onClick={handleCancel}
className="text-white hover:text-purple-400 transition-colors"
>
<Cross width={14} height={14} />
</button>
</div>

<div className="flex-1 overflow-y-auto pr-2">
{savedSlots.length > 0 && (
<div className="mb-8">
<h3 className="text-white text-lg font-semibold mb-4">
Scheduled Lessons ({savedSlots.length})
</h3>
<div className="space-y-3">
{savedSlots.map((slot, index) => (
<div
key={index}
className="bg-[#1E1D28] rounded-lg px-6 py-4 flex items-center justify-between"
>
<span className="text-white text-base">
{slot.day}, {slot.hour}:00
</span>
<button
onClick={() => handleRemoveSlot(index)}
className="text-red-400 hover:text-red-300 text-sm font-medium px-3 py-1 rounded hover:bg-red-400/10 transition-colors"
>
Remove
</button>
</div>
))}
</div>
</div>
)}

<div className="space-y-6">
<h3 className="text-white text-xl font-semibold">Add Lesson</h3>

<div className="bg-[#1E1D28] rounded-xl p-10">
<div className="grid grid-cols-2 gap-10 mb-8">
<div>
<label className="block text-gray-300 text-lg font-medium mb-5">
Day
</label>
<SelectComponent
options={DAYS}
value={currentDay}
onChange={(value) => setCurrentDay(value)}
placeholder="Select day"
/>
</div>

<div>
<label className="block text-gray-300 text-lg font-medium mb-5">
Time
</label>
<SelectComponent
options={HOURS}
value={currentHour.toString()}
onChange={(value) => setCurrentHour(parseInt(value))}
placeholder="Select time"
/>
</div>
</div>

{isDuplicate && (
<div className="mb-4 px-4 py-3 bg-red-500/20 border border-red-500/50 rounded-lg">
<p className="text-red-300 text-sm">
This time slot is already added to the schedule
</p>
</div>
)}

{isOccupied && !isDuplicate && (
<div className="mb-4 px-4 py-3 bg-red-500/20 border border-red-500/50 rounded-lg">
<p className="text-red-300 text-sm">
This time slot is already occupied by another Regular
Student
</p>
</div>
)}

<Button
onClick={handleAddLesson}
variant="secondary"
className="w-full text-base py-4"
disabled={cannotAdd}
>
{isDuplicate
? "Time Slot Already Added"
: isOccupied
? "Time Slot Occupied"
: "+ Add to Schedule"}
</Button>
</div>
</div>
</div>

<div className="mt-6 pt-6 border-t border-gray-700 flex justify-end gap-5">
<Button
onClick={handleCancel}
variant="secondary"
className="px-10 py-3 text-base"
>
Cancel
</Button>
<Button
onClick={handleSave}
variant="primary"
className="px-10 py-3 text-base"
>
Save Schedule
</Button>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { WeeklyScheduleSlot } from "../../types/appointments.types";
import { Button } from "../ui/button/Button";
import Cross from "../icons/Cross";

interface ViewScheduleModalProps {
isOpen: boolean;
onClose: () => void;
teacherName: string;
schedule: WeeklyScheduleSlot[];
}

export const ViewScheduleModal = ({
isOpen,
onClose,
teacherName,
schedule,
}: ViewScheduleModalProps) => {
if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-[#2A2433] rounded-2xl p-10 w-full max-w-4xl max-h-[85vh] flex flex-col">
<div className="flex items-center justify-between mb-10">
<h2 className="text-3xl font-bold text-white">
Weekly Schedule with {teacherName}
</h2>
<button
onClick={onClose}
className="text-white hover:text-purple-400 transition-colors"
>
<Cross width={14} height={14} />
</button>
</div>

<div className="flex-1 overflow-y-auto pr-2">
{schedule.length === 0 ? (
<div className="text-center py-12">
<p className="text-gray-400 text-lg">No schedule set yet</p>
<p className="text-gray-500 text-sm mt-2">
Your teacher hasn&apos;t set up a weekly schedule yet
</p>
</div>
) : (
<div>
<h3 className="text-white text-lg font-semibold mb-6">
Your Lessons ({schedule.length} per week)
</h3>
<div className="space-y-4">
{schedule.map((slot, index) => (
<div
key={index}
className="bg-[#1E1D28] rounded-lg px-8 py-5 flex items-center"
>
<div className="flex-1">
<span className="text-white text-lg font-medium">
{slot.day}
</span>
<span className="text-gray-400 text-lg ml-4">
at {slot.hour}:00
</span>
</div>
</div>
))}
</div>
</div>
)}
</div>

<div className="mt-8 pt-6 border-t border-gray-700 flex justify-center">
<Button
onClick={onClose}
variant="primary"
className="px-12 py-3 text-base"
>
Close
</Button>
</div>
</div>
</div>
);
};
Loading
Loading