import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, ScrollView, Alert, } from "react-native"; import { useState, useEffect, useRef } from "react"; import { useAuth } from "@clerk/clerk-expo"; import { LinearGradient } from "expo-linear-gradient"; import { Ionicons } from "@expo/vector-icons"; import { attendanceApi, Attendance } from "../../api/attendance"; import { AttendanceCalendar } from "../../components/AttendanceCalendar"; import { theme } from "../../styles/theme"; import { Animated } from "react-native"; import { getErrorMessage } from "../../utils/error-helpers"; import log from "../../utils/logger"; export default function AttendanceScreen() { const { getToken, userId } = useAuth(); const [loading, setLoading] = useState(true); const [activeCheckIn, setActiveCheckIn] = useState(null); const [history, setHistory] = useState([]); const pulseAnim = useRef(new Animated.Value(1)).current; useEffect(() => { if (activeCheckIn) { const pulse = Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.05, duration: 1000, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true, }), ]), ); pulse.start(); return () => pulse.stop(); } }, [activeCheckIn]); const fetchAttendance = async () => { try { setLoading(true); const token = await getToken(); if (!token) return; log.debug("Fetching attendance history"); const data = await attendanceApi.getHistory(token); setHistory(data); // Check if there's an active check-in (latest one has no checkOutTime) if (data.length > 0 && !data[0].checkOutTime) { setActiveCheckIn(data[0]); } else { setActiveCheckIn(null); } } catch (error) { log.error("Failed to fetch attendance", error); Alert.alert("Error", "Failed to load attendance data"); } finally { setLoading(false); } }; useEffect(() => { fetchAttendance(); }, []); const handleCheckIn = async () => { try { const token = await getToken(); if (!token) return; await attendanceApi.checkIn("gym", token); fetchAttendance(); Alert.alert("Success", "Checked in successfully!"); } catch (error: unknown) { log.error("Failed to check in", error); Alert.alert("Error", getErrorMessage(error, "Failed to check in")); } }; const handleCheckOut = async () => { try { const token = await getToken(); if (!token) return; await attendanceApi.checkOut(token); fetchAttendance(); Alert.alert("Success", "Checked out successfully!"); } catch (error: unknown) { log.error("Failed to check out", error); Alert.alert("Error", getErrorMessage(error, "Failed to check out")); } }; if (loading && !history.length) { return ( ); } return ( Attendance Track your gym visits {activeCheckIn ? ( Currently Checked In Since{" "} {new Date(activeCheckIn.checkInTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} Check Out ) : ( Check In )} {/* Attendance Calendar */} {history.length > 0 && } Recent History {history.map((item) => ( {new Date(item.checkInTime).toLocaleDateString()} {item.type.toUpperCase()} In:{" "} {new Date(item.checkInTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} {item.checkOutTime && ( Out:{" "} {new Date(item.checkOutTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} )} ))} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.colors.background, }, content: { paddingBottom: 20, }, centered: { flex: 1, justifyContent: "center", alignItems: "center", }, header: { paddingTop: 60, paddingBottom: 24, paddingHorizontal: 24, marginBottom: 24, borderBottomLeftRadius: theme.borderRadius.xl, borderBottomRightRadius: theme.borderRadius.xl, }, title: { fontSize: theme.typography.fontSize["3xl"], fontWeight: theme.typography.fontWeight.bold, color: theme.colors.white, marginBottom: 4, }, subtitle: { fontSize: theme.typography.fontSize.base, color: "rgba(255, 255, 255, 0.9)", }, actionContainer: { marginBottom: 32, paddingHorizontal: 20, }, checkInButton: { paddingVertical: 20, paddingHorizontal: 24, borderRadius: theme.borderRadius.xl, flexDirection: "row", alignItems: "center", justifyContent: "center", }, checkInButtonText: { color: theme.colors.white, fontSize: theme.typography.fontSize.xl, fontWeight: theme.typography.fontWeight.semibold, }, checkOutButton: { paddingVertical: 14, paddingHorizontal: 20, borderRadius: theme.borderRadius.lg, flexDirection: "row", alignItems: "center", justifyContent: "center", marginTop: 16, }, buttonText: { color: theme.colors.white, fontSize: theme.typography.fontSize.base, fontWeight: theme.typography.fontWeight.semibold, }, activeCard: { padding: 20, borderRadius: theme.borderRadius.xl, borderWidth: 1, borderColor: "rgba(16, 185, 129, 0.2)", }, activeCardContent: { flexDirection: "row", alignItems: "center", marginBottom: 16, }, activeIconContainer: { marginRight: 16, }, activeIcon: { width: 56, height: 56, borderRadius: 28, justifyContent: "center", alignItems: "center", }, activeTextContainer: { flex: 1, }, activeText: { fontSize: theme.typography.fontSize.lg, fontWeight: theme.typography.fontWeight.semibold, color: theme.colors.gray900, marginBottom: 4, }, timeText: { fontSize: theme.typography.fontSize.sm, color: theme.colors.gray600, }, sectionTitle: { fontSize: theme.typography.fontSize.xl, fontWeight: theme.typography.fontWeight.semibold, marginBottom: 16, paddingHorizontal: 20, color: theme.colors.gray900, }, historyItem: { padding: 16, borderRadius: theme.borderRadius.xl, marginBottom: 12, marginHorizontal: 20, flexDirection: "row", justifyContent: "space-between", alignItems: "center", borderWidth: 1, borderColor: "rgba(59, 130, 246, 0.1)", }, historyLeft: { flexDirection: "row", alignItems: "center", gap: 12, }, historyIconContainer: { marginRight: 4, }, historyIcon: { width: 32, height: 32, borderRadius: 16, justifyContent: "center", alignItems: "center", }, dateText: { fontSize: theme.typography.fontSize.base, fontWeight: theme.typography.fontWeight.semibold, color: theme.colors.gray900, }, typeText: { fontSize: theme.typography.fontSize.xs, color: theme.colors.gray600, marginTop: 2, }, timeContainer: { alignItems: "flex-end", }, historyTime: { fontSize: theme.typography.fontSize.sm, color: theme.colors.gray700, }, });