import React, { useState, useEffect } from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import { LinearGradient } from "expo-linear-gradient"; import { Ionicons } from "@expo/vector-icons"; import { theme } from "../styles/theme"; interface AttendanceRecord { id: string; checkInTime: string; checkOutTime?: string | null; duration?: number | null; } interface AttendanceCalendarProps { attendanceRecords: AttendanceRecord[]; } export function AttendanceCalendar({ attendanceRecords, }: AttendanceCalendarProps) { const [currentMonth, setCurrentMonth] = useState(new Date()); const [calendarDays, setCalendarDays] = useState< Array<{ date: Date | null; hasAttendance: boolean; isToday: boolean }> >([]); useEffect(() => { generateCalendar(currentMonth); }, [currentMonth, attendanceRecords]); const generateCalendar = (month: Date) => { const year = month.getFullYear(); const monthIndex = month.getMonth(); // Get first day of month and number of days const firstDay = new Date(year, monthIndex, 1); const lastDay = new Date(year, monthIndex + 1, 0); const daysInMonth = lastDay.getDate(); const startingDayOfWeek = firstDay.getDay(); // Create attendance lookup set const attendanceDates = new Set( attendanceRecords.map((record) => new Date(record.checkInTime).toDateString(), ), ); const today = new Date().toDateString(); // Build calendar array const days: Array<{ date: Date | null; hasAttendance: boolean; isToday: boolean; }> = []; // Add empty cells for days before month starts for (let i = 0; i < startingDayOfWeek; i++) { days.push({ date: null, hasAttendance: false, isToday: false }); } // Add days of the month for (let day = 1; day <= daysInMonth; day++) { const date = new Date(year, monthIndex, day); const dateString = date.toDateString(); days.push({ date, hasAttendance: attendanceDates.has(dateString), isToday: dateString === today, }); } setCalendarDays(days); }; const goToPreviousMonth = () => { setCurrentMonth( new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1), ); }; const goToNextMonth = () => { setCurrentMonth( new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1), ); }; const monthName = currentMonth.toLocaleDateString("en-US", { month: "long", year: "numeric", }); return ( {/* Header */} Attendance Calendar {/* Month Navigation */} {monthName} {/* Day Headers */} {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => ( {day} ))} {/* Calendar Grid */} {calendarDays.map((day, index) => ( {day.date ? ( {day.date.getDate()} {day.hasAttendance && } ) : ( )} ))} {/* Legend */} Attended Today ); } const styles = StyleSheet.create({ container: { paddingHorizontal: 20, marginBottom: 20, }, card: { borderRadius: theme.borderRadius["2xl"], padding: 20, }, header: { marginBottom: 16, }, title: { fontSize: theme.typography.fontSize.xl, fontWeight: theme.typography.fontWeight.bold, color: theme.colors.gray800, }, monthNav: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 16, paddingHorizontal: 8, }, navButton: { padding: 8, }, monthText: { fontSize: theme.typography.fontSize.lg, fontWeight: theme.typography.fontWeight.semibold, color: theme.colors.gray700, }, dayHeaders: { flexDirection: "row", marginBottom: 8, }, dayHeader: { flex: 1, textAlign: "center", fontSize: theme.typography.fontSize.xs, fontWeight: theme.typography.fontWeight.semibold, color: theme.colors.gray500, }, calendarGrid: { flexDirection: "row", flexWrap: "wrap", }, dayCell: { width: `${100 / 7}%`, aspectRatio: 1, padding: 2, }, dayContent: { flex: 1, justifyContent: "center", alignItems: "center", borderRadius: theme.borderRadius.md, position: "relative", }, todayContent: { backgroundColor: theme.colors.primaryLight, borderWidth: 1, borderColor: theme.colors.primary, }, attendanceContent: { backgroundColor: theme.colors.successLight, }, emptyCell: { flex: 1, }, dayText: { fontSize: theme.typography.fontSize.sm, color: theme.colors.gray700, fontWeight: theme.typography.fontWeight.medium, }, todayText: { color: theme.colors.white, fontWeight: theme.typography.fontWeight.bold, }, attendanceText: { color: theme.colors.white, fontWeight: theme.typography.fontWeight.bold, }, attendanceDot: { position: "absolute", bottom: 2, width: 4, height: 4, borderRadius: 2, backgroundColor: theme.colors.white, }, legend: { flexDirection: "row", justifyContent: "center", gap: 20, marginTop: 16, paddingTop: 16, borderTopWidth: 1, borderTopColor: theme.colors.gray200, }, legendItem: { flexDirection: "row", alignItems: "center", gap: 6, }, legendDotAttendance: { width: 12, height: 12, borderRadius: 6, backgroundColor: theme.colors.successLight, }, legendDotToday: { width: 12, height: 12, borderRadius: 6, backgroundColor: theme.colors.primaryLight, borderWidth: 1, borderColor: theme.colors.primary, }, legendText: { fontSize: theme.typography.fontSize.xs, color: theme.colors.gray600, }, });