85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
export type AttendanceExperience = 'teacher' | 'admin';
|
|
export type SchedulePhase = 'upcoming' | 'ongoing' | 'ended';
|
|
|
|
import { getRoleDomains } from '../../auth/menu-policy';
|
|
|
|
export function getAttendanceExperience(
|
|
permissions: readonly string[],
|
|
roles: readonly string[],
|
|
): AttendanceExperience {
|
|
const domains = getRoleDomains(roles, permissions);
|
|
if (
|
|
permissions.includes('attendance:manage') ||
|
|
domains.has('academic') ||
|
|
domains.has('super')
|
|
) {
|
|
return 'admin';
|
|
}
|
|
return 'teacher';
|
|
}
|
|
|
|
function toMinuteOfDay(time: string): number {
|
|
const [hour = 0, minute = 0] = time.split(':').map(Number);
|
|
return hour * 60 + minute;
|
|
}
|
|
|
|
export function getSchedulePhase(
|
|
startTime: string,
|
|
endTime: string,
|
|
now = new Date(),
|
|
): SchedulePhase {
|
|
const current = now.getHours() * 60 + now.getMinutes();
|
|
if (current < toMinuteOfDay(startTime)) return 'upcoming';
|
|
if (current <= toMinuteOfDay(endTime)) return 'ongoing';
|
|
return 'ended';
|
|
}
|
|
|
|
export function canPullAttendance(phase: SchedulePhase): boolean {
|
|
return phase !== 'upcoming';
|
|
}
|
|
|
|
export interface AttendanceSummary {
|
|
total: number;
|
|
present: number;
|
|
late: number;
|
|
absent: number;
|
|
leave: number;
|
|
pending: number;
|
|
}
|
|
|
|
export function summarizeAttendance(records: readonly { status: string }[]): AttendanceSummary {
|
|
const summary: AttendanceSummary = {
|
|
total: records.length,
|
|
present: 0,
|
|
late: 0,
|
|
absent: 0,
|
|
leave: 0,
|
|
pending: 0,
|
|
};
|
|
for (const record of records) {
|
|
if (record.status in summary && record.status !== 'total') {
|
|
summary[record.status as Exclude<keyof AttendanceSummary, 'total'>] += 1;
|
|
}
|
|
}
|
|
return summary;
|
|
}
|
|
|
|
export interface LessonCheckinSummary {
|
|
total: number;
|
|
checkedIn: number;
|
|
notCheckedIn: number;
|
|
}
|
|
|
|
export function summarizeLessonCheckins(
|
|
records: readonly { status: string }[],
|
|
): LessonCheckinSummary {
|
|
const checkedIn = records.filter(
|
|
(record) => record.status === 'present' || record.status === 'late',
|
|
).length;
|
|
return {
|
|
total: records.length,
|
|
checkedIn,
|
|
notCheckedIn: records.length - checkedIn,
|
|
};
|
|
}
|