zoho peple screen modified and re authentication rmoved based on token availability
This commit is contained in:
parent
54c751324c
commit
e479b59ae3
@ -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 = () => (
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen name="ZohoPeopleDashboard" component={ZohoPeopleDashboardScreen} options={{headerShown:false}} />
|
||||
<Stack.Screen name="ZohoPeopleData" component={ZohoPeopleDataScreen} options={{headerShown:false}} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
|
||||
@ -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
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.refreshButton, { backgroundColor: colors.primary }]}
|
||||
onPress={handleRefresh}
|
||||
>
|
||||
<Icon name="refresh" size={20} color={colors.surface} />
|
||||
</TouchableOpacity>
|
||||
<View style={styles.headerRight}>
|
||||
<TouchableOpacity
|
||||
style={[styles.dataButton, { backgroundColor: '#3B82F6', marginRight: 8 }]}
|
||||
onPress={() => navigation.navigate('ZohoPeopleData' as never)}
|
||||
>
|
||||
<Icon name="list" size={20} color={colors.surface} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.refreshButton, { backgroundColor: colors.primary }]}
|
||||
onPress={handleRefresh}
|
||||
>
|
||||
<Icon name="refresh" size={20} color={colors.surface} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
607
src/modules/hr/zoho/screens/ZohoPeopleDataScreen.tsx
Normal file
607
src/modules/hr/zoho/screens/ZohoPeopleDataScreen.tsx
Normal file
@ -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<AppDispatch>();
|
||||
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 <LoadingSpinner />;
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (error) {
|
||||
return <ErrorState onRetry={handleRetry} />;
|
||||
}
|
||||
|
||||
// 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) => (
|
||||
<View style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.cardTitleContainer}>
|
||||
<Icon name="person" size={20} color={colors.primary} />
|
||||
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]}>
|
||||
{item.FirstName} {item.LastName}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: item.Employeestatus === 'Active' ? '#E9FAF2' : '#FEF2F2' }]}>
|
||||
<Text style={[styles.statusText, { color: item.Employeestatus === 'Active' ? '#10B981' : '#EF4444', fontFamily: fonts.medium }]}>
|
||||
{item.Employeestatus}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardContent}>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Employee ID:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.EmployeeID}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Department:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Department || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Role:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Role || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{item.EmailID && (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Email:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>{item.EmailID}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Experience:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Experience || 'N/A'} years
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Date of Joining:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Dateofjoining || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
), [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 (
|
||||
<View style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.cardTitleContainer}>
|
||||
<Icon name="schedule" size={20} color={colors.primary} />
|
||||
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]}>
|
||||
{item.employeeDetails['first name']} {item.employeeDetails['last name']}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: getAttendanceStatusColor(todayAttendance?.Status) }]}>
|
||||
<Text style={[styles.statusText, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{todayAttendance?.Status || 'No Data'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardContent}>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Employee ID:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.employeeDetails.erecno}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{todayAttendance && (
|
||||
<>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>First In:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{todayAttendance.FirstIn || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Last Out:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{todayAttendance.LastOut || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.amountRow}>
|
||||
<View style={styles.amountItem}>
|
||||
<Text style={[styles.amountLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Working Hours:</Text>
|
||||
<Text style={[styles.amountValue, { color: colors.primary, fontFamily: fonts.bold }]}>
|
||||
{todayAttendance.WorkingHours || '00:00'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.amountItem}>
|
||||
<Text style={[styles.amountLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Total Hours:</Text>
|
||||
<Text style={[styles.amountValue, { color: '#10B981', fontFamily: fonts.bold }]}>
|
||||
{todayAttendance.TotalHours || '00:00'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}, [colors, fonts]);
|
||||
|
||||
const renderLeaveCard = useCallback((item: any) => (
|
||||
<View style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.cardTitleContainer}>
|
||||
<Icon name="event-available" size={20} color={colors.primary} />
|
||||
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]}>
|
||||
{item.Employee_ID}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: getLeaveStatusColor(item.ApprovalStatus) }]}>
|
||||
<Text style={[styles.statusText, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.ApprovalStatus}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardContent}>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Leave Type:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Leavetype}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>From:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.From || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>To:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.To || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Days Taken:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Daystaken} days
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{item.Reasonforleave && (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Reason:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Reasonforleave}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
), [colors, fonts]);
|
||||
|
||||
const renderHolidayCard = useCallback((item: any) => (
|
||||
<View style={[styles.card, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.cardTitleContainer}>
|
||||
<Icon name="event" size={20} color={colors.primary} />
|
||||
<Text style={[styles.cardTitle, { color: colors.text, fontFamily: fonts.bold }]}>
|
||||
{item.Name}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: getHolidayTypeColor(item) }]}>
|
||||
<Text style={[styles.statusText, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.isRestrictedHoliday ? 'Restricted' : item.isHalfday ? 'Half Day' : 'Full Day'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardContent}>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Date:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Date || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Location:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.LocationName || 'All Locations'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Shift:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.ShiftName || 'N/A'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{item.Remarks && (
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={[styles.infoLabel, { color: colors.textLight, fontFamily: fonts.regular }]}>Remarks:</Text>
|
||||
<Text style={[styles.infoValue, { color: colors.text, fontFamily: fonts.medium }]}>
|
||||
{item.Remarks}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
), [colors, fonts]);
|
||||
|
||||
const renderTabContent = useCallback(() => {
|
||||
const commonFlatListProps = {
|
||||
showsVerticalScrollIndicator: false,
|
||||
contentContainerStyle: styles.listContainer,
|
||||
refreshControl: (
|
||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
||||
),
|
||||
removeClippedSubviews: true,
|
||||
maxToRenderPerBatch: 10,
|
||||
windowSize: 10,
|
||||
initialNumToRender: 10,
|
||||
};
|
||||
|
||||
switch (selectedTab) {
|
||||
case 'employees':
|
||||
return (
|
||||
<FlatList
|
||||
data={employeeForms || []}
|
||||
renderItem={({ item }) => renderEmployeeCard(item)}
|
||||
keyExtractor={(_, index) => `emp-${index}`}
|
||||
{...commonFlatListProps}
|
||||
/>
|
||||
);
|
||||
case 'attendance':
|
||||
return (
|
||||
<FlatList
|
||||
data={attendanceReport || []}
|
||||
renderItem={({ item }) => renderAttendanceCard(item)}
|
||||
keyExtractor={(_, index) => `att-${index}`}
|
||||
{...commonFlatListProps}
|
||||
/>
|
||||
);
|
||||
case 'leaves':
|
||||
return (
|
||||
<FlatList
|
||||
data={leaveData || []}
|
||||
renderItem={({ item }) => renderLeaveCard(item)}
|
||||
keyExtractor={(_, index) => `leave-${index}`}
|
||||
{...commonFlatListProps}
|
||||
/>
|
||||
);
|
||||
case 'holidays':
|
||||
return (
|
||||
<FlatList
|
||||
data={holidays || []}
|
||||
renderItem={({ item }) => renderHolidayCard(item)}
|
||||
keyExtractor={(_, index) => `hol-${index}`}
|
||||
{...commonFlatListProps}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [selectedTab, employeeForms, attendanceReport, leaveData, holidays, refreshing, handleRefresh, renderEmployeeCard, renderAttendanceCard, renderLeaveCard, renderHolidayCard]);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
{/* Fixed Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={[styles.title, { color: colors.text, fontFamily: fonts.bold }]}>
|
||||
Zoho People Data
|
||||
</Text>
|
||||
<TouchableOpacity onPress={handleRefresh} disabled={refreshing}>
|
||||
<Icon name="refresh" size={24} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Fixed Tabs */}
|
||||
<View style={styles.tabsContainer}>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<View style={styles.tabs}>
|
||||
{tabs.map((tab) => (
|
||||
<TouchableOpacity
|
||||
key={tab.key}
|
||||
style={[
|
||||
styles.tab,
|
||||
selectedTab === tab.key && { backgroundColor: colors.primary },
|
||||
]}
|
||||
onPress={() => setSelectedTab(tab.key)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Icon
|
||||
name={tab.icon}
|
||||
size={20}
|
||||
color={selectedTab === tab.key ? colors.surface : colors.textLight}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.tabText,
|
||||
{
|
||||
color: selectedTab === tab.key ? colors.surface : colors.textLight,
|
||||
fontFamily: fonts.medium,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
styles.countBadge,
|
||||
{ backgroundColor: selectedTab === tab.key ? colors.surface : colors.primary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.countText,
|
||||
{
|
||||
color: selectedTab === tab.key ? colors.primary : colors.surface,
|
||||
fontFamily: fonts.bold,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{tab.count}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<View style={styles.content}>
|
||||
{renderTabContent()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper functions to get status colors
|
||||
const getAttendanceStatusColor = (status: string): string => {
|
||||
const statusColors: Record<string, string> = {
|
||||
present: '#E9FAF2',
|
||||
absent: '#FEF2F2',
|
||||
weekend: '#F3F4F6',
|
||||
'': '#F3F4F6',
|
||||
};
|
||||
return statusColors[status?.toLowerCase()] || '#F3F4F6';
|
||||
};
|
||||
|
||||
const getLeaveStatusColor = (status: string): string => {
|
||||
const statusColors: Record<string, string> = {
|
||||
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;
|
||||
@ -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<IntegrationsStackParamList, 'IntegrationCategory'>;
|
||||
|
||||
@ -59,8 +60,35 @@ const IntegrationCategoryScreen: React.FC<Props> = ({ route }) => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const [showZohoAuth, setShowZohoAuth] = React.useState(false);
|
||||
const [pendingService, setPendingService] = React.useState<string | null>(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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<FlatList
|
||||
@ -70,15 +98,16 @@ const IntegrationCategoryScreen: React.FC<Props> = ({ route }) => {
|
||||
ItemSeparatorComponent={() => <View style={[styles.sep, { backgroundColor: colors.border }]} />}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.row}
|
||||
style={[styles.row, isCheckingToken && styles.disabledRow]}
|
||||
activeOpacity={0.8}
|
||||
disabled={isCheckingToken}
|
||||
onPress={() => {
|
||||
// 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<Props> = ({ route }) => {
|
||||
<Icon name={item.icon} size={20} color={colors.primary} />
|
||||
</View>
|
||||
<Text style={[styles.title, { color: colors.text, fontFamily: fonts.medium }]}>{item.title}</Text>
|
||||
{isCheckingToken && (
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text style={[styles.loadingText, { color: colors.textLight, fontFamily: fonts.regular }]}>
|
||||
Checking...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user