diff --git a/guard_app/src/screen/SettingsScreen.tsx b/guard_app/src/screen/SettingsScreen.tsx
index 227bb8399..b7179abca 100644
--- a/guard_app/src/screen/SettingsScreen.tsx
+++ b/guard_app/src/screen/SettingsScreen.tsx
@@ -107,6 +107,7 @@ export default function SettingsScreen() {
try {
await LocalStorage.removeToken(); // clear auth tokens
await LocalStorage.removePushToken(); // clear push tokens
+ await LocalStorage.clearAll();
await AsyncStorage.removeItem(PROFILE_STORAGE_KEY); // clear profile data
navigation.reset({
index: 0,
diff --git a/guard_app/src/screen/ShiftDetailsScreen.tsx b/guard_app/src/screen/ShiftDetailsScreen.tsx
index 33718320f..c789538ab 100644
--- a/guard_app/src/screen/ShiftDetailsScreen.tsx
+++ b/guard_app/src/screen/ShiftDetailsScreen.tsx
@@ -6,7 +6,7 @@ import { AxiosError } from 'axios';
import React, { useEffect, useState } from 'react';
import { Alert, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
-import { checkIn, checkOut } from '../api/attendance';
+// import { checkIn, checkOut } from '../api/attendance'; //uncomment when API is ready
import LocationVerificationModal from '../components/LocationVerificationModal';
import { getAttendanceForShift, setAttendanceForShift } from '../lib/attendancestore';
import { useAppTheme } from '../theme';
@@ -24,6 +24,11 @@ type AttendanceState = {
checkOutTime?: string;
};
+type AttendanceHistoryItem = {
+ label: string;
+ value: string;
+};
+
function StatusBadge({ status, color }: { status: string; color: string }) {
return {status.toUpperCase()};
}
@@ -49,6 +54,10 @@ export default function ShiftDetailsScreen() {
const [modalVisible, setModalVisible] = useState(false);
const [actionType, setActionType] = useState<'check-in' | 'check-out'>('check-in');
+ useEffect(() => {
+ console.log('Shift details:', shift);
+ }, [shift]);
+
useEffect(() => {
(async () => {
const a = await getAttendanceForShift(shift._id);
@@ -66,28 +75,85 @@ export default function ShiftDetailsScreen() {
longitude: number;
timestamp: number;
}) => {
+ // Uncomment this section when the backend API is ready to handle location-based check-in/check-out.
+ // try {
+ // setModalVisible(false);
+ // console.log('Location:', loc);
+
+ // if (actionType === 'check-in') {
+ // const res = await checkIn(shift._id, loc);
+
+ // const next: AttendanceState = normalizeAttendance({
+ // checkInTime: res.attendance?.checkInTime,
+ // checkOutTime: undefined,
+ // });
+
+ // await setAttendanceForShift(shift._id, next);
+ // setAttendance(next);
+
+ // Alert.alert('Success', 'Checked in successfully ✅');
+ // } else {
+ // const res = await checkOut(shift._id, loc);
+
+ // const next: AttendanceState = normalizeAttendance({
+ // checkInTime: res.attendance?.checkInTime,
+ // checkOutTime: res.attendance?.checkOutTime,
+ // });
+
+ // await setAttendanceForShift(shift._id, next);
+ // setAttendance(next);
+
+ // Alert.alert('Success', 'Checked out successfully ✅');
+ // }
+
+ // if (route.params.refresh) route.params.refresh();
+ // } catch (e: unknown) {
+ // setModalVisible(false);
+ // let msg;
+ // if (e instanceof AxiosError) {
+ // msg = e?.response?.data?.message ?? e?.message ?? 'Action failed';
+ // } else {
+ // msg = 'Action failed';
+ // }
+
+ // if (typeof msg === 'string' && msg.toLowerCase().includes('location')) {
+ // Alert.alert('Location Error', 'You are not at the shift location ❌');
+ // } else {
+ // Alert.alert('Error', msg);
+ // }
+ // }
+
+ // locally update attendance (e.g. due to location verification issues in API)
try {
setModalVisible(false);
+ console.log('Location:', loc);
- if (actionType === 'check-in') {
- const res = await checkIn(shift._id, loc);
+ const now = new Date().toISOString();
+
+ // checking in/out should only be allowed during shift time - this is a fallback check in case the API doesn't enforce it properly
+ const nowD = new Date();
+ const start = new Date(`${shift.date}T${shift.startTime}`);
+ const end = new Date(`${shift.date}T${shift.endTime}`);
+ if (actionType === 'check-in' && (nowD < start || nowD > end)) {
+ Alert.alert('Not allowed', 'Check-in is only allowed during shift time.');
+ return;
+ }
- const next: AttendanceState = normalizeAttendance({
- checkInTime: res.attendance?.checkInTime,
- checkOutTime: undefined,
- });
+ if (actionType === 'check-in') {
+ const next: AttendanceState = {
+ checkInTime: now,
+ checkOutTime: attendance?.checkOutTime,
+ };
await setAttendanceForShift(shift._id, next);
setAttendance(next);
Alert.alert('Success', 'Checked in successfully ✅');
} else {
- const res = await checkOut(shift._id, loc);
-
- const next: AttendanceState = normalizeAttendance({
- checkInTime: res.attendance?.checkInTime,
- checkOutTime: res.attendance?.checkOutTime,
- });
+ const next: AttendanceState = {
+ checkInTime: attendance?.checkInTime,
+ checkOutTime: now,
+ };
await setAttendanceForShift(shift._id, next);
setAttendance(next);
@@ -96,20 +162,9 @@ export default function ShiftDetailsScreen() {
}
if (route.params.refresh) route.params.refresh();
- } catch (e: unknown) {
+ } catch {
setModalVisible(false);
- let msg;
- if (e instanceof AxiosError) {
- msg = e?.response?.data?.message ?? e?.message ?? 'Action failed';
- } else {
- msg = 'Action failed';
- }
-
- if (typeof msg === 'string' && msg.toLowerCase().includes('location')) {
- Alert.alert('Location Error', 'You are not at the shift location ❌');
- } else {
- Alert.alert('Error', msg);
- }
+ Alert.alert('Error', 'Action failed');
}
};
@@ -121,9 +176,15 @@ export default function ShiftDetailsScreen() {
const hasCheckedIn = !!attendance?.checkInTime;
const hasCheckedOut = !!attendance?.checkOutTime;
+ const shouldShowAttendanceHistory = shift.status === 'completed' || hasCheckedIn;
const showCheckIn = canDoAttendance && !hasCheckedIn;
const showCheckOut = canDoAttendance && hasCheckedIn && !hasCheckedOut;
+ const attendanceHistory: AttendanceHistoryItem[] = [
+ ...(attendance?.checkInTime ? [{ label: 'Check In', value: attendance.checkInTime }] : []),
+ ...(attendance?.checkOutTime ? [{ label: 'Check Out', value: attendance.checkOutTime }] : []),
+ ];
+
const handleMessageEmployer = () => {
const employerId = shift.createdBy?._id;
if (!employerId) {
@@ -181,6 +242,46 @@ export default function ShiftDetailsScreen() {
+
+ Location Details
+
+
+ Street
+ {shift.location?.street ?? 'N/A'}
+
+
+
+ Suburb
+ {shift.location?.suburb ?? 'N/A'}
+
+
+
+ State
+ {shift.location?.state ?? 'N/A'}
+
+
+
+ Postcode
+ {shift.location?.postcode ?? 'N/A'}
+
+
+ {shouldShowAttendanceHistory && (
+
+ Attendance History
+
+ {attendanceHistory.length > 0 ? (
+ attendanceHistory.map((item, index) => (
+
+ {item.label}
+ {item.value}
+
+ ))
+ ) : (
+ No attendance history available
+ )}
+
+ )}
+
{attendance?.checkInTime && (
✅ Checked in: {attendance.checkInTime}
)}
@@ -333,4 +434,52 @@ const getStyles = (colors: AppColors) =>
hintStrong: {
fontWeight: '700',
},
+ section: {
+ marginTop: 18,
+ paddingTop: 16,
+ borderTopWidth: 1,
+ borderTopColor: colors.border,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: '700',
+ color: colors.text,
+ marginBottom: 12,
+ },
+ infoRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginBottom: 10,
+ },
+ infoLabel: {
+ fontSize: 14,
+ color: colors.muted,
+ },
+ infoValue: {
+ fontSize: 14,
+ color: colors.text,
+ fontWeight: '500',
+ maxWidth: '65%',
+ textAlign: 'right',
+ },
+ historyItem: {
+ backgroundColor: colors.primarySoft,
+ borderRadius: 10,
+ padding: 12,
+ marginBottom: 10,
+ },
+ historyLabel: {
+ fontSize: 13,
+ color: colors.muted,
+ marginBottom: 4,
+ },
+ historyValue: {
+ fontSize: 14,
+ color: colors.text,
+ fontWeight: '700',
+ },
+ emptyHistory: {
+ fontSize: 14,
+ color: colors.muted,
+ },
});
diff --git a/guard_app/src/screen/ShiftsScreen.tsx b/guard_app/src/screen/ShiftsScreen.tsx
index d29295033..e8d6f2585 100644
--- a/guard_app/src/screen/ShiftsScreen.tsx
+++ b/guard_app/src/screen/ShiftsScreen.tsx
@@ -1,6 +1,6 @@
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { useFocusEffect } from '@react-navigation/native';
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Alert,
@@ -19,6 +19,7 @@ import {
import { getMe } from '../api/auth';
import { applyToShift, listShifts, myShifts, type ShiftDto } from '../api/shifts';
import { useAppTheme } from '../theme';
+import { getAttendanceForShift } from '../lib/attendancestore';
import type { AppColors } from '../theme/colors';
@@ -158,6 +159,20 @@ function ShiftDetailsModal({
}) {
const s = getStyles(colors);
+ const [attendance, setAttendance] = useState<{
+ checkInTime?: string;
+ checkOutTime?: string;
+ } | null>(null);
+
+ useEffect(() => {
+ if (!shift || !visible) return;
+
+ (async () => {
+ const data = await getAttendanceForShift(shift?.id);
+ setAttendance(data);
+ })();
+ }, [shift, visible]);
+
if (!shift) return null;
const status = 'status' in shift ? shift.status : 'Completed';
@@ -227,6 +242,25 @@ function ShiftDetailsModal({
+ {(attendance?.checkInTime || attendance?.checkOutTime) && (
+
+ Attendance History
+
+ {attendance?.checkInTime && (
+
+ Check In:
+ {attendance.checkInTime}
+
+ )}
+
+ {attendance?.checkOutTime && (
+
+ Check Out:
+ {attendance.checkOutTime}
+
+ )}
+
+ )}