From e479b59ae3f8482e6cf524c7382078f3ecff2ec8 Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Mon, 22 Sep 2025 20:20:21 +0530 Subject: [PATCH] zoho peple screen modified and re authentication rmoved based on token availability --- src/modules/hr/navigation/HRNavigator.tsx | 2 + .../screens/ZohoPeopleDashboardScreen.tsx | 33 +- .../hr/zoho/screens/ZohoPeopleDataScreen.tsx | 607 ++++++++++++++++++ .../screens/IntegrationCategoryScreen.tsx | 61 +- 4 files changed, 691 insertions(+), 12 deletions(-) create mode 100644 src/modules/hr/zoho/screens/ZohoPeopleDataScreen.tsx diff --git a/src/modules/hr/navigation/HRNavigator.tsx b/src/modules/hr/navigation/HRNavigator.tsx index 2d09aaa..e2bd3de 100644 --- a/src/modules/hr/navigation/HRNavigator.tsx +++ b/src/modules/hr/navigation/HRNavigator.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import ZohoPeopleDashboardScreen from '@/modules/hr/zoho/screens/ZohoPeopleDashboardScreen'; +import ZohoPeopleDataScreen from '@/modules/hr/zoho/screens/ZohoPeopleDataScreen'; const Stack = createStackNavigator(); const HRNavigator = () => ( + ); diff --git a/src/modules/hr/zoho/screens/ZohoPeopleDashboardScreen.tsx b/src/modules/hr/zoho/screens/ZohoPeopleDashboardScreen.tsx index f177e79..16ac4e2 100644 --- a/src/modules/hr/zoho/screens/ZohoPeopleDashboardScreen.tsx +++ b/src/modules/hr/zoho/screens/ZohoPeopleDashboardScreen.tsx @@ -9,6 +9,7 @@ import { Dimensions } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; +import { useNavigation } from '@react-navigation/native'; import Icon from 'react-native-vector-icons/MaterialIcons'; import { Container, LoadingSpinner, ErrorState } from '@/shared/components/ui'; import { fetchZohoPeopleData } from '../store/hrSlice'; @@ -26,6 +27,7 @@ const { width } = Dimensions.get('window'); const ZohoPeopleDashboardScreen: React.FC = () => { const dispatch = useDispatch(); + const navigation = useNavigation(); const { colors, fonts, spacing, shadows } = useTheme(); const zohoPeopleState = useSelector(selectZohoPeopleState); @@ -74,12 +76,20 @@ const ZohoPeopleDashboardScreen: React.FC = () => { Zoho People Dashboard - - - + + navigation.navigate('ZohoPeopleData' as never)} + > + + + + + + @@ -442,6 +452,10 @@ const styles = StyleSheet.create({ fontSize: 20, marginLeft: 8, }, + headerRight: { + flexDirection: 'row', + alignItems: 'center', + }, refreshButton: { width: 36, height: 36, @@ -449,6 +463,13 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, + dataButton: { + width: 36, + height: 36, + borderRadius: 18, + justifyContent: 'center', + alignItems: 'center', + }, content: { flex: 1, }, diff --git a/src/modules/hr/zoho/screens/ZohoPeopleDataScreen.tsx b/src/modules/hr/zoho/screens/ZohoPeopleDataScreen.tsx new file mode 100644 index 0000000..fba2db2 --- /dev/null +++ b/src/modules/hr/zoho/screens/ZohoPeopleDataScreen.tsx @@ -0,0 +1,607 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, + RefreshControl, + FlatList, +} from 'react-native'; +import { useSelector, useDispatch } from 'react-redux'; +import type { AppDispatch } from '@/store/store'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import { Container, LoadingSpinner, ErrorState } from '@/shared/components/ui'; +import { useTheme } from '@/shared/styles/useTheme'; +import { fetchZohoPeopleData } from '../store/hrSlice'; +import { + selectZohoPeopleLoading, + selectZohoPeopleError, + selectAttendanceReport, + selectEmployeeForms, + selectHolidays, + selectLeaveData, +} from '../store/selectors'; + +const ZohoPeopleDataScreen: React.FC = () => { + const { colors, fonts, spacing, shadows } = useTheme(); + const dispatch = useDispatch(); + const [selectedTab, setSelectedTab] = useState<'employees' | 'attendance' | 'leaves' | 'holidays'>('employees'); + const [refreshing, setRefreshing] = useState(false); + + // Redux selectors + const attendanceReport = useSelector(selectAttendanceReport); + const employeeForms = useSelector(selectEmployeeForms); + const holidays = useSelector(selectHolidays); + const leaveData = useSelector(selectLeaveData); + const loading = useSelector(selectZohoPeopleLoading); + const error = useSelector(selectZohoPeopleError); + + // Fetch data using Redux + const fetchData = useCallback(async (showRefresh = false) => { + try { + if (showRefresh) { + setRefreshing(true); + } + await dispatch(fetchZohoPeopleData()).unwrap(); + } catch (err) { + console.error('Error fetching Zoho People data:', err); + } finally { + setRefreshing(false); + } + }, [dispatch]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleRefresh = useCallback(() => { + fetchData(true); + }, [fetchData]); + + const handleRetry = useCallback(() => { + fetchData(); + }, [fetchData]); + + // Loading state + if (loading && !employeeForms?.length && !attendanceReport?.length) { + return ; + } + + // Error state + if (error) { + return ; + } + + // Tab configuration + const tabs = [ + { key: 'employees', label: 'Employees', icon: 'people', count: employeeForms?.length || 0 }, + { key: 'attendance', label: 'Attendance', icon: 'schedule', count: attendanceReport?.length || 0 }, + { key: 'leaves', label: 'Leaves', icon: 'event-available', count: leaveData?.length || 0 }, + { key: 'holidays', label: 'Holidays', icon: 'event', count: holidays?.length || 0 }, + ] as const; + + const renderEmployeeCard = useCallback((item: any) => ( + + + + + + {item.FirstName} {item.LastName} + + + + + {item.Employeestatus} + + + + + + + Employee ID: + + {item.EmployeeID} + + + + + Department: + + {item.Department || 'N/A'} + + + + + Role: + + {item.Role || 'N/A'} + + + + {item.EmailID && ( + + Email: + {item.EmailID} + + )} + + + Experience: + + {item.Experience || 'N/A'} years + + + + + Date of Joining: + + {item.Dateofjoining || 'N/A'} + + + + + ), [colors, fonts]); + + const renderAttendanceCard = useCallback((item: any) => { + const today = new Date(); + const todayISO = today.toISOString().split('T')[0]; + const todayFormatted = todayISO.split('-').reverse().join('-'); + const todayAttendance = item.attendanceDetails?.[todayFormatted] || item.attendanceDetails?.[todayISO]; + + return ( + + + + + + {item.employeeDetails['first name']} {item.employeeDetails['last name']} + + + + + {todayAttendance?.Status || 'No Data'} + + + + + + + Employee ID: + + {item.employeeDetails.erecno} + + + + {todayAttendance && ( + <> + + First In: + + {todayAttendance.FirstIn || 'N/A'} + + + + + Last Out: + + {todayAttendance.LastOut || 'N/A'} + + + + + + Working Hours: + + {todayAttendance.WorkingHours || '00:00'} + + + + Total Hours: + + {todayAttendance.TotalHours || '00:00'} + + + + + )} + + + ); + }, [colors, fonts]); + + const renderLeaveCard = useCallback((item: any) => ( + + + + + + {item.Employee_ID} + + + + + {item.ApprovalStatus} + + + + + + + Leave Type: + + {item.Leavetype} + + + + + From: + + {item.From || 'N/A'} + + + + + To: + + {item.To || 'N/A'} + + + + + Days Taken: + + {item.Daystaken} days + + + + {item.Reasonforleave && ( + + Reason: + + {item.Reasonforleave} + + + )} + + + ), [colors, fonts]); + + const renderHolidayCard = useCallback((item: any) => ( + + + + + + {item.Name} + + + + + {item.isRestrictedHoliday ? 'Restricted' : item.isHalfday ? 'Half Day' : 'Full Day'} + + + + + + + Date: + + {item.Date || 'N/A'} + + + + + Location: + + {item.LocationName || 'All Locations'} + + + + + Shift: + + {item.ShiftName || 'N/A'} + + + + {item.Remarks && ( + + Remarks: + + {item.Remarks} + + + )} + + + ), [colors, fonts]); + + const renderTabContent = useCallback(() => { + const commonFlatListProps = { + showsVerticalScrollIndicator: false, + contentContainerStyle: styles.listContainer, + refreshControl: ( + + ), + removeClippedSubviews: true, + maxToRenderPerBatch: 10, + windowSize: 10, + initialNumToRender: 10, + }; + + switch (selectedTab) { + case 'employees': + return ( + renderEmployeeCard(item)} + keyExtractor={(_, index) => `emp-${index}`} + {...commonFlatListProps} + /> + ); + case 'attendance': + return ( + renderAttendanceCard(item)} + keyExtractor={(_, index) => `att-${index}`} + {...commonFlatListProps} + /> + ); + case 'leaves': + return ( + renderLeaveCard(item)} + keyExtractor={(_, index) => `leave-${index}`} + {...commonFlatListProps} + /> + ); + case 'holidays': + return ( + renderHolidayCard(item)} + keyExtractor={(_, index) => `hol-${index}`} + {...commonFlatListProps} + /> + ); + default: + return null; + } + }, [selectedTab, employeeForms, attendanceReport, leaveData, holidays, refreshing, handleRefresh, renderEmployeeCard, renderAttendanceCard, renderLeaveCard, renderHolidayCard]); + + return ( + + {/* Fixed Header */} + + + Zoho People Data + + + + + + + {/* Fixed Tabs */} + + + + {tabs.map((tab) => ( + setSelectedTab(tab.key)} + activeOpacity={0.8} + > + + + {tab.label} + + + + {tab.count} + + + + ))} + + + + + {/* Scrollable Content */} + + {renderTabContent()} + + + ); +}; + +// Helper functions to get status colors +const getAttendanceStatusColor = (status: string): string => { + const statusColors: Record = { + present: '#E9FAF2', + absent: '#FEF2F2', + weekend: '#F3F4F6', + '': '#F3F4F6', + }; + return statusColors[status?.toLowerCase()] || '#F3F4F6'; +}; + +const getLeaveStatusColor = (status: string): string => { + const statusColors: Record = { + approved: '#E9FAF2', + pending: '#FEF3C7', + rejected: '#FEF2F2', + draft: '#F3F4F6', + }; + return statusColors[status?.toLowerCase()] || '#F3F4F6'; +}; + +const getHolidayTypeColor = (holiday: any): string => { + if (holiday.isRestrictedHoliday) return '#FEF3C7'; + if (holiday.isHalfday) return '#E0F2FE'; + return '#E9FAF2'; +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 12, + }, + title: { + fontSize: 24, + }, + tabsContainer: { + backgroundColor: '#FFFFFF', + paddingVertical: 12, + borderBottomWidth: 1, + borderBottomColor: '#E5E7EB', + }, + tabs: { + flexDirection: 'row', + paddingHorizontal: 16, + }, + tab: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 10, + marginRight: 8, + borderRadius: 20, + backgroundColor: '#F1F5F9', + }, + tabText: { + marginLeft: 6, + fontSize: 14, + }, + countBadge: { + marginLeft: 6, + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 10, + minWidth: 20, + alignItems: 'center', + }, + countText: { + fontSize: 12, + }, + content: { + flex: 1, + }, + listContainer: { + paddingHorizontal: 16, + paddingBottom: 20, + }, + card: { + borderRadius: 12, + borderWidth: 1, + padding: 16, + marginBottom: 12, + ...StyleSheet.flatten({ + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }), + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 12, + }, + cardTitleContainer: { + flexDirection: 'row', + alignItems: 'center', + flex: 1, + }, + cardTitle: { + fontSize: 16, + marginLeft: 8, + }, + statusBadge: { + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 12, + }, + statusText: { + fontSize: 12, + textTransform: 'capitalize', + }, + cardContent: { + gap: 8, + }, + infoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + infoLabel: { + fontSize: 14, + flex: 1, + }, + infoValue: { + fontSize: 14, + textAlign: 'right', + flex: 1, + }, + amountRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginTop: 8, + paddingTop: 8, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: '#E5E7EB', + }, + amountItem: { + flex: 1, + alignItems: 'center', + }, + amountLabel: { + fontSize: 12, + marginBottom: 4, + }, + amountValue: { + fontSize: 16, + fontWeight: 'bold', + }, +}); + +export default ZohoPeopleDataScreen; diff --git a/src/modules/integrations/screens/IntegrationCategoryScreen.tsx b/src/modules/integrations/screens/IntegrationCategoryScreen.tsx index c47d7b7..28bd3e1 100644 --- a/src/modules/integrations/screens/IntegrationCategoryScreen.tsx +++ b/src/modules/integrations/screens/IntegrationCategoryScreen.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, Text, StyleSheet, FlatList, TouchableOpacity } from 'react-native'; +import { View, Text, StyleSheet, FlatList, TouchableOpacity, Alert } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { useTheme } from '@/shared/styles/useTheme'; import type { RouteProp } from '@react-navigation/native'; @@ -9,6 +9,7 @@ import { setSelectedService } from '@/modules/integrations/store/integrationsSli import type { AppDispatch } from '@/store/store'; import ZohoAuth from './ZohoAuth'; import { Modal } from 'react-native'; +import httpClient from '@/services/http'; type Route = RouteProp; @@ -59,8 +60,35 @@ const IntegrationCategoryScreen: React.FC = ({ route }) => { const dispatch = useDispatch(); const [showZohoAuth, setShowZohoAuth] = React.useState(false); const [pendingService, setPendingService] = React.useState(null); + const [isCheckingToken, setIsCheckingToken] = React.useState(false); const services = servicesMap[route.params.categoryKey] ?? []; + // Check for existing Zoho token + const checkZohoToken = async (serviceKey: string) => { + try { + setIsCheckingToken(true); + const response = await httpClient.get('/api/v1/users/decrypt-token?service_name=zoho'); + const responseData = response.data as any; + + if (responseData.status === 'success' && responseData.data?.accessToken) { + // Token exists and is valid, navigate directly + console.log('Zoho token found, navigating directly to:', serviceKey); + dispatch(setSelectedService(serviceKey)); + } else { + // No valid token, show auth modal + setPendingService(serviceKey); + setShowZohoAuth(true); + } + } catch (error) { + console.log('No valid Zoho token found, showing auth modal'); + // Token doesn't exist or is invalid, show auth modal + setPendingService(serviceKey); + setShowZohoAuth(true); + } finally { + setIsCheckingToken(false); + } + }; + return ( = ({ route }) => { ItemSeparatorComponent={() => } renderItem={({ item }) => ( { // Here we decide whether to require Zoho authentication first const requiresZohoAuth = item.key === 'zohoProjects' || item.key === 'zohoPeople' || item.key === 'zohoBooks' || item.key === 'zohoCRM'; - console.log('key pressed',item.key) + console.log('key pressed', item.key); + if (requiresZohoAuth) { - setPendingService(item.key); - setShowZohoAuth(true); + checkZohoToken(item.key); } else { dispatch(setSelectedService(item.key)); } @@ -88,6 +117,13 @@ const IntegrationCategoryScreen: React.FC = ({ route }) => { {item.title} + {isCheckingToken && ( + + + Checking... + + + )} )} /> @@ -131,6 +167,9 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingVertical: 12, }, + disabledRow: { + opacity: 0.6, + }, iconCircle: { width: 36, height: 36, @@ -139,7 +178,17 @@ const styles = StyleSheet.create({ justifyContent: 'center', marginRight: 12, }, - title: { fontSize: 14 }, + title: { + fontSize: 14, + flex: 1, + }, + loadingContainer: { + marginLeft: 8, + }, + loadingText: { + fontSize: 12, + fontStyle: 'italic', + }, }); export default IntegrationCategoryScreen;