197 lines
7.1 KiB
TypeScript
197 lines
7.1 KiB
TypeScript
import FormContainer from "@/components/FormContainer";
|
|
import Pagination from "@/components/Pagination";
|
|
import Table from "@/components/Table";
|
|
import TableSearch from "@/components/TableSearch";
|
|
import { getSupabaseClient } from "@/lib/supabase";
|
|
import { ITEM_PER_PAGE } from "@/lib/settings";
|
|
import { Tables } from "@/types/supabase";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { auth } from "@clerk/nextjs/server";
|
|
|
|
type SlotList = Tables<"SchoolTimetableSlot"> & {
|
|
schoolTimetable: (Tables<"SchoolTimetable"> & { academicYear: Tables<"AcademicYear"> | null }) | null;
|
|
};
|
|
|
|
const TimetableSlotListPage = async ({
|
|
searchParams,
|
|
}: {
|
|
searchParams: { [key: string]: string | undefined };
|
|
}) => {
|
|
const { sessionClaims } = auth();
|
|
const role = (sessionClaims?.metadata as { role?: string })?.role;
|
|
const teacherType = (sessionClaims?.metadata as { teacherType?: string })?.teacherType;
|
|
const schoolId = (sessionClaims?.metadata as { schoolId?: string })?.schoolId;
|
|
|
|
const canManageSchool =
|
|
role === "admin" ||
|
|
(role === "teacher" &&
|
|
(teacherType === "INDEPENDENT" || teacherType === "AGENCY"));
|
|
|
|
const columns = [
|
|
{
|
|
header: "Slot Name",
|
|
accessor: "name",
|
|
},
|
|
{
|
|
header: "Timetable",
|
|
accessor: "schoolTimetable",
|
|
className: "hidden md:table-cell",
|
|
},
|
|
{
|
|
header: "Start Time",
|
|
accessor: "startTime",
|
|
className: "hidden md:table-cell",
|
|
},
|
|
{
|
|
header: "End Time",
|
|
accessor: "endTime",
|
|
className: "hidden md:table-cell",
|
|
},
|
|
{
|
|
header: "Teaching Slot",
|
|
accessor: "isTeachingSlot",
|
|
className: "hidden md:table-cell",
|
|
},
|
|
...(canManageSchool
|
|
? [
|
|
{
|
|
header: "Actions",
|
|
accessor: "action",
|
|
},
|
|
]
|
|
: []),
|
|
];
|
|
|
|
const renderRow = (item: SlotList) => (
|
|
<tr
|
|
key={item.id}
|
|
className="border-b border-gray-200 even:bg-slate-50 text-sm hover:bg-lamaPurpleLight"
|
|
>
|
|
<td className="flex items-center gap-4 p-4">{item.name}</td>
|
|
<td className="hidden md:table-cell text-gray-600">
|
|
{item.schoolTimetable
|
|
? `${item.schoolTimetable.name}${item.schoolTimetable.academicYear ? ` (${item.schoolTimetable.academicYear.name})` : ""}`
|
|
: "—"}
|
|
</td>
|
|
<td className="hidden md:table-cell">{item.startTime}</td>
|
|
<td className="hidden md:table-cell">{item.endTime}</td>
|
|
<td className="hidden md:table-cell">
|
|
{item.isTeachingSlot ? "Yes" : "No"}
|
|
</td>
|
|
<td>
|
|
<div className="flex items-center gap-2">
|
|
{canManageSchool && (
|
|
<>
|
|
<FormContainer
|
|
table="schoolTimetableSlot"
|
|
type="update"
|
|
data={item}
|
|
/>
|
|
<FormContainer
|
|
table="schoolTimetableSlot"
|
|
type="delete"
|
|
id={item.id}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
|
|
const { page, ...queryParams } = searchParams;
|
|
|
|
const p = page ? parseInt(page) : 1;
|
|
const supabase = await getSupabaseClient();
|
|
|
|
const schoolTimetableId = queryParams.schoolTimetableId
|
|
? parseInt(queryParams.schoolTimetableId)
|
|
: undefined;
|
|
|
|
let query = supabase
|
|
.from("SchoolTimetableSlot")
|
|
.select("*, schoolTimetable:SchoolTimetable(id, name, academicYear:AcademicYear(name))", { count: "exact" })
|
|
.order("position", { ascending: true });
|
|
|
|
if (schoolId) {
|
|
query = query.eq("schoolId", schoolId);
|
|
}
|
|
if (schoolTimetableId && !Number.isNaN(schoolTimetableId)) {
|
|
query = query.eq("schoolTimetableId", schoolTimetableId);
|
|
}
|
|
|
|
if (queryParams) {
|
|
for (const [key, value] of Object.entries(queryParams)) {
|
|
if (value !== undefined) {
|
|
switch (key) {
|
|
case "search":
|
|
query = query.ilike("name", `%${value}%`);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// PAGINATION
|
|
query = query.range(ITEM_PER_PAGE * (p - 1), ITEM_PER_PAGE * p - 1);
|
|
|
|
const { data: rawData, count, error } = await query;
|
|
|
|
if (error) {
|
|
console.error("Error fetching timetable slots from Supabase:", error);
|
|
}
|
|
|
|
const data = (rawData || []) as unknown as SlotList[];
|
|
|
|
let filterTimetableName: string | null = null;
|
|
if (schoolTimetableId && !Number.isNaN(schoolTimetableId)) {
|
|
const { data: st } = await supabase
|
|
.from("SchoolTimetable")
|
|
.select("name, academicYear:AcademicYear(name)")
|
|
.eq("id", schoolTimetableId)
|
|
.single();
|
|
if (st) {
|
|
const ay = (st as { academicYear?: { name: string } }).academicYear;
|
|
filterTimetableName = ay ? `${st.name} (${ay.name})` : st.name;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white p-4 rounded-md flex-1 m-4 mt-0">
|
|
{/* TOP */}
|
|
{filterTimetableName && (
|
|
<div className="mb-2 text-sm text-gray-600 flex items-center gap-2 flex-wrap">
|
|
<span>Showing slots for: <strong>{filterTimetableName}</strong></span>
|
|
<Link href="/list/timeslots" className="text-blue-500 hover:underline">Show all slots</Link>
|
|
</div>
|
|
)}
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="hidden md:block text-lg font-semibold">Timetable Slots</h1>
|
|
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
|
|
<TableSearch />
|
|
<div className="flex items-center gap-4 self-end">
|
|
<button className="w-8 h-8 flex items-center justify-center rounded-full bg-lamaYellow">
|
|
<Image src="/filter.png" alt="" width={14} height={14} />
|
|
</button>
|
|
<button className="w-8 h-8 flex items-center justify-center rounded-full bg-lamaYellow">
|
|
<Image src="/sort.png" alt="" width={14} height={14} />
|
|
</button>
|
|
{role === "admin" && (
|
|
<FormContainer table="schoolTimetableSlot" type="create" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* LIST */}
|
|
<Table columns={columns} renderRow={renderRow} data={data} />
|
|
{/* PAGINATION */}
|
|
<Pagination page={p} count={count || 0} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TimetableSlotListPage;
|