diff --git a/guard_app/package-lock.json b/guard_app/package-lock.json index d5fd13d9..a5ac095f 100644 --- a/guard_app/package-lock.json +++ b/guard_app/package-lock.json @@ -3281,7 +3281,8 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", diff --git a/guard_app/src/api/availability.ts b/guard_app/src/api/availability.ts index 55c04c33..2d52c18f 100644 --- a/guard_app/src/api/availability.ts +++ b/guard_app/src/api/availability.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import http from '../lib/http'; export interface AvailabilityData { diff --git a/guard_app/src/api/shifts.ts b/guard_app/src/api/shifts.ts index cdca51fc..2495b318 100644 --- a/guard_app/src/api/shifts.ts +++ b/guard_app/src/api/shifts.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + // src/api/shifts.ts import http from '../lib/http'; diff --git a/guard_app/src/navigation/AppNavigator.tsx b/guard_app/src/navigation/AppNavigator.tsx index 12b5472c..fabfacd6 100644 --- a/guard_app/src/navigation/AppNavigator.tsx +++ b/guard_app/src/navigation/AppNavigator.tsx @@ -1,6 +1,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; import AppTabs from './AppTabs'; +import CertificatesScreen from '../screen/CertificatesScreen'; import EditProfileScreen from '../screen/EditProfileScreen'; import LoginScreen from '../screen/loginscreen'; import MessagesScreen from '../screen/MessagesScreen'; @@ -21,7 +22,7 @@ export type RootStackParamList = { Messages: undefined; Notifications: undefined; - Documents: { docType?: string } | undefined; + Certificates: undefined; ShiftDetails: { shift: any; refresh?: () => void }; }; @@ -56,6 +57,9 @@ export default function AppNavigator() { options={{ headerShown: false }} /> new Date(d.getFullYear(), d.getMonth(), d.getDate()); +const formatDMY = (d?: Date) => { + if (!d) return '—'; + const day = String(d.getDate()).padStart(2, '0'); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const year = d.getFullYear(); + return `${day}/${month}/${year}`; +}; + +function computeStatus(expiry?: Date) { + if (!expiry) return 'Valid'; // placeholder if backend not ready + const today = startOfDay(new Date()); + const exp = startOfDay(expiry); + + if (exp < today) return 'Expired'; + + const msDay = 24 * 60 * 60 * 1000; + const daysLeft = Math.ceil((exp.getTime() - today.getTime()) / msDay); + + if (daysLeft <= 30) return 'Expiring'; + return 'Valid'; +} + +export default function CertificatesScreen() { + const docs: DocItem[] = useMemo( + () => [ + { id: '1', name: 'Security License', uploadedAt: '20/01/2026' }, + { id: '2', name: 'CPR', uploadedAt: '18/01/2026' }, + { id: '3', name: 'First Aid', uploadedAt: '10/01/2026' }, + ], + [], + ); + + const [expiryDates, setExpiryDates] = useState>({}); + const [pickerDocId, setPickerDocId] = useState(null); + const [warningDocId, setWarningDocId] = useState(null); + + const [isPickerOpen, setIsPickerOpen] = useState(false); + + const makeNextDates = (days = 365) => { + const arr: Date[] = []; + const base = new Date(); + base.setHours(0, 0, 0, 0); + for (let i = 0; i < days; i++) { + const d = new Date(base); + d.setDate(base.getDate() + i); + arr.push(d); + } + return arr; + }; + + const dateOptions = useMemo(() => makeNextDates(365), []); + + const renderItem = ({ item }: { item: DocItem }) => { + const expiry = expiryDates[item.id]; + const status = computeStatus(expiry); + + return ( + + + {item.name} + + {status} + + + + + Upload date: {item.uploadedAt} + Expiry date: {formatDMY(expiry)} + + + { + setPickerDocId(item.id); + setWarningDocId(null); + setIsPickerOpen(true); + }} + style={{ + marginTop: 10, + paddingVertical: 10, + borderRadius: 12, + borderWidth: 1, + borderColor: '#e2e2e2', + alignItems: 'center', + }} + > + Set Expiry Date + + + {warningDocId === item.id ? ( + + Expiry date cannot be before today. + + ) : null} + + ); + }; + + return ( + + Certificates + + {docs.length === 0 ? ( + + No documents + + Upload a document to see it listed here with expiry details. + + + ) : ( + x.id} renderItem={renderItem} /> + )} + + + + + + Select Expiry Date + setIsPickerOpen(false)}> + Close + + + + d.toISOString()} + renderItem={({ item: d }) => ( + { + if (!pickerDocId) return; + // Since we only show today+ future dates, it's always valid. + setExpiryDates((prev) => ({ ...prev, [pickerDocId]: d })); + setWarningDocId(null); + setIsPickerOpen(false); + setPickerDocId(null); + }} + style={{ paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#eee' }} + > + {formatDMY(d)} + + )} + /> + + + + + ); +} diff --git a/guard_app/src/screen/ProfileScreen.tsx b/guard_app/src/screen/ProfileScreen.tsx index d32febf4..79399c35 100644 --- a/guard_app/src/screen/ProfileScreen.tsx +++ b/guard_app/src/screen/ProfileScreen.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-native/no-inline-styles */ + import { Ionicons } from '@expo/vector-icons'; import { useEffect, useState } from 'react'; import { @@ -195,7 +196,12 @@ export default function ProfileScreen({ navigation, route }: any) { )} {/* Certifications */} - + + navigation.navigate('Certificates')} + > Certifications {['Security License', 'CPR', 'First Aid'].map((badge) => ( @@ -208,7 +214,7 @@ export default function ProfileScreen({ navigation, route }: any) { ))} - + ); diff --git a/guard_app/src/screen/ShiftsScreen.tsx b/guard_app/src/screen/ShiftsScreen.tsx index 40bd2753..a0d82713 100644 --- a/guard_app/src/screen/ShiftsScreen.tsx +++ b/guard_app/src/screen/ShiftsScreen.tsx @@ -1,3 +1,11 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// ShiftScreen.tsx +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; +import { useFocusEffect } from '@react-navigation/native'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; import React, { useCallback, useEffect, useState } from 'react'; import {