From ddccf3002fcd2cd2b8d7fa7bca740002e480980d Mon Sep 17 00:00:00 2001 From: code-with-zoro Date: Fri, 6 Mar 2026 21:26:16 +0530 Subject: [PATCH] feat: updated GoBuild app UI and integrated new backend APIs --- App.js | 31 +- android/app/build.gradle | 40 +- android/app/src/main/AndroidManifest.xml | 2 + android/build_log.txt | Bin 0 -> 5848 bytes package-lock.json | 81 ++- package.json | 2 + src/api/Api.js | 7 + src/screens/AuthScreens/Login.js | 1 - src/screens/HomeScreen/HomeScreen.js | 453 ++++++------- src/screens/HomeScreen/homeScreenModals.js | 50 ++ src/screens/HomeScreen/homeScreenStyles.js | 205 ++++++ src/screens/ProfileScreen/ProfileScreen.js | 708 ++++++++++++--------- src/screens/ProfileScreen/jobModals.js | 185 ++++++ src/screens/ProfileScreen/profileStyle.js | 292 +++++++++ 14 files changed, 1498 insertions(+), 559 deletions(-) create mode 100644 android/build_log.txt create mode 100644 src/api/Api.js create mode 100644 src/screens/HomeScreen/homeScreenModals.js create mode 100644 src/screens/HomeScreen/homeScreenStyles.js create mode 100644 src/screens/ProfileScreen/jobModals.js create mode 100644 src/screens/ProfileScreen/profileStyle.js diff --git a/App.js b/App.js index b26ba67..44b942d 100644 --- a/App.js +++ b/App.js @@ -19,7 +19,7 @@ import { } from './src/notifications/notificationService'; import IncomingWorkOverlay from './src/components/IncomingWorkOverlay'; -import { clearIncomingWork } from './src/redux/workSlice'; +import { clearIncomingWork, showIncomingWork } from './src/redux/workSlice'; function AppContainer() { const dispatch = useDispatch(); @@ -29,6 +29,9 @@ function AppContainer() { state => state.work?.incomingWork ); + // Track current logged in worker for real-time listener + const workerId = useSelector(state => state.auth?.user?.id); + /* NOTIFICATION INIT */ useEffect(() => { const initNotifications = async () => { @@ -66,6 +69,32 @@ function AppContainer() { initNotifications(); }, []); + /* SUPABASE REALTIME NOTIFICATIONS */ + useEffect(() => { + if (!workerId) return; + + console.log(`Setting up Supabase real-time listener for worker ${workerId}...`); + const channel = supabase.channel('worker_notifications'); + + const subscription = channel + .on('broadcast', { event: `new_task_worker_${workerId}` }, (payload) => { + const jobDetails = payload.payload; + console.log('Received new task broadcast:', jobDetails); + + // Show IncomingWorkOverlay and play ringtone + dispatch(showIncomingWork(jobDetails)); + RingtoneService.startRingtone(); + }) + .subscribe((status) => { + console.log(`Supabase Realtime status for worker ${workerId}:`, status); + }); + + return () => { + console.log(`Unsubscribing from worker_notifications for worker ${workerId}`); + supabase.removeChannel(channel); + }; + }, [workerId, dispatch]); + /* UI*/ return ( diff --git a/android/app/build.gradle b/android/app/build.gradle index 9a2ab13..a7cd602 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -78,6 +78,7 @@ android { compileSdk rootProject.ext.compileSdkVersion namespace "com.workers" + defaultConfig { applicationId "com.workers" minSdkVersion rootProject.ext.minSdkVersion @@ -85,7 +86,11 @@ android { versionCode 4 versionName "1.0.3" } - + + packagingOptions { + pickFirst '**/libworklets.so' + } + signingConfigs { debug { storeFile file('debug.keystore') @@ -95,26 +100,27 @@ android { } release { - storeFile file(GOBUILD_UPLOAD_STORE_FILE) - storePassword GOBUILD_UPLOAD_STORE_PASSWORD - keyAlias GOBUILD_UPLOAD_KEY_ALIAS - keyPassword GOBUILD_UPLOAD_KEY_PASSWORD - } - - } - buildTypes { - debug { - signingConfig signingConfigs.debug + storeFile file(GOBUILD_UPLOAD_STORE_FILE) + storePassword GOBUILD_UPLOAD_STORE_PASSWORD + keyAlias GOBUILD_UPLOAD_KEY_ALIAS + keyPassword GOBUILD_UPLOAD_KEY_PASSWORD + } } - release { - signingConfig signingConfigs.release - minifyEnabled false - shrinkResources false - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + + buildTypes { + debug { + signingConfig signingConfigs.debug + } + + release { + signingConfig signingConfigs.release + minifyEnabled false + shrinkResources false + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } } } -} dependencies { // The version of react-native is set by the React Native Gradle Plugin diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0393ecb..3280e03 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + a_;9pXXfv}ez445Th)eks#e9CHqcky+Sag=_4PheU(3208>%nY z!&g&ZH^*!CUNdT<^po{8ukAB;3KaJX^foo8Ywt9tDIRczGJV_n8mKi8kIW85ZP)ho zY}=N7ZlBrRWPUHo1IbmF2LA^Md)ATNTke6P9Wy(U4NCIXEB$ZThFaU7Y>y(hm$KBk zewpn#k3;<**@10-GM~QtvnDSL^&I%S=Uxwe%jZ~={fKH}mMB`j?f&q%S{CJbnN{@D zeMH4w_%An3W^*ng(3bnMrdVnzL-K2cSdMS!$pIqGir|Nk;(nrfw&u%>b8xsSE9OG5 zu38}ex%x^o_npUx+KXy(zj?e2sg{}Di^=m)IlriM4}k=W$#x(3u$UzCP=h+|t?Ih2 z(BRBYMp!^y`Kxy8eXkczyawv{{*4rUNBmslE$<7}3YAh-6sTA;r&H0Jx{{uXt^2d- ztqZ4cs(xzA9eXa`%&QB5s*(jf3Kj6VTDe_n?VGVR8j& zb@e96hZ+mDP}1#oPibsTSygqeny~G)UfGVvu<}B>qA&K`(7a6ZsL#>-slLEY>R-N? z^OgE0(O}I`>#JJdR(08M9h#b5(o>VZjQ2Fwu_Y=$?%pY_KW$bLrBQv;+B>NPMSuoKt*b-#v}xI*atRrY6mauW+WwY)#fZ z)F^cc5&T5+-ztv3*84!SI=;4{D*A)g5)JXBb5p7piW}^IX}_q2FA}G8D%w?y1+VY9 z7GV$NJk`-d9M{Dwo}mH{{$D6UcI{`c>9;*7@At%dY8MG?t(IVwd zrfB`np54-qh`}-qStPXJg2#i-cmV%lzpmQMA#>7sE-OfpFzT_|C3u+Ac0h)KG&++l z$73bvPQD}*c>KCW%nY99>qtH$%0q^O4fv6a?n~?3^JUfkR3r`bBPWVaUGvc&#GE!K z&L4_uQxpPP5H;~0Bt*jW@u6@vahfd2)f5TIO|qL)F+1fvDancHk`NQqv_Q&nZj&XA z_UO1JK)Ore7UzPnJ7RxOsqGRc8b}t`7T-f*P{82RiFXW>XcgDTcG#i0m15_t3pWFP zagPA+rtFvd9tlMHNMoVaNcBS8D_690uxCtKF>pTIWt2zsR&MyY zXqB5WkiL@@(e}zfzaRCB=ohgMIlwON|2iJ+-->gQN3_<}#NOYod*d3a5$Y~%G4N4F z__2SyR^R0aM?ste@g>L7K13Z7aEKd5c3V!8L~>6Si92eh7Q1-x5ntR%Mg(IeeQ|9< zxXJv;&`=d1bEt(<{>|phu1^+VTfEO~a3r9~JkCSu7IxmG5BL=BNukO;U2&H^yk4=U zXzcku1%I;WM7GU5VmWOi4>b(+?URvLvH6b1*t3%4*sd*!LtUN8mfZjL<%dvL$MNwU zwJiDrX&iLK!_S4TR32NR6sr(!a|6B-b3>1Wr}mBW;)WpH1xGBvYvGm}-mG9IyHQCa MJ^j};4r;^iA8?-Jj{pDw literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index ca9d4e4..1bf08c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@notifee/react-native": "^9.1.8", "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-community/geolocation": "^3.4.0", "@react-native-firebase/app": "^23.8.3", "@react-native-firebase/messaging": "^23.8.3", "@react-native/new-app-screen": "0.82.1", @@ -24,6 +25,7 @@ "buffer": "^6.0.3", "react": "19.1.1", "react-native": "0.82.1", + "react-native-calendars": "^1.1314.0", "react-native-dotenv": "^3.4.11", "react-native-flash-message": "^0.4.2", "react-native-fs": "^2.20.0", @@ -3693,6 +3695,19 @@ "node": ">=10" } }, + "node_modules/@react-native-community/geolocation": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.4.0.tgz", + "integrity": "sha512-bzZH89/cwmpkPMKKveoC72C4JH0yF4St5Ceg/ZM9pA1SqX9MlRIrIrrOGZ/+yi++xAvFDiYfihtn9TvXWU9/rA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native-firebase/app": { "version": "23.8.3", "resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-23.8.3.tgz", @@ -9678,7 +9693,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.camelcase": { @@ -9691,7 +9705,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "devOptional": true, "license": "MIT" }, "node_modules/lodash.merge": { @@ -10408,6 +10421,16 @@ "node": ">=10" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11336,6 +11359,27 @@ } } }, + "node_modules/react-native-calendars": { + "version": "1.1314.0", + "resolved": "https://registry.npmjs.org/react-native-calendars/-/react-native-calendars-1.1314.0.tgz", + "integrity": "sha512-4DLAVto8Qo9L3ggL2vsY9Gk8FFpJWtne8F/3wN8yUb7Xha9/SKS4B+vs7xlhWjKeqZUHws/Vi/q/6IZ8s60kcQ==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.1", + "lodash": "^4.17.15", + "memoize-one": "^5.2.1", + "prop-types": "^15.5.10", + "react-native-swipe-gestures": "^1.0.5", + "recyclerlistview": "^4.0.0", + "xdate": "^0.8.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "moment": "^2.29.4" + } + }, "node_modules/react-native-dotenv": { "version": "3.4.11", "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.11.tgz", @@ -11558,6 +11602,12 @@ "react-native": "*" } }, + "node_modules/react-native-swipe-gestures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", + "integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==", + "license": "MIT" + }, "node_modules/react-native-url-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-3.0.0.tgz", @@ -11813,6 +11863,21 @@ "node": ">= 6" } }, + "node_modules/recyclerlistview": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.3.tgz", + "integrity": "sha512-STR/wj/FyT8EMsBzzhZ1l2goYirMkIgfV3gYEPxI3Kf3lOnu6f7Dryhyw7/IkQrgX5xtTcDrZMqytvteH9rL3g==", + "license": "Apache-2.0", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -13080,6 +13145,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==", + "license": "ISC" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -13695,6 +13766,12 @@ "async-limiter": "~1.0.0" } }, + "node_modules/xdate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/xdate/-/xdate-0.8.3.tgz", + "integrity": "sha512-1NhJWPJwN+VjbkACT9XHbQK4o6exeSVtS2CxhMPwUE7xQakoEFTlwra9YcqV/uHQVyeEUYoYC46VGDJ+etnIiw==", + "license": "(MIT OR GPL-2.0)" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 4adea31..dc4e70e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@notifee/react-native": "^9.1.8", "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-community/geolocation": "^3.4.0", "@react-native-firebase/app": "^23.8.3", "@react-native-firebase/messaging": "^23.8.3", "@react-native/new-app-screen": "0.82.1", @@ -26,6 +27,7 @@ "buffer": "^6.0.3", "react": "19.1.1", "react-native": "0.82.1", + "react-native-calendars": "^1.1314.0", "react-native-dotenv": "^3.4.11", "react-native-flash-message": "^0.4.2", "react-native-fs": "^2.20.0", diff --git a/src/api/Api.js b/src/api/Api.js new file mode 100644 index 0000000..39543e2 --- /dev/null +++ b/src/api/Api.js @@ -0,0 +1,7 @@ +import axios from "axios"; + +const Api = axios.create({ + baseURL: "http://192.168.29.153:3000", + timeout: 5000, +}); +export default Api ; \ No newline at end of file diff --git a/src/screens/AuthScreens/Login.js b/src/screens/AuthScreens/Login.js index 1bd28d2..d38c48d 100644 --- a/src/screens/AuthScreens/Login.js +++ b/src/screens/AuthScreens/Login.js @@ -306,7 +306,6 @@ const styles = StyleSheet.create({ marginBottom: 20, gap: 10, top: 10, - marginBottom: 30 }, loginText: { diff --git a/src/screens/HomeScreen/HomeScreen.js b/src/screens/HomeScreen/HomeScreen.js index 2d319f7..67f6324 100644 --- a/src/screens/HomeScreen/HomeScreen.js +++ b/src/screens/HomeScreen/HomeScreen.js @@ -7,24 +7,118 @@ import { Image, StatusBar, FlatList, - TouchableOpacity + TouchableOpacity, + Animated, + Easing, + Pressable, + RefreshControl, + PermissionsAndroid, + Platform } from "react-native"; +import Geolocation from '@react-native-community/geolocation'; import Icon from "react-native-vector-icons/Feather"; import LinearGradient from "react-native-linear-gradient"; import { useNavigation, DrawerActions } from "@react-navigation/native"; +import Api from "../../api/Api"; import { SafeAreaView } from "react-native-safe-area-context"; import YoutubePlayer from "react-native-youtube-iframe"; import TopWorkers from "../TopWorkers/TopWorkers"; import { SCREENS } from "../../navigation/StringNavigator"; import stringsoflanguages from "../../components/Language/ConnectLang"; import { useSelector } from "react-redux"; +import { styles } from "./homeScreenStyles"; +import { SafetyItem, TaskItem, TrainingItem } from "./homeScreenModals"; const HomeScreen = () => { const navigation = useNavigation(); const [notificationCount] = useState(3); - const user = useSelector(state => state.auth.user); + const [isActive, setIsActive] = useState(user?.isActive || false); + const [isLoadingToggle, setIsLoadingToggle] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); // Used for Pull-to-Refresh + + // Animation specific state + const slideAnimation = React.useRef(new Animated.Value(user?.isActive ? 1 : 0)).current; + + // Sync animated slide value directly when isActive initially loads or changes externally + React.useEffect(() => { + slideAnimation.setValue(isActive ? 1 : 0); + }, [isActive]); + + const [isHovered, setIsHovered] = useState(false); // Used to expand for Text display + + // Request permission for location on load + React.useEffect(() => { + requestLocationPermission(); + + // Setup the recurring 10-minute timer for geolocation updates + const locationInterval = setInterval(() => { + updateLiveLocation(); + }, 10 * 60 * 1000); // 10 minutes + + return () => clearInterval(locationInterval); // clear interval on unmount + }, []); + + const requestLocationPermission = async () => { + if (Platform.OS === 'android') { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, + { + title: 'Geolocation Permission', + message: 'App needs access to your location.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + }, + ); + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + // Initial update when granted + updateLiveLocation(); + } else { + console.log('Location permission denied'); + } + } catch (err) { + console.warn(err); + } + } else { + // For iOS (assuming configurations are set in Info.plist) + Geolocation.requestAuthorization(); + updateLiveLocation(); + } + }; + + const updateLiveLocation = async () => { + if (!user?.id) return; + + Geolocation.getCurrentPosition( + async position => { + const { latitude, longitude } = position.coords; + try { + await Api.post('/workers/location', { + worker_id: user.id, + lat: latitude, + lng: longitude + }); + console.log(`Live location updated: ${latitude}, ${longitude}`); + } catch (error) { + console.log('Error updating live location:', error); + } + }, + error => console.log('Geolocation Error:', error.message), + { enableHighAccuracy: false, timeout: 30000, maximumAge: 10000 } + ); + }; + + const onRefresh = React.useCallback(async () => { + setIsRefreshing(true); + // Force an immediate location update + await updateLiveLocation(); + // Mock a slight delay so the spinner shows clearly for the user + setTimeout(() => setIsRefreshing(false), 1000); + }, [user?.id]); + /* DATA */ @@ -87,55 +181,70 @@ const HomeScreen = () => { }, ]; - /*RENDER FUNCTIONS*/ - const renderSafetyItem = ({ item }) => { - return ( - navigation.navigate(item.screen)} - > - - {item.title} - {item.desc} - - ); + const toggleStatus = async () => { + if (isLoadingToggle) return; // Prevent multiple requests + + const newStatus = !isActive; + setIsLoadingToggle(true); + setIsActive(newStatus); // Optimistic update + + // Animate toggle + Animated.timing(slideAnimation, { + toValue: newStatus ? 1 : 0, + duration: 250, + easing: Easing.bezier(0.25, 0.1, 0.25, 1), + useNativeDriver: false, + }).start(); + + try { + const response = await Api.post(`/workers/toggle-status/${user.id}`); + + if (response.data && response.data.success !== undefined && !response.data.success) { + // Revert if explicit failure + setIsActive(!newStatus); + Animated.timing(slideAnimation, { + toValue: !newStatus ? 1 : 0, + duration: 250, + useNativeDriver: false, + }).start(); + } + } catch (error) { + console.log("Error toggling status:", error); + // Properly revert the optimistic update + setIsActive(!newStatus); + Animated.timing(slideAnimation, { + toValue: !newStatus ? 1 : 0, + duration: 250, + useNativeDriver: false, + }).start(); + } finally { + setIsLoadingToggle(false); + } }; - const renderTaskItem = ({ item }) => ( - - - - - - - {item.title} - Due: {item.date} - + // Animation configurations + const trackColor = slideAnimation.interpolate({ + inputRange: [0, 1], + outputRange: ["#E0E0E0", "#4CAF50"] // Gray -> Green + }); - - - ); + const thumbTranslateX = slideAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [2, isHovered ? 78 : 32] // Moves according to width (Expanded vs Collapsed) + }); - const renderTrainingItem = ({ item }) => ( - - - - + const containerWidth = isHovered ? 110 : 64; // Expanding width container - {item.title} - {item.time} - - ); return ( - + + } + > @@ -176,20 +285,54 @@ const HomeScreen = () => { {/* Greeting */} + + {user?.image ? ( + + ) : ( + + )} - {user?.image ? ( - - ) : ( - - )} - - - Good Morning, - {user.Name} + + Good Morning, + {user.Name} + + + !isLoadingToggle && setIsHovered(true)} + onPressOut={() => setIsHovered(false)} + style={[styles.customToggleOuterContainer, { opacity: isLoadingToggle ? 0.7 : 1 }]} + disabled={isLoadingToggle} + > + + + {isHovered && !isLoadingToggle && ( + + {isActive ? "ACTIVE" : "INACTIVE"} + + )} + + + + + + {/* My Tasks */} @@ -199,7 +342,7 @@ const HomeScreen = () => { } keyExtractor={(item) => item.id} scrollEnabled={false} /> @@ -215,7 +358,7 @@ const HomeScreen = () => { } keyExtractor={(item) => item.id} horizontal showsHorizontalScrollIndicator={false} @@ -228,7 +371,7 @@ const HomeScreen = () => { navigation.navigate(item.screen)} />} keyExtractor={(item) => item.id} numColumns={2} columnWrapperStyle={{ justifyContent: "space-between" }} @@ -241,197 +384,3 @@ const HomeScreen = () => { }; export default HomeScreen; - -const styles = StyleSheet.create({ - - container: { - flex: 1, - backgroundColor: "#FFF", - }, - - header: { - paddingTop: 50, - paddingBottom: 15, - paddingHorizontal: 20, - }, - - headerRow: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - }, - - logoText: { - fontSize: 24, - fontWeight: "bold", - color: "#FFF", - }, - - logoHighlight: { - color: "#FFD54F", - }, - - notificationWrapper: { - position: "relative", - }, - - badge: { - position: "absolute", - top: -6, - right: -6, - backgroundColor: "red", - minWidth: 18, - height: 18, - borderRadius: 9, - justifyContent: "center", - alignItems: "center", - }, - - badgeText: { - color: "#fff", - fontSize: 10, - fontWeight: "bold", - }, - - whiteCard: { - backgroundColor: "#fff", - borderTopLeftRadius: 30, - borderTopRightRadius: 30, - padding: 20, - paddingTop: 30, - }, - - greetingRow: { - flexDirection: "row", - alignItems: "center", - marginBottom: 20, - }, - - goodMorning: { - fontSize: 15, - color: "#444", - }, - - username: { - fontSize: 18, - fontWeight: "bold", - }, - - sectionHeader: { - flexDirection: "row", - justifyContent: "space-between", - marginTop: 20, - }, - - sectionTitle: { - fontSize: 18, - fontWeight: "bold", - marginBottom: 10 - }, - - taskCard: { - flexDirection: "row", - alignItems: "center", - padding: 12, - backgroundColor: "#F8F8F8", - borderRadius: 12, - marginTop: 12, - }, - - taskIconBox: { - width: 40, - height: 40, - borderRadius: 10, - backgroundColor: "#1E3C72", - justifyContent: "center", - alignItems: "center", - marginRight: 12, - }, - - taskTitle: { - fontSize: 15, - fontWeight: "600", - }, - - taskDate: { - fontSize: 12, - color: "#777", - }, - - checkBox: { - width: 20, - height: 20, - borderRadius: 6, - borderWidth: 1.5, - borderColor: "#aaa", - }, - - profileImage: { - width: 50, - height: 50, - borderRadius: 30, - }, - - videoCard: { - width: 180, - marginRight: 15, - marginTop: 15, - }, - - videoImage: { - height: 110, - width: "100%", - borderRadius: 12, - }, - - playBtn: { - position: "absolute", - top: 35, - left: 70, - }, - - videoTitle: { - marginTop: 5, - fontWeight: "600", - }, - - videoTime: { - fontSize: 12, - color: "#777", - }, - - trainingVideoCard: { - width: 250, - marginRight: 15, - marginTop: 15, - backgroundColor: "#F8F8F8", - borderRadius: 14, - padding: 8, - }, - - youtubeWrapper: { - borderRadius: 12, - overflow: "hidden", - }, - - - gridCard: { - width: "47%", - backgroundColor: "#F8F8F8", - padding: 15, - borderRadius: 15, - marginBottom: 15, - }, - - gridTitle: { - marginTop: 10, - fontWeight: "700", - }, - - gridDesc: { - fontSize: 12, - color: "#777", - marginTop: 3, - }, - -}); diff --git a/src/screens/HomeScreen/homeScreenModals.js b/src/screens/HomeScreen/homeScreenModals.js new file mode 100644 index 0000000..c7be34f --- /dev/null +++ b/src/screens/HomeScreen/homeScreenModals.js @@ -0,0 +1,50 @@ +import React from "react"; +import { View, Text, TouchableOpacity } from "react-native"; +import Icon from "react-native-vector-icons/Feather"; +import YoutubePlayer from "react-native-youtube-iframe"; +import { styles } from "./homeScreenStyles"; + +export const SafetyItem = ({ item, onPress }) => { + return ( + + + {item.title} + {item.desc} + + ); +}; + +export const TaskItem = ({ item }) => ( + + + + + + + {item.title} + Due: {item.date} + + + + +); + +export const TrainingItem = ({ item }) => ( + + + + + + {item.title} + {item.time} + +); diff --git a/src/screens/HomeScreen/homeScreenStyles.js b/src/screens/HomeScreen/homeScreenStyles.js new file mode 100644 index 0000000..e9cf502 --- /dev/null +++ b/src/screens/HomeScreen/homeScreenStyles.js @@ -0,0 +1,205 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#FFF", + }, + header: { + paddingTop: 50, + paddingBottom: 15, + paddingHorizontal: 20, + }, + headerRow: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + logoText: { + fontSize: 24, + fontWeight: "bold", + color: "#FFF", + }, + logoHighlight: { + color: "#FFD54F", + }, + notificationWrapper: { + position: "relative", + }, + badge: { + position: "absolute", + top: -6, + right: -6, + backgroundColor: "red", + minWidth: 18, + height: 18, + borderRadius: 9, + justifyContent: "center", + alignItems: "center", + }, + badgeText: { + color: "#fff", + fontSize: 10, + fontWeight: "bold", + }, + whiteCard: { + backgroundColor: "#fff", + borderTopLeftRadius: 30, + borderTopRightRadius: 30, + padding: 20, + paddingTop: 30, + }, + greetingRow: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + marginBottom: 20, + }, + userInfoWrapper: { + flexDirection: "row", + alignItems: "center", + }, + customToggleOuterContainer: { + alignItems: "center", + justifyContent: "center", + }, + customToggleTrack: { + height: 34, + borderRadius: 17, + padding: 2, + flexDirection: "row", + alignItems: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.15, + shadowRadius: 3, + elevation: 3, + overflow: 'hidden', + }, + customToggleThumb: { + width: 30, + height: 30, + borderRadius: 15, + backgroundColor: "#FFFFFF", + position: "absolute", + justifyContent: "center", + alignItems: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.2, + shadowRadius: 2, + elevation: 4, + }, + customToggleTextInner: { + position: 'absolute', + fontSize: 10, + fontWeight: "bold", + letterSpacing: 0.5, + }, + goodMorning: { + fontSize: 15, + color: "#444", + }, + username: { + fontSize: 18, + fontWeight: "bold", + }, + sectionHeader: { + flexDirection: "row", + justifyContent: "space-between", + marginTop: 20, + }, + sectionTitle: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 10 + }, + taskCard: { + flexDirection: "row", + alignItems: "center", + padding: 12, + backgroundColor: "#F8F8F8", + borderRadius: 12, + marginTop: 12, + }, + taskIconBox: { + width: 40, + height: 40, + borderRadius: 10, + backgroundColor: "#1E3C72", + justifyContent: "center", + alignItems: "center", + marginRight: 12, + }, + taskTitle: { + fontSize: 15, + fontWeight: "600", + }, + taskDate: { + fontSize: 12, + color: "#777", + }, + checkBox: { + width: 20, + height: 20, + borderRadius: 6, + borderWidth: 1.5, + borderColor: "#aaa", + }, + profileImage: { + width: 50, + height: 50, + borderRadius: 30, + }, + videoCard: { + width: 180, + marginRight: 15, + marginTop: 15, + }, + videoImage: { + height: 110, + width: "100%", + borderRadius: 12, + }, + playBtn: { + position: "absolute", + top: 35, + left: 70, + }, + videoTitle: { + marginTop: 5, + fontWeight: "600", + }, + videoTime: { + fontSize: 12, + color: "#777", + }, + trainingVideoCard: { + width: 250, + marginRight: 15, + marginTop: 15, + backgroundColor: "#F8F8F8", + borderRadius: 14, + padding: 8, + }, + youtubeWrapper: { + borderRadius: 12, + overflow: "hidden", + }, + gridCard: { + width: "47%", + backgroundColor: "#F8F8F8", + padding: 15, + borderRadius: 15, + marginBottom: 15, + }, + gridTitle: { + marginTop: 10, + fontWeight: "700", + }, + gridDesc: { + fontSize: 12, + color: "#777", + marginTop: 3, + }, +}); diff --git a/src/screens/ProfileScreen/ProfileScreen.js b/src/screens/ProfileScreen/ProfileScreen.js index b301ada..218c7f3 100644 --- a/src/screens/ProfileScreen/ProfileScreen.js +++ b/src/screens/ProfileScreen/ProfileScreen.js @@ -1,317 +1,453 @@ -import React, { use } from "react"; -import { View, Text, StyleSheet, ScrollView } from "react-native"; +import React, { useEffect, useState, useRef } from "react"; +import { View, Text, StyleSheet, ScrollView, Modal, TouchableOpacity, Alert } from "react-native"; import Icon from "react-native-vector-icons/Feather"; import { useSelector } from "react-redux"; import { Image } from "react-native"; - +import styles from "./profileStyle" +import Api from "../../api/Api"; +import JobModal from "./jobModals"; +import { RefreshControl } from "react-native"; +import { Calendar } from "react-native-calendars"; const ProfileScreen = () => { const user = useSelector(state => state.auth.user); - return ( - + const [selectedJob, setSelectedJob] = useState(null); + const [modalVisible, setModalVisible] = useState(false); + //const [seconds, setSeconds] = useState(0); + const [isRunning, setIsRunning] = useState(false); + const [jobs, setJobs] = useState([]); + const [activeJobId, setActiveJobId] = useState(null); + const [filter, setFilter] = useState("all"); + const [loading, setLoading] = useState(false); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [calendarVisible, setCalendarVisible] = useState(false); + useEffect(() => { + if (user?.id) { + fetchJobs(); + } + }, [user]); + + useEffect(() => { + if (!user?.id) return; + + const interval = setInterval(() => { + if (!isRunning) { + fetchJobs(); + } + }, 30000); + + return () => clearInterval(interval); + }, [user, isRunning]); + + const jobsRef = useRef([]); + + useEffect(() => { + jobsRef.current = jobs; + }, [jobs]); + + useEffect(() => { + let interval = null; + + if (isRunning && activeJobId) { + interval = setInterval(() => { + setJobs(prevJobs => + prevJobs.map(job => + job.worker_task_id === activeJobId + ? { ...job, elapsedTime: (job.elapsedTime || 0) + 1 } + : job + ) + ); + }, 1000); + } + + return () => clearInterval(interval); + }, [isRunning, activeJobId]); + + const mapStatus = (status) => { + if (!status) return ""; + + const normalized = status.toLowerCase().trim(); + + if (normalized === "assigned") return "pending"; + if (normalized === "complete" || normalized === "completed") return "completed"; + if (normalized === "in-progress" || normalized === "in progress") return "in-progress"; + + + return normalized; + }; + + const fetchJobs = async () => { + try { + setLoading(true); + + const response = await Api.get(`/workers/assigned-work/${user.id}`); + + const formattedJobs = response.data.data.map(task => { + const requestData = task["User Request"] || task; + const previousJob = jobsRef.current.find( + j => j.worker_task_id === task.worker_task_id + ); + return { + id: task.id, + worker_task_id: task.worker_task_id, + service_request_id: task.service_request_id, + status: mapStatus(task.status), + Name: requestData.Name, + DateOfService: requestData.DateOfService, + Location: requestData.Location, + ServiceType: requestData.ServiceType, + Phone: requestData.Phone, + elapsedTime: + previousJob?.elapsedTime ?? + (task.worker_task_id === activeJobId ? 0 : 0) + }; + }); + + setJobs(formattedJobs); + + + const runningJob = formattedJobs.find(j => j.status === "in-progress"); + + // only set active job if we don't already have one + if (runningJob && !activeJobId) { + setActiveJobId(runningJob.worker_task_id); + } + + } catch (error) { + console.log("Error fetching jobs:", error.response?.data || error.message); + } finally { + setLoading(false); + } + };//to fetch individual user jobs + + + const currentJob = jobs.find( + j => j.worker_task_id === selectedJob?.worker_task_id + ); - - - - {user.image ? ( - - ) : ( - - )} - + //for sorting jobs based on status + const sortedAndFilteredJobs = jobs + .filter(job => { + if (filter === "all") return true; + + return job.status === filter; + }) + .sort((a, b) => { + // Always pin in-progress at top + if (a.status === "in-progress" && b.status !== "in-progress") return -1; + if (b.status === "in-progress" && a.status !== "in-progress") return 1; + + return 0; + }); + + const formatTime = (totalSeconds) => { + const hrs = Math.floor(totalSeconds / 3600); + const mins = Math.floor((totalSeconds % 3600) / 60); + const secs = totalSeconds % 60; + + return `${hrs.toString().padStart(2, "0")}:${mins + .toString() + .padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; + }; + + // Calculate marked dates for the calendar based on jobs + const markedDates = jobs.reduce((acc, job) => { + if (job.DateOfService) { + // Attempt to parse DateOfService. Since its format might vary, + // a basic implementation is provided. Adjust parsing according to your date format. + // Assuming DateOfService is like "2023-10-15" or parsable by Date + try { + const dateObj = new Date(job.DateOfService); + if (!isNaN(dateObj)) { + const dateString = dateObj.toISOString().split('T')[0]; + acc[dateString] = { + marked: true, + dotColor: job.status === "pending" ? "#9C7B00" : + job.status === "in-progress" ? "#2674FF" : + job.status === "completed" ? "#00994d" : "#00AFFF" + }; + } + } catch (e) { + console.log("Error parsing date for calendar:", job.DateOfService); + } + } + return acc; + }, {}); + + const handleStart = async (jobId) => { + + if (jobs.some(job => job.status === "in-progress")) { + Alert.alert("Task Conflict", "Only one task can be in progress at a time."); + return false; + } + + try { + await Api.post(`/workers/button-calls/start/${jobId}`); + + setActiveJobId(jobId); + setIsRunning(true); + + setJobs(prev => + prev.map(job => + job.worker_task_id === jobId + ? { ...job, status: "in-progress" } + : job + ) + ); + + return true; // 🔥 IMPORTANT + } catch (error) { + console.log(error); + return false; + } + }; + + const handleEnd = async (jobId) => { + try { + await Api.post(`/workers/button-calls/end/${jobId}`); + + setIsRunning(false); + //setSeconds(0); + setSelectedJob(prev => ({ ...prev, status: "completed" })); + + await fetchJobs(); // refresh UI + } catch (error) { + console.log("End error:", error.response?.data || error.message); + } + }; + const modalJob = currentJob ? currentJob : selectedJob; + return ( + + + + + + {user.image ? () : ()} + - {/*Name + Profession */} - - {user.Name} - {user.Skill} - + {/*Name + Profession */} + + {user.Name} + {user.Skill} + - {/* BODY CONTENT */} - - - - - {user.Area} J&K - - - - - - - {user.Experience} years experience - + {/* BODY CONTENT */} + + } + > + + + + {user.Area} J&K + - + - - - {user.MobileNo} - + + + {user.Experience} years experience - {/* ---------- EARNINGS CARD ---------- */} - - - ₹ - + - - Total Earnings - ₹12,450 - + + + {user.MobileNo} + - {/* ---------- RECENT JOBS ---------- */} - - Recent Jobs - + {/* ---------- EARNINGS CARD ---------- */} + + + ₹ - - - {/* Jobs*/} - - - Sarah Johnson - Dec 3, 2025 - - - ₹3200 - pending - - - - + + Total Earnings + ₹12,450 + + - - - John Smith - Dec 1, 2025 - - - ₹2,500 - in progress - + {/* ---------- RECENT JOBS ---------- */} + + Recent Jobs + + + setCalendarVisible(true)}> + + + + + setDropdownOpen(!dropdownOpen)} + style={{ padding: 5 }} + > + + + + {dropdownOpen && ( + + {["all", "pending", "in-progress", "completed"].map(type => ( + { + setFilter(type); + setDropdownOpen(false); + }} + > + + {type.toUpperCase()} + + + ))} + + )} + + + + {jobs.length === 0 ? ( + + No jobs found + + ) : ( + sortedAndFilteredJobs.map((job) => ( + { + /* setSelectedJob(job); + setIsRunning(false); + //setSeconds(0); + setModalVisible(true);*/ + setSelectedJob(job); + setModalVisible(true); + if (job.status === "in-progress") { + if (job.status === "in-progress") { + if (!activeJobId) { + setActiveJobId(job.worker_task_id); + } + } else { + setIsRunning(false); + } + } else { + setIsRunning(false); + } + + }} + > + + + {job.Name} + + + + {job.DateOfService} + + + + + + {job.ServiceType} + + + + {job.status} + + + + )) + )} + - - - - - Mike Davis - Nov 28, 2025 - - - ₹800 - completed + + + setCalendarVisible(false)} + > + setCalendarVisible(false)} + > + true}> + + Your Schedule + setCalendarVisible(false)}> + + + + + { + // Find if any job is on this date + const jobOnDate = jobs.find((j) => { + if (!j.DateOfService) return false; + + const jobDate = new Date(j.DateOfService) + .toISOString() + .split("T")[0]; + + return jobDate === day.dateString; + }); + if (jobOnDate) { + setCalendarVisible(false); + setSelectedJob(jobOnDate); + setModalVisible(true); + } else { + Alert.alert("No Tasks", "You have no scheduled tasks on this date."); + } + }} + theme={{ + selectedDayBackgroundColor: '#00AFFF', + todayTextColor: '#00AFFF', + arrowColor: '#00AFFF', + }} + /> - - - - - ); + + + + ); }; export default ProfileScreen; -const styles = StyleSheet.create({ - - screenContainer: { - flex: 1, - backgroundColor: "#F5F7FA" - }, - - Bar: { - backgroundColor: "#00AFFF", - paddingTop: 50, - paddingBottom: 25, - paddingHorizontal: 20, - borderBottomLeftRadius: 30, - borderBottomRightRadius: 30, - elevation: 6, - }, - - Row: { - flexDirection: "row", - alignItems: "center", - }, - - avatarImage: { - width: 70, - height: 70, - borderRadius: 45, - backgroundColor: "#f1f1f1", - justifyContent: "center", - alignItems: "center", - elevation: 5, - }, - - NameWrapper: { - marginLeft: 15, - }, - - UserName: { - fontSize: 22, - color: "#fff", - fontWeight: "700", - }, - - Profession: { - fontSize: 15, - color: "#EAF9FF", - marginTop: 3, - }, - - bodyContent: { - paddingHorizontal: 20, - paddingTop: 20, - paddingBottom: 30 - }, - - userInfoCard: { - backgroundColor: "#fff", - padding: 18, - borderRadius: 15, - elevation: 3, - marginBottom: 15, - }, - - infoRow: { - flexDirection: "row", - alignItems: "center", - paddingVertical: 8, - gap: 12 - }, - - infoText: { - fontSize: 15, - color: "#333" - }, - - cardDivider: { - height: 1, - backgroundColor: "#eee", - marginVertical: 5 - }, - - earningsCard: { - backgroundColor: "#fff", - flexDirection: "row", - alignItems: "center", - padding: 18, - borderRadius: 15, - elevation: 3, - marginBottom: 15, - }, - - earningsIconBox: { - width: 45, - height: 45, - backgroundColor: "#2ECC71", - borderRadius: 25, - justifyContent: "center", - alignItems: "center", - marginRight: 15, - }, - - earningsDollarSymbol: { - color: "#fff", - fontWeight: "bold", - fontSize: 18 - }, - - earningsLabel: { - color: "#555", - fontSize: 14 - }, - - earningsAmount: { - fontSize: 24, - fontWeight: "700", - color: "#000" - }, - - recentJobsHeaderRow: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - marginBottom: 10, - }, - - recentJobsTitle: { - fontSize: 20, - fontWeight: "700" - }, - - recentJobsCard: { - backgroundColor: "#fff", - padding: 18, - borderRadius: 15, - elevation: 3, - marginBottom: 20, - }, - - jobRow: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - paddingVertical: 10, - }, - - jobCustomerName: { - fontSize: 16, - fontWeight: "600", - color: "#000" - }, - - jobDateText: { - fontSize: 12, - color: "#777", - marginTop: 3 - }, - - jobRightSection: { - alignItems: "flex-end" - }, - - jobPayment: { - fontSize: 16, - fontWeight: "700", - color: "#2ECC71" - }, - - jobStatusPending: { - backgroundColor: "#FFE483", - color: "#9C7B00", - paddingHorizontal: 10, - borderRadius: 8, - marginTop: 3, - fontSize: 12, - }, - - jobStatusProgress: { - backgroundColor: "#DCE9FF", - color: "#2674FF", - paddingHorizontal: 10, - borderRadius: 8, - marginTop: 3, - fontSize: 12, - }, - - jobStatusCompleted: { - backgroundColor: "#C8FFE0", - color: "#00994d", - paddingHorizontal: 10, - borderRadius: 8, - marginTop: 3, - fontSize: 12, - }, - -}); +/*Style sheet */ diff --git a/src/screens/ProfileScreen/jobModals.js b/src/screens/ProfileScreen/jobModals.js new file mode 100644 index 0000000..d666826 --- /dev/null +++ b/src/screens/ProfileScreen/jobModals.js @@ -0,0 +1,185 @@ +import React from "react"; +import { View, Text, Modal, TouchableOpacity } from "react-native"; +import Icon from "react-native-vector-icons/Feather"; +import styles from "./profileStyle"; +import { useEffect } from "react"; + +const JobModal = ({ + modalVisible, + setModalVisible, + selectedJob, + isRunning, + seconds, + formatTime, + setIsRunning, + setSelectedJob, + handleStart, + handleEnd, + setJobs +}) => { + + useEffect(() => { + + if (!selectedJob) return; + + if (selectedJob.status === "in-progress") { + setIsRunning(true); + } else { + setIsRunning(false); + } + + }, [selectedJob?.worker_task_id, selectedJob?.status]); + + if (!selectedJob) return null; + + const startJob = async () => { + + const success = await handleStart(selectedJob.worker_task_id); + + if (success) { + + setSelectedJob(prev => ({ + ...prev, + status: "in-progress" + })); + + setIsRunning(true); + } + }; + + const endJob = async () => { + await handleEnd(selectedJob.worker_task_id); + }; + + return ( + + + + + {/* HEADER */} + + + {selectedJob?.Name} + + + setModalVisible(false)}> + + + + + + + {/* DATE */} + + Date + + {selectedJob?.DateOfService} + + + + {/* ADDRESS */} + + Address + + {selectedJob?.Location} + + + + {/* SERVICE */} + + Service Type + + + {selectedJob?.ServiceType} + + + + + {/* CONTACT */} + + Contact no + + {selectedJob?.Phone} + + + + {/* STATUS */} + + Status + + {selectedJob?.status} + + + + {/* -------- PENDING -------- */} + {selectedJob?.status === "pending" && ( + + + console.log("Open map")} + > + View + + + + Start + + + + )} + + {/* -------- IN PROGRESS -------- */} + {selectedJob?.status === "in-progress" && ( + <> + + {formatTime(seconds)} + + + + + {isRunning ? ( + setIsRunning(false)} + > + Pause + + ) : ( + setIsRunning(true)} + > + Continue + + )} + + + End + + + + + )} + + {/* -------- COMPLETED -------- */} + {selectedJob?.status === "completed" && ( + + Job Completed ✅ + + )} + + + + + ); +}; + +export default JobModal; + diff --git a/src/screens/ProfileScreen/profileStyle.js b/src/screens/ProfileScreen/profileStyle.js new file mode 100644 index 0000000..d87adc5 --- /dev/null +++ b/src/screens/ProfileScreen/profileStyle.js @@ -0,0 +1,292 @@ +import {StyleSheet} from 'react-native'; + +const styles = StyleSheet.create({ + + screenContainer: { + flex: 1, backgroundColor: "#F5F7FA" + }, + + Bar: { + backgroundColor: "#00AFFF", + paddingTop: 50, + paddingBottom: 25, + paddingHorizontal: 20, + borderBottomLeftRadius: 30, + borderBottomRightRadius: 30, + elevation: 6, + }, + + Row: { + flexDirection: "row", alignItems: "center", + }, + + avatarImage: { + width: 70, + height: 70, + borderRadius: 45, + backgroundColor: "#f1f1f1", + justifyContent: "center", + alignItems: "center", + elevation: 5, + }, + + NameWrapper: { + marginLeft: 15, + }, + + UserName: { + fontSize: 22, color: "#fff", fontWeight: "700", + }, + + Profession: { + fontSize: 15, color: "#EAF9FF", marginTop: 3, + }, + + bodyContent: { + paddingHorizontal: 20, paddingTop: 20, paddingBottom: 30 + }, + + userInfoCard: { + backgroundColor: "#fff", padding: 18, borderRadius: 15, elevation: 3, marginBottom: 15, + }, + + infoRow: { + flexDirection: "row", alignItems: "center", paddingVertical: 8, gap: 12 + }, + + infoText: { + fontSize: 15, color: "#333" + }, + + cardDivider: { + height: 1, backgroundColor: "#eee", marginVertical: 5 + }, + + earningsCard: { + backgroundColor: "#fff", + flexDirection: "row", + alignItems: "center", + padding: 18, + borderRadius: 15, + elevation: 3, + marginBottom: 15, + }, + + earningsIconBox: { + width: 45, + height: 45, + backgroundColor: "#2ECC71", + borderRadius: 25, + justifyContent: "center", + alignItems: "center", + marginRight: 15, + }, + + earningsDollarSymbol: { + color: "#fff", fontWeight: "bold", fontSize: 18 + }, + + earningsLabel: { + color: "#555", fontSize: 14 + }, + + earningsAmount: { + fontSize: 24, fontWeight: "700", color: "#000" + }, + + recentJobsHeaderRow: { + flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 10, + }, + + recentJobsTitle: { + fontSize: 20, fontWeight: "700" + }, + + recentJobsCard: { + backgroundColor: "#fff", padding: 18, borderRadius: 15, elevation: 3, marginBottom: 20, + }, + + jobRow: { + flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingVertical: 10, + }, + + jobCustomerName: { + fontSize: 16, fontWeight: "600", color: "#000" + }, + + jobDateText: { + fontSize: 12, color: "#777", marginTop: 3 + }, + + jobRightSection: { + alignItems: "flex-end" + }, + + jobPayment: { + fontSize: 16, fontWeight: "700", color: "#2ECC71" + }, + + jobStatusPending: { + backgroundColor: "#FFE483", + color: "#9C7B00", + paddingHorizontal: 10, + borderRadius: 8, + marginTop: 3, + fontSize: 12, + }, + + jobStatusProgress: { + backgroundColor: "#DCE9FF", + color: "#2674FF", + paddingHorizontal: 10, + borderRadius: 8, + marginTop: 3, + fontSize: 12, + }, + + jobStatusCompleted: { + backgroundColor: "#C8FFE0", + color: "#00994d", + paddingHorizontal: 10, + borderRadius: 8, + marginTop: 3, + fontSize: 12, + }, modalOverlay: { + flex: 1, backgroundColor: "rgba(0,0,0,0.45)", justifyContent: "center", alignItems: "center", + }, + + modalContainer: { + width: "90%", backgroundColor: "#fff", borderRadius: 20, padding: 20, elevation: 12, + }, + + modalHeader: { + flexDirection: "row", justifyContent: "space-between", alignItems: "center", + }, + + modalTitle: { + fontSize: 20, fontWeight: "700", color: "#111", + }, + + modalDivider: { + height: 1, backgroundColor: "#eee", marginVertical: 15, + }, + + detailRow: { + flexDirection: "row", justifyContent: "space-between", marginBottom: 12, + }, + + detailRowColumn: { + marginBottom: 12, + }, + + detailLabel: { + fontSize: 13, color: "#777", + }, + + detailValue: { + fontSize: 15, fontWeight: "500", color: "#222", + }, + + amountText: { + fontSize: 16, fontWeight: "700", color: "#2ECC71", + }, + + statusBadge: { + backgroundColor: "#FFE483", paddingHorizontal: 10, paddingVertical: 4, borderRadius: 20, + }, + + statusText: { + fontSize: 12, fontWeight: "600", color: "#9C7B00", + }, + + buttonRow: { + flexDirection: "row", justifyContent: "space-between", marginTop: 20, + }, + + acceptButton: { + flex: 1, backgroundColor: "#00AFFF", padding: 12, borderRadius: 10, alignItems: "center", marginLeft: 8, + }, + + rejectButton: { + flex: 1, backgroundColor: "#FF4D4D", padding: 12, borderRadius: 10, alignItems: "center", marginRight: 8, + }, + + buttonText: { + color: "#fff", fontWeight: "600", + }, viewButton: { + flex: 1, backgroundColor: "#555", padding: 12, borderRadius: 10, alignItems: "center", marginRight: 8, + }, + + startButton: { + flex: 1, backgroundColor: "#00C853", padding: 12, borderRadius: 10, alignItems: "center", marginLeft: 8, + }, + + pauseButton: { + flex: 1, backgroundColor: "#FF9800", padding: 12, borderRadius: 10, alignItems: "center", marginRight: 8, + }, + + endButton: { + flex: 1, backgroundColor: "#D32F2F", padding: 12, borderRadius: 10, alignItems: "center", marginLeft: 8, + }, + + timerText: { + fontSize: 28, fontWeight: "700", textAlign: "center", marginVertical: 15, color: "#111", + }, filterContainer: { + flexDirection: "row", justifyContent: "space-between", marginBottom: 10, + }, + + filterButton: { + flex: 1, + paddingVertical: 6, + marginHorizontal: 4, + borderRadius: 20, + backgroundColor: "#EAEAEA", + alignItems: "center", + }, + + activeFilter: { + backgroundColor: "#00AFFF", + }, + + filterText: { + fontSize: 12, fontWeight: "600", color: "#555", + }, + + activeFilterText: { + color: "#fff", + }, + + dropdownHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: "#fff", + padding: 15, + borderRadius: 10, + elevation: 2, + }, + dropdownHeaderText: { + fontSize: 16, + fontWeight: "600", + color: "#333", + }, + dropdownList: { + backgroundColor: "#fff", + borderRadius: 10, + marginTop: 5, + elevation: 3, + paddingVertical: 5, + }, + dropdownItem: { + padding: 15, + borderBottomWidth: 1, + borderBottomColor: "#f0f0f0", + }, + dropdownItemText: { + fontSize: 15, + color: "#555", + }, + +}) + +export default styles; \ No newline at end of file