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;