Files
classroomio/apps/dashboard/src/routes/courses/[id]/attendance/+page.svelte
T

225 lines
7.2 KiB
Svelte
Raw Normal View History

<script lang="ts">
import Box from '$lib/components/Box/index.svelte';
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
import { getLectureNo } from '$lib/components/Course/function.js';
import { course, group } from '$lib/components/Course/store';
2025-11-01 15:49:29 +01:00
import { CourseContainer } from '$lib/components/CourseContainer';
import { PageBody, PageNav } from '$lib/components/Page';
import { snackbar } from '$lib/components/Snackbar/store';
import { ROLE } from '$lib/utils/constants/roles';
import { t } from '$lib/utils/functions/translations';
import { takeAttendance } from '$lib/utils/services/attendance';
import { globalStore } from '$lib/utils/store/app';
import { attendance } from '$lib/utils/store/attendance';
import { profile } from '$lib/utils/store/user';
import type { GroupPerson, Lesson } from '$lib/utils/types/index';
2023-08-07 09:53:07 +00:00
import {
Checkbox,
Pagination,
Search,
2023-08-07 09:53:07 +00:00
StructuredList,
StructuredListBody,
StructuredListCell,
StructuredListHead,
StructuredListRow
2023-08-07 09:53:07 +00:00
} from 'carbon-components-svelte';
2025-11-01 15:49:29 +01:00
import BadgeXIcon from '@lucide/svelte/icons/badge-x';
2021-09-04 22:33:35 +03:00
2025-11-01 15:49:29 +01:00
let { data = $bindable() } = $props();
2023-10-22 09:47:13 +01:00
interface CourseData {
attendance: {
student_id: string;
lesson_id: string;
is_present: boolean;
id: number;
}[];
}
2021-09-04 22:33:35 +03:00
2025-11-01 15:49:29 +01:00
let hasFetched = $state(false);
const students: GroupPerson[] = $derived(
$globalStore.isStudent
? $group.people.filter((person) => !!person.profile && person.profile.id === $profile.id)
: $group.people.filter((person) => !!person.profile && person.role_id === ROLE.STUDENT)
);
let searchValue = $state('');
2021-09-05 22:16:42 +03:00
function setAttendance(courseData: CourseData) {
2021-09-05 22:16:42 +03:00
for (const attendanceItem of courseData.attendance) {
const { student_id, lesson_id, is_present, id } = attendanceItem;
if (!$attendance[student_id]) {
$attendance[student_id] = {
[lesson_id]: {
id,
2023-08-07 09:53:07 +00:00
is_present
}
2021-09-05 22:16:42 +03:00
};
} else {
$attendance[student_id] = {
...$attendance[student_id],
[lesson_id]: {
id,
2023-08-07 09:53:07 +00:00
is_present
}
2021-09-05 22:16:42 +03:00
};
}
}
}
function handleAttendanceChange(e: any, student: GroupPerson, lesson: Lesson) {
if ($globalStore.isStudent) return;
2021-10-22 14:55:33 +03:00
const attendanceItem = $attendance[student.id]
? $attendance[student.id][lesson.id] || { id: undefined }
: { id: undefined };
2021-10-22 14:55:33 +03:00
2021-11-13 21:29:26 +02:00
/*
This validation is useless. We shouldn't assume we already have an attendance item id
because it might be a new attendance.
2021-11-02 09:52:05 +02:00
if (!attendanceItem.id) {
2023-09-27 05:35:06 +01:00
snackbar.error("Something isn't right. Please reload and take attendance again");
2021-10-22 14:55:33 +03:00
console.error(`Attendance Id Missing`, courseId, student.id, lesson.id);
return;
}
2021-11-13 21:29:26 +02:00
*/
2021-10-22 14:55:33 +03:00
const _data = {
2021-10-22 14:55:33 +03:00
id: attendanceItem.id,
student_id: student.id,
is_present: e.target.checked,
lesson_id: lesson.id,
2023-08-07 09:53:07 +00:00
course_id: data.courseId
2021-10-22 14:55:33 +03:00
};
takeAttendance(_data).then((res) => {
2021-10-22 14:55:33 +03:00
if (res.error) {
console.error(`res.error`, res.error);
2024-04-19 05:45:48 +01:00
snackbar.error('snackbar.attendance.error.reload');
2021-10-22 14:55:33 +03:00
} else {
2021-11-13 21:29:26 +02:00
// console.log('res', JSON.stringify(res));
2021-10-22 14:55:33 +03:00
const { id, is_present } = res.data[0];
if ($attendance[student.id]) {
$attendance[student.id][lesson.id] = {
id,
2023-08-07 09:53:07 +00:00
is_present
2021-10-22 14:55:33 +03:00
};
} else {
$attendance[student.id] = {
[lesson.id]: {
id,
2023-08-07 09:53:07 +00:00
is_present
}
2021-10-22 14:55:33 +03:00
};
}
}
});
}
2023-08-07 09:53:07 +00:00
// function for the searchbar
function searchStudents(query: string, _students: GroupPerson[]) {
2023-08-07 09:53:07 +00:00
const lowercaseQuery = query.toLowerCase();
2025-11-01 15:49:29 +01:00
return _students.filter((student) => student.profile?.fullname?.toLowerCase()?.includes(lowercaseQuery));
2023-08-07 09:53:07 +00:00
}
2025-11-01 15:49:29 +01:00
async function firstRender(courseId?: string) {
if (!courseId || hasFetched) return;
hasFetched = true;
if (!Object.keys($attendance).length) {
setAttendance($course);
2021-09-06 11:10:54 +03:00
}
2025-11-01 15:49:29 +01:00
return;
}
2021-09-05 22:16:42 +03:00
2025-11-01 15:49:29 +01:00
$effect(() => {
firstRender($course.id);
});
2021-09-04 22:33:35 +03:00
</script>
2025-11-01 15:49:29 +01:00
<CourseContainer courseId={data.courseId}>
2024-04-19 05:45:48 +01:00
<PageNav title={$t('course.navItem.attendance.title')} />
<PageBody width="w-full max-w-6xl md:w-11/12">
<section class="mx-2 my-5 flex items-center lg:mx-9">
2025-11-01 15:49:29 +01:00
<div class="flex w-full flex-col items-start justify-between gap-2 lg:flex-row lg:items-center">
<div class="flex">
<p class="mr-5 flex items-center">
2024-04-19 05:45:48 +01:00
<Checkbox checked disabled />
{$t('course.navItem.attendance.present')}
</p>
<p class="flex items-center">
2024-04-19 05:45:48 +01:00
<Checkbox disabled />
{$t('course.navItem.attendance.absent')}
</p>
</div>
<div>
<Search
class="dark:text-slate-950"
2024-04-19 05:45:48 +01:00
placeholder={$t('course.navItem.attendance.search_students')}
bind:value={searchValue}
/>
</div>
2023-08-07 09:53:07 +00:00
</div>
</section>
2023-08-07 09:53:07 +00:00
<section class="mx-2 my-5 lg:mx-9">
<StructuredList class="relative m-0">
<!-- Moved the lesson headers outside the students loop -->
<StructuredListHead class="bg-primary-100">
<StructuredListRow head class="mx-7">
2024-04-19 05:45:48 +01:00
<StructuredListCell head class="text-primary-600 py-3"
>{$t('course.navItem.attendance.student')}</StructuredListCell
>
2025-11-01 15:49:29 +01:00
{#each $lessons as _lesson, index}
<StructuredListCell head class="text-primary-600 py-3"
2025-11-01 15:49:29 +01:00
>{$t('course.navItem.attendance.lesson')} 0{getLectureNo(index + 1)}</StructuredListCell
>
2023-08-07 09:53:07 +00:00
{/each}
</StructuredListRow>
</StructuredListHead>
{#each searchStudents(searchValue, students) as student}
<StructuredListBody>
<StructuredListRow>
<StructuredListCell>
<div class="flex w-1/4 items-center">
<p class="font-semibold dark:text-white">
{student.profile.fullname}
</p>
</div>
</StructuredListCell>
{#each $lessons as lesson}
<StructuredListCell class="">
<Checkbox
class={$globalStore.isStudent ? 'cursor-not-allowed' : ''}
disabled={$globalStore.isStudent}
checked={$attendance[student.id]
? $attendance[student.id][lesson.id]
? $attendance[student.id][lesson.id].is_present
: false
: false}
on:change={(e) => handleAttendanceChange(e, student, lesson)}
/>
</StructuredListCell>
{/each}
</StructuredListRow>
</StructuredListBody>
{/each}
</StructuredList>
{#if students.length === 0}
<Box className="h-[300px] w-full">
2025-11-01 15:49:29 +01:00
<BadgeXIcon size={48} />
<h3 class="text-center text-3xl text-gray-500 dark:text-white">
2024-04-19 05:45:48 +01:00
{$t('course.navItem.attendance.no_student')}
</h3>
2021-09-11 19:27:11 +03:00
</Box>
{/if}
{#if students.length !== 0}
<Pagination totalItems={10} pageSizes={[10, 15, 20]} />
{/if}
</section>
</PageBody>
2023-08-07 09:53:07 +00:00
</CourseContainer>