'use client';
import React, { useState } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Card } from '../../card';
import { BiCalendarCheck, BiCalendarX } from 'react-icons/bi';
import { useAttendance } from '@/lib/api/student/queryHooks';
import { AttendanceRecord } from '@/lib/api/student/functions';
import { useTranslate } from '@/components/hooks/useTranslate';
export default function AttendanceCalendar() {
const translate = useTranslate();
// Initialize with current date
const [currentDate, setCurrentDate] = useState(new Date());
// Get current month and year for API call
const currentMonth = currentDate.getMonth() + 1; // API expects 1-12
const currentYear = currentDate.getFullYear();
// Fetch attendance data using the API
const {
data: attendanceData,
isLoading,
error,
} = useAttendance(currentMonth, currentYear);
// Format the current month display
const currentMonthDisplay = currentDate.toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
});
// Get current date for comparison
const today = new Date();
const isCurrentMonth =
currentDate.getMonth() === today.getMonth() &&
currentDate.getFullYear() === today.getFullYear();
// Navigation functions
const goToPreviousMonth = () => {
setCurrentDate((prevDate) => {
const newDate = new Date(prevDate);
newDate.setMonth(newDate.getMonth() - 1);
return newDate;
});
};
const goToNextMonth = () => {
setCurrentDate((prevDate) => {
const newDate = new Date(prevDate);
newDate.setMonth(newDate.getMonth() + 1);
return newDate;
});
};
// Create a map of attendance data by date for quick lookup
const attendanceMap = new Map<string, AttendanceRecord>();
if (attendanceData?.data?.attendance) {
attendanceData.data.attendance.forEach((record) => {
// Parse the date and create a key in YYYY-MM-DD format
const dateKey = record.get_date_original;
attendanceMap.set(dateKey, record);
});
}
// Generate calendar data dynamically based on current date
const generateCalendarData = (date: Date) => {
const year = date.getFullYear();
const month = date.getMonth();
// Get first day of current month and calculate starting day of week
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startDayOfWeek = (firstDay.getDay() + 6) % 7; // Convert Sunday=0 to Monday=0
const calendarData = [];
// Add previous month trailing days
const prevMonth = new Date(year, month - 1, 0);
const prevMonthDays = prevMonth.getDate();
for (let i = startDayOfWeek - 1; i >= 0; i--) {
calendarData.push({
day: prevMonthDays - i,
isCurrentMonth: false,
});
}
// Add current month days with real attendance data
const daysInMonth = lastDay.getDate();
for (let day = 1; day <= daysInMonth; day++) {
// Create date string in YYYY-MM-DD format for lookup
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(
day
).padStart(2, '0')}`;
const attendanceRecord = attendanceMap.get(dateStr);
let status = null;
let isHoliday = false;
if (attendanceRecord) {
// type: 1 = present, 0 = absent, 3 = holiday (don't count)
if (attendanceRecord.type === 1) {
status = 'present';
} else if (attendanceRecord.type === 0) {
status = 'absent';
} else if (attendanceRecord.type === 3) {
isHoliday = true;
}
// type: 3 (holiday) is not counted in attendance, so status remains null
}
calendarData.push({
day,
isCurrentMonth: true,
status,
isHoliday,
});
}
// Add next month leading days to fill the grid
const remainingDays = 42 - calendarData.length; // 6 weeks * 7 days
for (let day = 1; day <= remainingDays; day++) {
calendarData.push({
day,
isCurrentMonth: false,
});
}
return calendarData;
};
const calendarData = generateCalendarData(currentDate);
// Calculate attendance summary for current month
// Note: Holidays (type: 3) are not counted in attendance statistics
const currentMonthDays = calendarData.filter((day) => day.isCurrentMonth);
const presentCount = currentMonthDays.filter(
(day) => day.status === 'present'
).length;
const absentCount = currentMonthDays.filter(
(day) => day.status === 'absent'
).length;
const dayNames = [
translate('monday'),
translate('tuesday'),
translate('wednesday'),
translate('thursday'),
translate('friday'),
translate('saturday'),
translate('sunday'),
];
const getDayStyle = (dayData: {
isCurrentMonth: boolean;
status?: string | null;
isHoliday?: boolean;
}) => {
if (!dayData.isCurrentMonth) {
return 'text-gray-400 text-sm sm:text-lg';
}
if (dayData.status === 'present') {
return 'bg-[var(--secondary-color)] text-white rounded-full w-8 h-8 sm:w-10 sm:h-10 flex items-center justify-center text-sm sm:text-lg font-medium';
} else if (dayData.status === 'absent') {
return 'bg-[var(--fourth-color)] text-white rounded-full w-8 h-8 sm:w-10 sm:h-10 flex items-center justify-center text-sm sm:text-lg font-medium';
} else {
// Holidays and regular days both use default styling
return 'text-gray-900 text-sm sm:text-lg font-medium';
}
};
const formatDay = (day: number) => {
return day.toString().padStart(2, '0');
};
return (
<Card className="mx-auto bg-white rounded-[12px] border border-gray-200 shadow-none overflow-hidden">
{/* Header - responsive padding */}
<h1 className="text-xl font-medium text-gray-900 p-4 sm:p-6 border-b border-gray-200">
{translate('attendance')}
</h1>
{/* Loading State */}
{isLoading && (
<div className="p-4 sm:p-6 text-center">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--primary-color)]"></div>
<p className="mt-2 text-gray-600">
{translate('loadingAttendanceData')}
</p>
</div>
)}
{/* Error State */}
{error && (
<div className="p-4 sm:p-6 text-center">
<p className="text-red-600">
{translate('failedToLoadAttendanceData')}
</p>
</div>
)}
{/* Calendar Container - responsive padding */}
{!isLoading && !error && (
<div className="bg-white p-4 sm:p-6">
<div className="bg-[var(--light-primary-color)] rounded-[16px] p-3 sm:p-4 mb-4 sm:mb-6">
{/* Month Navigation - responsive layout */}
{/* RTL-aware: Icons automatically flip direction using CSS transform */}
<div className="flex items-center justify-between mb-4 sm:mb-6 bg-white p-3 sm:p-4 rounded-[16px]">
{/* Previous Month Button */}
{/* Icon rotates 180deg in RTL to point in correct direction */}
<button
onClick={goToPreviousMonth}
className="p-2 border border-[var(--primary-color)] rounded-[4px] hover:bg-gray-100 transition-colors"
aria-label={translate('previousMonth')}
>
<ChevronLeft className="w-4 h-4 sm:w-5 sm:h-5 text-[var(--primary-color)] rtl:rotate-180" />
</button>
{/* Current Month Display */}
<h2 className="text-sm sm:text-lg font-medium text-gray-700 bg-[var(--light-primary-color)] p-2 sm:p-3 rounded-[16px] text-center">
{currentMonthDisplay}
</h2>
{/* Next Month Button */}
{/* Icon rotates 180deg in RTL to point in correct direction */}
<button
onClick={goToNextMonth}
disabled={isCurrentMonth}
className={`p-2 border rounded-[4px] transition-colors ${
isCurrentMonth
? 'border-gray-300 opacity-50 cursor-not-allowed'
: 'border-[var(--primary-color)] hover:bg-gray-100 cursor-pointer'
}`}
style={{ cursor: isCurrentMonth ? 'not-allowed' : 'pointer' }}
aria-label={translate('nextMonth')}
>
<ChevronRight
className={`w-4 h-4 sm:w-5 sm:h-5 rtl:rotate-180 ${
isCurrentMonth
? 'text-gray-400'
: 'text-[var(--primary-color)]'
}`}
/>
</button>
</div>
{/* Day Headers - responsive grid and text */}
<div className="grid grid-cols-7 gap-1 sm:gap-4 bg-white py-3 sm:py-6 rounded-[16px]">
{dayNames.map((day: string) => (
<div
key={day}
className="text-center text-gray-900 text-sm sm:text-xl font-normal"
>
{day}
</div>
))}
{calendarData.map((dayData, index) => (
<div
key={index}
className="flex justify-center text-sm sm:text-xl font-normal"
>
<div className={getDayStyle(dayData)}>
{formatDay(dayData.day)}
</div>
</div>
))}
</div>
</div>
{/* Summary Cards - responsive layout */}
<div className="flex flex-wrap gap-3 sm:gap-4">
{/* Total Present */}
<div className="flex-1 bg-white rounded-[16px] p-3 sm:p-4 border border-[var(--secondary-color)]">
<div className="flex items-center gap-2 sm:gap-3">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-emerald-100 rounded-xl flex items-center justify-center">
<BiCalendarCheck className="w-5 h-5 sm:w-6 sm:h-6 text-[var(--secondary-color)]" />
</div>
<div className="flex-1">
<p className="text-gray-900 text-sm sm:text-base font-medium">
{translate('totalPresent')}
</p>
</div>
<div className="bg-[var(--secondary-color)] text-white rounded-xl p-2 w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center">
<span className="text-2xl sm:text-3xl font-medium">
{presentCount}
</span>
</div>
</div>
</div>
{/* Total Absent */}
<div className="flex-1 bg-white rounded-[16px] p-3 sm:p-4 border border-[var(--fourth-color)]">
<div className="flex items-center gap-2 sm:gap-3">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-red-100 rounded-xl flex items-center justify-center">
<BiCalendarX className="w-5 h-5 sm:w-6 sm:h-6 text-[var(--fourth-color)]" />
</div>
<div className="flex-1">
<p className="text-gray-900 text-sm sm:text-base font-medium">
{translate('totalAbsent')}
</p>
</div>
<div className="bg-[var(--fourth-color)] text-white rounded-xl p-2 w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center">
<span className="text-2xl sm:text-3xl font-medium">
{absentCount}
</span>
</div>
</div>
</div>
</div>
</div>
)}
</Card>
);
}