From 265ab066929132931480983e1a78ba38988608b7 Mon Sep 17 00:00:00 2001 From: Bhavya1534 <139518528+Bhavya1534@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:53:11 +1100 Subject: [PATCH 1/2] FE: Certificates list view + expiry date - Added Certificates navigation from Profile - Added list view with status + local expiry state - Added DD/MM/YYYY formatting --- guard_app/package-lock.json | 45 ++--- guard_app/package.json | 2 +- guard_app/src/navigation/AppNavigator.tsx | 9 +- guard_app/src/screen/CertificatesScreen.tsx | 180 ++++++++++++++++++++ guard_app/src/screen/ProfileScreen.tsx | 27 +-- 5 files changed, 232 insertions(+), 31 deletions(-) create mode 100644 guard_app/src/screen/CertificatesScreen.tsx diff --git a/guard_app/package-lock.json b/guard_app/package-lock.json index dc592a0b1..fbc1d52da 100644 --- a/guard_app/package-lock.json +++ b/guard_app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "@react-native-async-storage/async-storage": "2.1.2", - "@react-native-community/datetimepicker": "^8.6.0", + "@react-native-community/datetimepicker": "8.4.1", "@react-navigation/bottom-tabs": "^7.4.7", "@react-navigation/material-top-tabs": "^7.3.7", "@react-navigation/native": "^7.1.17", @@ -106,6 +106,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2680,9 +2681,9 @@ } }, "node_modules/@react-native-community/datetimepicker": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.6.0.tgz", - "integrity": "sha512-yxPSqNfxgpGaqHQIpatqe6ykeBdU/1pdsk/G3x01mY2bpTflLpmVTLqFSJYd3MiZzxNZcMs/j1dQakUczSjcYA==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.4.1.tgz", + "integrity": "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -3129,6 +3130,7 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz", "integrity": "sha512-uEcYWi1NV+2Qe1oELfp9b5hTYekqWATv2cuwcOAg5EvsIsUPtzFrKIasgUXLBRGb9P7yR5ifoJ+ug4u6jdqSTQ==", "license": "MIT", + "peer": true, "dependencies": { "@react-navigation/core": "^7.12.4", "escape-string-regexp": "^4.0.0", @@ -3305,6 +3307,7 @@ "integrity": "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3370,6 +3373,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -3874,6 +3878,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4493,8 +4498,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/bplist-creator": { "version": "0.1.0", @@ -4557,6 +4561,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -5187,7 +5192,6 @@ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -5204,7 +5208,6 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", "license": "MIT", - "peer": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -5218,7 +5221,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5228,7 +5230,6 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">= 6" }, @@ -5486,7 +5487,6 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -5506,15 +5506,13 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -5530,7 +5528,6 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -5619,7 +5616,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -5874,6 +5870,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5930,6 +5927,7 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6028,6 +6026,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6459,6 +6458,7 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-53.0.22.tgz", "integrity": "sha512-sJ2I4W/e5iiM4u/wYCe3qmW4D7WPCRqByPDD0hJcdYNdjc9HFFFdO4OAudZVyC/MmtoWZEIH5kTJP1cw9FjzYA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.21", @@ -6555,6 +6555,7 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.3.2.tgz", "integrity": "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A==", "license": "MIT", + "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -9183,8 +9184,7 @@ "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "license": "CC0-1.0", - "peer": true + "license": "CC0-1.0" }, "node_modules/memoize-one": { "version": "5.2.1", @@ -9880,7 +9880,6 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -10577,6 +10576,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10804,6 +10804,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10844,6 +10845,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -10874,6 +10876,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.5.tgz", "integrity": "sha512-jVihwsE4mWEHZ9HkO1J2eUZSwHyDByZOqthwnGrVZCh6kTQBCm4v8dicsyDa6p0fpWNE5KicTcpX/XXl0ASJFg==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.79.5", @@ -10953,6 +10956,7 @@ "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.7.1.tgz", "integrity": "sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -10975,6 +10979,7 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz", "integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -10985,6 +10990,7 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz", "integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==", "license": "MIT", + "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.1.7", @@ -12967,6 +12973,7 @@ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/guard_app/package.json b/guard_app/package.json index bc2d7b3cd..8d1451fb6 100644 --- a/guard_app/package.json +++ b/guard_app/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "2.1.2", - "@react-native-community/datetimepicker": "^8.6.0", + "@react-native-community/datetimepicker": "8.4.1", "@react-navigation/bottom-tabs": "^7.4.7", "@react-navigation/material-top-tabs": "^7.3.7", "@react-navigation/native": "^7.1.17", diff --git a/guard_app/src/navigation/AppNavigator.tsx b/guard_app/src/navigation/AppNavigator.tsx index 7f053a57c..002f72b80 100644 --- a/guard_app/src/navigation/AppNavigator.tsx +++ b/guard_app/src/navigation/AppNavigator.tsx @@ -8,6 +8,7 @@ import NotificationsScreen from '../screen/notifications'; import SettingsScreen from '../screen/SettingsScreen'; import SignupScreen from '../screen/signupscreen'; import SplashScreen from '../screen/SplashScreen'; +import CertificatesScreen from "../screen/CertificatesScreen"; export type RootStackParamList = { AppTabs: undefined; @@ -18,13 +19,14 @@ export type RootStackParamList = { EditProfile: undefined; Messages: undefined; Notifications: undefined; + Certificates: undefined; }; const Stack = createNativeStackNavigator(); export default function AppNavigator() { return ( - + @@ -49,6 +51,11 @@ export default function AppNavigator() { component={EditProfileScreen} options={{ headerShown: false }} /> + ); } diff --git a/guard_app/src/screen/CertificatesScreen.tsx b/guard_app/src/screen/CertificatesScreen.tsx new file mode 100644 index 000000000..0f0fb5569 --- /dev/null +++ b/guard_app/src/screen/CertificatesScreen.tsx @@ -0,0 +1,180 @@ +import React, { useMemo, useState } from "react"; +import { View, Text, FlatList, Pressable, Modal, TouchableOpacity } from "react-native"; + +type DocItem = { + id: string; + name: string; + uploadedAt: string; +}; + +const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()); +const formatDate = (d?: Date) => (d ? d.toLocaleDateString() : "—"); +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 426cda0f4..2b9a06c8e 100644 --- a/guard_app/src/screen/ProfileScreen.tsx +++ b/guard_app/src/screen/ProfileScreen.tsx @@ -10,6 +10,7 @@ import { ScrollView, TouchableOpacity, Image, + Pressable, } from 'react-native'; import { getUserProfile } from '../api/profile'; @@ -194,16 +195,22 @@ export default function ProfileScreen({ navigation, route }: any) { )} {/* Certifications */} - - Certifications - - {['Security License', 'CPR', 'First Aid'].map((badge, index) => ( - - {badge} - - ))} - - + + navigation.navigate('Certificates')} +> + Certifications + + {['Security License', 'CPR', 'First Aid'].map((badge, index) => ( + + {badge} + + ))} + + + ); From 0ad1e86acd44d40c9906e73325ce77507d6f2e9c Mon Sep 17 00:00:00 2001 From: Bhavya1534 <139518528+Bhavya1534@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:45:17 +1100 Subject: [PATCH 2/2] Fix prettier/lint Ran prettier + removed unused var --- guard_app/src/api/availability.ts | 2 + guard_app/src/api/shifts.ts | 2 + guard_app/src/navigation/AppNavigator.tsx | 4 +- guard_app/src/screen/CertificatesScreen.tsx | 167 +++++++++++--------- guard_app/src/screen/ProfileScreen.tsx | 31 ++-- guard_app/src/screen/ShiftsScreen.tsx | 3 + 6 files changed, 116 insertions(+), 93 deletions(-) diff --git a/guard_app/src/api/availability.ts b/guard_app/src/api/availability.ts index 55c04c33a..2d52c18fc 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 e1666afff..abafa7943 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 002f72b80..4e0550aed 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'; @@ -8,7 +9,6 @@ import NotificationsScreen from '../screen/notifications'; import SettingsScreen from '../screen/SettingsScreen'; import SignupScreen from '../screen/signupscreen'; import SplashScreen from '../screen/SplashScreen'; -import CertificatesScreen from "../screen/CertificatesScreen"; export type RootStackParamList = { AppTabs: undefined; @@ -54,7 +54,7 @@ export default function AppNavigator() { ); diff --git a/guard_app/src/screen/CertificatesScreen.tsx b/guard_app/src/screen/CertificatesScreen.tsx index 0f0fb5569..7eab274c2 100644 --- a/guard_app/src/screen/CertificatesScreen.tsx +++ b/guard_app/src/screen/CertificatesScreen.tsx @@ -1,5 +1,8 @@ -import React, { useMemo, useState } from "react"; -import { View, Text, FlatList, Pressable, Modal, TouchableOpacity } from "react-native"; +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import React, { useMemo, useState } from 'react'; +import { View, Text, FlatList, Pressable, Modal, TouchableOpacity } from 'react-native'; type DocItem = { id: string; @@ -8,38 +11,36 @@ type DocItem = { }; const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()); -const formatDate = (d?: Date) => (d ? d.toLocaleDateString() : "—"); const formatDMY = (d?: Date) => { - if (!d) return "—"; - const day = String(d.getDate()).padStart(2, "0"); - const month = String(d.getMonth() + 1).padStart(2, "0"); + 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 + if (!expiry) return 'Valid'; // placeholder if backend not ready const today = startOfDay(new Date()); const exp = startOfDay(expiry); - if (exp < today) return "Expired"; + 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"; + 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" }, + { 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>({}); @@ -48,21 +49,20 @@ export default function CertificatesScreen() { 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 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 dateOptions = useMemo(() => makeNextDates(365), []); - const renderItem = ({ item }: { item: DocItem }) => { const expiry = expiryDates[item.id]; const status = computeStatus(expiry); @@ -73,24 +73,28 @@ const dateOptions = useMemo(() => makeNextDates(365), []); padding: 14, borderRadius: 14, borderWidth: 1, - borderColor: "#e2e2e2", - backgroundColor: "white", + borderColor: '#e2e2e2', + backgroundColor: 'white', marginBottom: 12, }} > - - {item.name} + + {item.name} - {status} + {status} @@ -110,15 +114,15 @@ const dateOptions = useMemo(() => makeNextDates(365), []); paddingVertical: 10, borderRadius: 12, borderWidth: 1, - borderColor: "#e2e2e2", - alignItems: "center", + borderColor: '#e2e2e2', + alignItems: 'center', }} > - Set Expiry Date + Set Expiry Date {warningDocId === item.id ? ( - + Expiry date cannot be before today. ) : null} @@ -127,12 +131,12 @@ const dateOptions = useMemo(() => makeNextDates(365), []); }; return ( - - Certificates + + Certificates {docs.length === 0 ? ( - - No documents + + No documents Upload a document to see it listed here with expiry details. @@ -141,40 +145,53 @@ const dateOptions = useMemo(() => makeNextDates(365), []); 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); + + + - {formatDMY(d)} - - )} - /> - - - - - + + 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 2b9a06c8e..bc1528dba 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 { @@ -10,7 +11,6 @@ import { ScrollView, TouchableOpacity, Image, - Pressable, } from 'react-native'; import { getUserProfile } from '../api/profile'; @@ -196,21 +196,20 @@ export default function ProfileScreen({ navigation, route }: any) { {/* Certifications */} - navigation.navigate('Certificates')} -> - Certifications - - {['Security License', 'CPR', 'First Aid'].map((badge, index) => ( - - {badge} - - ))} - - - + navigation.navigate('Certificates')} + > + Certifications + + {['Security License', 'CPR', 'First Aid'].map((badge, index) => ( + + {badge} + + ))} + + ); diff --git a/guard_app/src/screen/ShiftsScreen.tsx b/guard_app/src/screen/ShiftsScreen.tsx index 3c3e297cb..6101dad33 100644 --- a/guard_app/src/screen/ShiftsScreen.tsx +++ b/guard_app/src/screen/ShiftsScreen.tsx @@ -1,3 +1,6 @@ +/* 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';