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;