import React, { useState, useCallback, useEffect } from 'react'; import { View, Text, StyleSheet, PermissionsAndroid, Platform, Alert, ActivityIndicator, TouchableOpacity, FlatList, RefreshControl, Linking } from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import { useFocusEffect } from '@react-navigation/native'; import api from '../services/api'; import Colors from '../constants/Colors'; const AttendanceScreen = () => { const [loading, setLoading] = useState(false); const [historyLoading, setHistoryLoading] = useState(true); const [currentSession, setCurrentSession] = useState(null); const [history, setHistory] = useState([]); const getCurrentLocation = () => { return new Promise((resolve, reject) => { Geolocation.getCurrentPosition( (position) => resolve(position.coords), (error) => { Alert.alert("Location Error", error.message); reject(error); }, { enableHighAccuracy: false, // Switch to false for maximum stability timeout: 15000, maximumAge: 10000, // FORCE using the standard Android Location Manager // instead of the Google Fused Provider (which is crashing) forceLocationManager: true } ); }); }; const fetchAttendanceData = async () => { setHistoryLoading(true); try { const response = await api.get('/attendance/my-history'); const data = response.data; setHistory(data); if (data.length > 0 && !data[0].checkOutTime) { setCurrentSession(data[0]); } else { setCurrentSession(null); } } catch (error) { console.error("Failed to fetch history", error); if (error.message === 'Network Error') { Alert.alert("Network Error", "Could not connect to server. Please check your internet and if the API is running at " + api.defaults.baseURL); } } finally { setHistoryLoading(false); } }; useFocusEffect( useCallback(() => { fetchAttendanceData(); }, []) ); // Periodic Location Tracking when Checked In /* useEffect(() => { let interval; if (currentSession && !currentSession.checkOutTime) { console.log("Starting periodic tracking..."); const sendLocation = async () => { try { const coords = await getCurrentLocation(); await api.post('/locations', { lat: coords.latitude, lng: coords.longitude }); } catch (error) { console.error("Periodic tracking failed", error); } }; sendLocation(); // Initial interval = setInterval(sendLocation, 5 * 60 * 1000); // 5 mins } return () => { if (interval) { console.log("Stopping periodic tracking."); clearInterval(interval); } }; }, [currentSession]); */ // Request Location Permission const requestLocationPermission = async () => { if (Platform.OS === 'android') { try { const granted = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION, ]); const fine = granted?.[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION] === PermissionsAndroid.RESULTS.GRANTED; const coarse = granted?.[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION] === PermissionsAndroid.RESULTS.GRANTED; if (!fine || !coarse) { Alert.alert("Permission Denied", "Attendance requires location access. Please enable it in settings."); } return fine && coarse; } catch (err) { console.warn(err); return false; } } return true; }; const getCurrentLocationReal = getCurrentLocation; const handleCheckIn = async () => { const hasPermission = await requestLocationPermission(); if (!hasPermission) return; setLoading(true); try { const coords = await getCurrentLocation(); await api.post('/attendance/check-in', { latitude: coords.latitude, longitude: coords.longitude }); await fetchAttendanceData(); Alert.alert("Success", "Checked In!"); } catch (error) { console.error(error); Alert.alert("Error", "Check-in failed."); } finally { setLoading(false); } }; const handleCheckOut = async () => { if (!currentSession?.id) return; const hasPermission = await requestLocationPermission(); if (!hasPermission) return; setLoading(true); try { const coords = await getCurrentLocation(); await api.patch(`/attendance/check-out/${currentSession.id}`, { latitude: coords.latitude, longitude: coords.longitude }); await fetchAttendanceData(); Alert.alert("Success", "Checked Out Successfully!"); } catch (error) { console.error(error); Alert.alert("Error", "Check-out failed."); } finally { setLoading(false); } }; const formatDate = (dateString) => { if (!dateString) return '--:--'; const date = new Date(dateString); const hours = date.getHours(); const mins = date.getMinutes(); const ampm = hours >= 12 ? 'PM' : 'AM'; const h12 = hours % 12 || 12; return `${h12}:${mins < 10 ? '0' + mins : mins} ${ampm}`; }; const getDayMonth = (dateString) => { const date = new Date(dateString); const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; return `${date.getDate()} ${months[date.getMonth()]}`; }; const openMap = (lat, lng) => { const url = Platform.select({ ios: `maps:0,0?q=${lat},${lng}`, android: `geo:0,0?q=${lat},${lng}(Attendance Location)` }); Linking.openURL(url); }; const renderHistoryItem = ({ item }) => ( {getDayMonth(item.checkInTime)} In: {formatDate(item.checkInTime)} Out: {item.checkOutTime ? formatDate(item.checkOutTime) : 'Active'} {(item.checkInLat && item.checkInLng) && ( openMap(item.checkInLat, item.checkInLng)} style={styles.locationContainer} > 📍 {Number(item.checkInLat || 0).toFixed(4)}, {Number(item.checkInLng || 0).toFixed(4)} View Map )} ); return ( My Attendance Today's Status {currentSession ? 'Currently Checked In' : 'Not Checked In'} {loading ? ( ) : ( {currentSession ? "Check Out Now" : "Check In Now"} )} Recent History {historyLoading ? ( ) : ( item.id} renderItem={renderHistoryItem} contentContainerStyle={styles.listContent} ListEmptyComponent={No attendance records found.} refreshControl={} /> )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.background, padding: 20 }, headerTitle: { fontSize: 28, fontWeight: 'bold', color: Colors.text, marginBottom: 20 }, actionCard: { backgroundColor: 'white', borderRadius: 16, padding: 20, alignItems: 'center', elevation: 3, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 5, marginBottom: 30 }, cardTitle: { fontSize: 16, color: Colors.textMuted, marginBottom: 5 }, statusText: { fontSize: 20, fontWeight: 'bold', marginBottom: 20 }, textActive: { color: Colors.success }, textInactive: { color: Colors.textMuted }, actionButton: { width: '100%', paddingVertical: 15, borderRadius: 12, alignItems: 'center', }, btnCheckin: { backgroundColor: Colors.primary }, btnCheckout: { backgroundColor: Colors.danger }, btnText: { color: 'white', fontSize: 18, fontWeight: 'bold' }, sectionHeader: { fontSize: 18, fontWeight: 'bold', color: Colors.text, marginBottom: 15 }, listContent: { paddingBottom: 20 }, historyCard: { flexDirection: 'row', backgroundColor: 'white', borderRadius: 12, padding: 15, marginBottom: 10, alignItems: 'center', elevation: 1 }, dateBox: { width: 60, justifyContent: 'center', borderRightWidth: 1, borderRightColor: Colors.borderLight, marginRight: 15 }, dateText: { fontSize: 16, fontWeight: 'bold', color: Colors.text, textAlign: 'center' }, timeBox: { flex: 1 }, timeRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 4 }, timeLabel: { fontSize: 12, color: Colors.textLight, width: 30 }, timeValue: { fontSize: 14, color: Colors.text, fontWeight: '500' }, statusIndicator: { width: 10, height: 10, borderRadius: 5, marginLeft: 10 }, statusActive: { backgroundColor: Colors.success }, statusCompleted: { backgroundColor: Colors.textLight }, locationContainer: { marginTop: 10, padding: 8, backgroundColor: '#f8f9fa', borderRadius: 8, borderWidth: 1, borderColor: '#e9ecef' }, locationText: { fontSize: 12, color: Colors.text, fontFamily: 'monospace' }, mapLink: { fontSize: 10, color: Colors.primary, fontWeight: 'bold', marginTop: 5 }, emptyText: { textAlign: 'center', marginTop: 30, color: Colors.textLight } }); export default AttendanceScreen;