Skip to content

Commit

Permalink
ft(#92):Login History(activities) (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
pacifiquemboni authored Nov 14, 2024
1 parent 9acae12 commit 3d57b3e
Show file tree
Hide file tree
Showing 10 changed files with 4,609 additions and 1,787 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { GET_TRAINEE_ATTENDANCE } from '@/graphql/queries/Attendance';
import { useColorScheme } from 'react-native';
import { useToast } from 'react-native-toast-notifications';
import { jwtDecode } from 'jwt-decode';
import { useTranslation } from 'react-i18next';


interface DayStatus {
date: string;
Expand Down Expand Up @@ -38,7 +40,7 @@ interface TraineeAttendanceData {
phases: PhaseData[];
}

const staticDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
const staticDays = ['mon', 'tue', 'wed', 'thu', 'fri'];

export default function TraineeAttendance() {
const [selectedWeek, setSelectedWeek] = useState<string | null>(null);
Expand All @@ -49,6 +51,7 @@ export default function TraineeAttendance() {


const toast = useToast();
const {t} = useTranslation ();

const colorScheme = useColorScheme();
const textColor = colorScheme === 'dark' ? 'text-gray-100' : 'text-gray-800';
Expand Down Expand Up @@ -145,14 +148,14 @@ export default function TraineeAttendance() {
return (
<ScrollView className={`${bgColor}`}>
<View className="m-4">
<Text className={`${textColor} font-bold text-xl`}>Your Attendance</Text>
<Text className={`${textColor} font-bold text-xl`}>{t('attendance.attendance')}</Text>
<View className="flex-row items-baseline justify-between">
<Text className={`${textColor} font-bold text-xl`}>{phaseName}</Text>
<TouchableOpacity
className={`border w-32 mt-5 ${nthbgColor} rounded-lg p-2 flex-row justify-between items-center`}
className={`border w-fit mt-5 ${nthbgColor} rounded-lg p-2 flex-row justify-between items-center`}
onPress={() => setDropdownVisible(!dropdownVisible)}
>
<Text className={`${textColor} font-bold text-xl`}>Week:</Text>
<Text className={`${textColor} font-bold text-xl`}>{t('attendance.week')}</Text>
<Text className={`${textColor} font-bold text-xl`}>{selectedWeek}</Text>
<AntDesign name="down" size={18} color={Color} />
</TouchableOpacity>
Expand All @@ -179,9 +182,9 @@ export default function TraineeAttendance() {

<View className="border mt-5">
<View className={`flex-row justify-around h-10 ${thbg}`}>
<Text className={`pt-2 flex-1 h-10 text-center font-bold ${textColor}`}>Day</Text>
<Text className={`pt-2 flex-1 h-10 text-center font-bold ${textColor}`}>Date</Text>
<Text className={`pt-2 flex-1 h-10 text-center font-bold ${textColor}`}>Score</Text>
<Text className={`pt-2 flex-1 h-10 text-center font-bold ${textColor}`}>{t('attendance.day')}</Text>
<Text className={`pt-2 flex-1 h-10 text-center font-bold ${textColor}`}>{t('attendance.date')}</Text>
<Text className={`pt-2 flex-1 h-10 text-center font-bold ${textColor}`}>{t('attendance.score')}</Text>
</View>

{selectedWeekData ? (
Expand All @@ -192,7 +195,7 @@ export default function TraineeAttendance() {
key={day}
className={`flex-row justify-around h-12 items-center ${index % 2 === 0 ? nthbgColor : bgColor}`}
>
<Text className={`${textColor} flex-1 text-center`}>{day}</Text>
<Text className={`${textColor} flex-1 text-center`}>{t(`days.${day}`)}</Text>
<Text className={`${textColor} flex-1 text-center`}>
{date ? formatDate(date) : '-'}
</Text>
Expand All @@ -214,36 +217,36 @@ export default function TraineeAttendance() {
})
) : (
<Text className={`${textColor} text-center p-4`}>
You don't have an attendance record in the system at the moment.
{t('attendance.youDontHaveAttendance')}
</Text>
)}
</View>
<View className="flex flex-col gap-y-8 xmd:flex-row justify-between xmd:items-end list-inside py-5 pl-0 pr-1 md:px-10 md:py-5 text-[.8rem] xmd:text-[.83rem] md:text-sm">
<View>
<Text className={`uppercase font-semibold mb-3 ${textColor}`}>Attendance Averages:</Text>
<Text className={`uppercase font-semibold mb-3 ${textColor}`}>{t('attendance.attendanceAverages')}</Text>
<View className="flex flex-col gap-y-2 list-disc font-medium pl-3">
<Text className={`${textColor} font-bold`}>Week: {selectedWeekAverage !== undefined ? selectedWeekAverage : 0.0}</Text>
<Text className={`${textColor} font-bold`}>{t('attendance.week')} {selectedWeekAverage !== undefined ? selectedWeekAverage : 0.0}</Text>

<Text className={`${textColor} font-bold`}>
{phaseName !== undefined ? phaseName : 'Phase Name'}: {phaseAverage !== undefined ? phaseAverage : 0.0}
{phaseName !== undefined ? phaseName : t('attendance.phaseName')}: {phaseAverage !== undefined ? phaseAverage : 0.0}
</Text>
<Text className={`${textColor} font-bold`}>All Phases Average: {allPhasesAverageData}</Text>
<Text className={`${textColor} font-bold`}>{t('attendance.allPhasesAverage')} {allPhasesAverageData}</Text>
</View>
</View>
</View>

<View className="p-3 gap-3">
<View className="flex-row gap-2">
<AntDesign name="checkcircle" size={21} color="green" />
<Text className={`${textColor}`}>= [2] Attended and Communicated</Text>
<Text className={`${textColor}`}>= {t('attendance.attendedAndCommunicated')}</Text>
</View>
<View className="flex-row gap-2">
<Image source={require('../../../assets/images/attend.png')} />
<Text className={`${textColor}`}>= [1] Didn't attend but communicated</Text>
<Text className={`${textColor}`}>= {t('attendance.didntAttendButCommunicated')}</Text>
</View>
<View className="flex-row gap-2">
<AntDesign name="closecircle" size={21} color="red" />
<Text className={`${textColor}`}>= [0] Didn't attend and didn't communicate</Text>
<Text className={`${textColor}`}>= {t('attendance.didntAttendAndCommunicate')}</Text>
</View>
</View>
</View>
Expand Down
189 changes: 189 additions & 0 deletions app/dashboard/trainee/LoginActivity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { GET_LOGIN_ACTIVITIES } from '@/graphql/queries/loginActivity';
import { useQuery } from '@apollo/client';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { t } from 'i18next';
import { useState, useEffect, Key } from 'react';
import { ScrollView, Text, TouchableOpacity, useColorScheme, View } from 'react-native';
import { useToast } from 'react-native-toast-notifications';
import SkeletonLoader from './LoginActivitySkeleton';
import { useTranslation } from 'react-i18next';

interface Profile {
__typename: string;
activity: any[];
}
interface Response {
getProfile: Profile;
}
interface LoginActivity {
date: string;
country_name: string;
city: string;
state: string;
IPv4: string;
latitude: number;
longitude: number;
country_code: string;
postal: string;
failed: number;
}

export default function LoginActivity() {
const [loginActivities, setLoginActivities] = useState<LoginActivity[]>([]);
const [userToken, setUserToken] = useState<string | null>(null);
const toast = useToast();
const [page, setPage] = useState(1);
const colorScheme = useColorScheme();
const { t } = useTranslation();

const textColor = colorScheme === 'dark' ? 'text-gray-100' : 'text-gray-800';
const bgColor = colorScheme === 'dark' ? 'bg-gray-500' : 'bg-gray-500';
const inputbg = colorScheme === 'dark' ? 'bg-primary-dark' : 'bg-primary-light';
// Fetch user token from AsyncStorage
useEffect(() => {
const fetchToken = async () => {
const token = await AsyncStorage.getItem('authToken');
if (token) {
setUserToken(token);
} else {
toast.show(t('sprintRating.user_token_not_found'), { type: 'danger' });
}
};
fetchToken();
}, []);
const { loading, data, error } = useQuery(GET_LOGIN_ACTIVITIES, {
context: {
headers: {
Authorization: `Bearer ${userToken}`,
},
},
skip: !userToken,
});

useEffect(() => {
if (error) {
setLoginActivities([]);
}
}, [error]);

let fetchedData = data?.getProfile.activity || [];
const handleLoadMore = () => {
setPage((prevPage) => prevPage + 1);
};

const handleGoBack = () => {
if (page > 1) {
setPage((prevPage) => prevPage - 1);
}
};
const pageSize = 12;
const totalActivities = fetchedData.length;
const totalPages = Math.ceil(totalActivities / pageSize);
const startIndex = (page - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, totalActivities);
const displayActivities = fetchedData.slice(startIndex, endIndex);

return (
<View>
<Text className={`font-bold text-3xl text-center ${textColor}`}>
{t('loginActivity.title')}
</Text>
{loading ? (
<SkeletonLoader />
) : (
<ScrollView horizontal={true} showsHorizontalScrollIndicator={true}>
<View className="pt-5 rounded-tl-sm rounded-tr-sm">
<View className="flex flex-row bg-[#5856D6] h-12 rounded-tl-md rounded-tr-md">
<Text className="w-52 border-r border-r-[#808080] p-2 text-white text-center font-bold">
{t('loginActivity.Date')}
</Text>
<Text className="w-32 p-2 border-r border-r-[#808080] text-white text-center font-bold">
{t('loginActivity.CountryName')}
</Text>
<Text className="w-24 border-r border-r-[#808080] p-2 text-white text-center font-bold">
{t('loginActivity.City')}
</Text>
<Text className="w-24 border-r border-r-[#808080] p-2 text-white text-center font-bold">
{t('loginActivity.State')}
</Text>
<Text className="w-36 border-r border-r-[#808080] p-2 text-white text-center font-bold">
{t('loginActivity.IPv4')}
</Text>
<Text className="w-28 p-2 text-white text-center font-bold">
{t('loginActivity.Attempt')}
</Text>
</View>
{displayActivities.length === 0 ? (
<Text className={`${textColor} text-center font-bold p-10`}>
{t('loginActivity.Nologinactivitiesyet')}
</Text>
) : (
displayActivities.map((item: any, index: Key | null | undefined) => (
<View key={index} className="flex flex-row border-b border-b-[#808080]">
<Text
className={`${textColor} w-52 border-r border-r-[#808080] border-l border-l-[#808080] p-2 text-center`}
>
{new Date(Number(item.date)).toLocaleString()}
</Text>
<Text
className={` ${textColor} w-32 border-r border-r-[#808080] p-2 text-center`}
>
{item.country_name === null ? 'N/A' : item.country_name}
</Text>
<Text
className={`${textColor} w-24 border-r border-r-[#808080] p-2 text-center`}
>
{item.city === null ? 'N/A' : item.city}
</Text>
<Text
className={`${textColor} w-24 border-r border-r-[#808080] p-2 text-center`}
>
{item.state === null ? 'N/A' : item.state}
</Text>
<Text
className={`${textColor} w-36 border-r border-r-[#808080] p-2 text-center`}
>
{item.IPv4 === null ? 'N/A' : item.IPv4}
</Text>
<Text
className={`${textColor} w-28 border-r border-r-[#808080] p-2 text-center`}
>
{item.failed === 0 ? t('loginActivity.Failed') : t('loginActivity.Success')}

</Text>
</View>
))
)}
</View>
</ScrollView>
)}

<View className="flex flex-row justify-center gap-5 items-center pt-2">
<Text className={`${textColor}`}>
{t('loginActivity.Page{page}of{totalPages}', { page, totalPages })}

</Text>
{page > 1 && (
<TouchableOpacity
onPress={() => handleGoBack()}
style={[{ padding: 8, borderRadius: 8, backgroundColor: '#6B7280' }]}
>
<Text style={{ color: '#FFFFFF' }}>{t('loginActivity.Previous')}
</Text>
</TouchableOpacity>
)}
{page < totalPages && (
<TouchableOpacity
onPress={() => handleLoadMore()}
style={{ padding: 8, backgroundColor: '#3B82F6', borderRadius: 8 }}
>
<Text style={{ marginLeft: 2, color: textColor, backgroundColor: '#3B82F6' }}>
{t('loginActivity.Next')}

</Text>
</TouchableOpacity>
)}
</View>
</View>
);
}
71 changes: 71 additions & 0 deletions app/dashboard/trainee/LoginActivitySkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useRef, useEffect } from 'react';
import { View, Text, StyleSheet, Animated } from 'react-native';

const SkeletonLoader = () => {
const shimmerAnimation = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(shimmerAnimation, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(shimmerAnimation, {
toValue: 0,
duration: 1000,
useNativeDriver: true,
}),
])
).start();
}, [shimmerAnimation]);

const shimmerStyle = {
opacity: shimmerAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0.3, 1],
}),
};

return (
<View style={styles.container}>
<View style={styles.row}>
<Animated.View style={[styles.skeletonBox, shimmerStyle]} />
<Animated.View style={[styles.skeletonBox, shimmerStyle]} />
<Animated.View style={[styles.skeletonBox, shimmerStyle]} />
</View>
<Animated.View style={[styles.skeletonLine, shimmerStyle]} />
<Animated.View style={[styles.skeletonLine, shimmerStyle]} />
<Animated.View style={[styles.skeletonLine, shimmerStyle]} />
</View>
);
};

const styles = StyleSheet.create({
container: {
flexDirection: 'column',
justifyContent: 'center',
padding: 10,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 10,
},
skeletonBox: {
width: 100,
height: 50,
backgroundColor: '#e0e0e0',
borderRadius: 4,
},
skeletonLine: {
height: 55,
backgroundColor: '#e0e0e0',
borderRadius: 4,
marginVertical: 4,
width: '100%',
},
});

export default SkeletonLoader;
Loading

0 comments on commit 3d57b3e

Please sign in to comment.