|
|
|
@ -1,75 +1,176 @@
|
|
|
|
|
import { TouchableRipple, TextInput, Card, Text, Appbar } from 'react-native-paper'; |
|
|
|
|
import { View, StyleSheet, ScrollView } from 'react-native'; |
|
|
|
|
import React, { useState } from 'react'; |
|
|
|
|
import { View, StyleSheet, ScrollView, RefreshControl, StatusBar, Image } from 'react-native'; |
|
|
|
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; |
|
|
|
|
import { colors } from '../../utils/color' |
|
|
|
|
import Icon from 'react-native-vector-icons/AntDesign'; |
|
|
|
|
import moment from 'moment'; |
|
|
|
|
import 'moment/locale/id'; |
|
|
|
|
import RequestModule from '../../services/api/request'; |
|
|
|
|
import renderSkeleton from '../../components/renderSkeleton' |
|
|
|
|
import { |
|
|
|
|
BottomSheetModal, |
|
|
|
|
BottomSheetModalProvider, |
|
|
|
|
BottomSheetView |
|
|
|
|
} from '@gorhom/bottom-sheet'; |
|
|
|
|
|
|
|
|
|
moment.locale('id'); |
|
|
|
|
const PAGE_SIZE = 25; |
|
|
|
|
export default function PresenceScreen({ route, navigation }) { |
|
|
|
|
const request = new RequestModule('presence'); |
|
|
|
|
const [refreshing, setRefreshing] = useState(false); |
|
|
|
|
const [page, setPage] = useState(0); |
|
|
|
|
const [dataPresence, setDataPresence] = useState([]); |
|
|
|
|
const [loading, setLoading] = useState(false); |
|
|
|
|
const bottomSheetImage = useRef(null); |
|
|
|
|
const [selectedImageUri, setSelectedImageUri] = useState(null); |
|
|
|
|
const handleOpenSheetImage = useCallback((uri) => { |
|
|
|
|
console.log('URI received:', uri); |
|
|
|
|
setSelectedImageUri(uri); |
|
|
|
|
bottomSheetImage.current?.present(); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
const onRefresh = useCallback(() => { |
|
|
|
|
setRefreshing(true); |
|
|
|
|
setPage(0); |
|
|
|
|
getPresenceHistory(0); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
getPresenceHistory(page); |
|
|
|
|
}, [page]); |
|
|
|
|
|
|
|
|
|
const getPresenceHistory = async (pageNum) => { |
|
|
|
|
if (loading) return; |
|
|
|
|
setLoading(true); |
|
|
|
|
const payload = { |
|
|
|
|
paging: { start: pageNum * PAGE_SIZE, length: PAGE_SIZE }, |
|
|
|
|
columns: [ |
|
|
|
|
{ name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' }, |
|
|
|
|
], |
|
|
|
|
orders: { columns: ['created_at', 'id'], ascending: false }, |
|
|
|
|
}; |
|
|
|
|
const result = await request.getDataSearch(payload); |
|
|
|
|
setDataPresence((prevData) => |
|
|
|
|
pageNum === 0 ? result.data.data : [...prevData, ...result.data.data] |
|
|
|
|
); |
|
|
|
|
setPage(pageNum + 1); |
|
|
|
|
setRefreshing(false); |
|
|
|
|
setLoading(false); |
|
|
|
|
}; |
|
|
|
|
// console.log("uri", dataPresence[0].attachments);
|
|
|
|
|
const convertDuration = (data) => { |
|
|
|
|
const hours = Math.floor(data / 60); |
|
|
|
|
const minutes = data % 60; |
|
|
|
|
return `${hours} jam ${minutes} menit`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const dummyData = [ |
|
|
|
|
{ id: '1', date: '2024-02-09', timeIn: '08:00', timeOut: '17:00', shift: "Pagi", duration: new Date(0, 0, 0, 8, 0, 0) }, |
|
|
|
|
{ id: '2', date: '2024-02-10', timeIn: '08:15', timeOut: '17:30', shift: "Pagi", duration: new Date(0, 0, 0, 8, 30, 0) }, |
|
|
|
|
{ id: '3', date: '2024-02-11', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 9, 0, 0) }, |
|
|
|
|
{ id: '4', date: '2024-02-12', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 10, 0, 0) }, |
|
|
|
|
{ id: '5', date: '2024-02-13', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 11, 0, 0) }, |
|
|
|
|
{ id: '6', date: '2024-02-14', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 9, 30, 0) }, |
|
|
|
|
{ id: '7', date: '2024-02-15', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 12, 0, 0) }, |
|
|
|
|
]; |
|
|
|
|
const handleScroll = ({ nativeEvent }) => { |
|
|
|
|
if (isCloseToBottom(nativeEvent)) { |
|
|
|
|
getPresenceHistory(page); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => { |
|
|
|
|
return layoutMeasurement.height + contentOffset.y >= contentSize.height - 20; |
|
|
|
|
}; |
|
|
|
|
const renderImage = useMemo(() => { |
|
|
|
|
console.log('Rendering image with URI:', selectedImageUri); |
|
|
|
|
return ( |
|
|
|
|
<Image |
|
|
|
|
source={{ uri: selectedImageUri }} |
|
|
|
|
style={styles.imageDetail} |
|
|
|
|
resizeMode="cover" |
|
|
|
|
/> |
|
|
|
|
); |
|
|
|
|
}, [selectedImageUri]); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<StatusBar backgroundColor={colors.blue} barStyle='ligth-content' translucent={true} /> |
|
|
|
|
<Appbar.Header mode='center-aligned' style={{ backgroundColor: colors.blue }}> |
|
|
|
|
<Appbar.BackAction color={colors.pureWhite} onPress={() => { navigation.goBack() }} /> |
|
|
|
|
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={colors.pureWhite} title='Riwayat kehadiran' /> |
|
|
|
|
</Appbar.Header> |
|
|
|
|
|
|
|
|
|
<ScrollView style={{ flex: 1 }}> |
|
|
|
|
{dummyData.map((item) => ( |
|
|
|
|
<Card key={item.id} style={styles.cardPrecense}> |
|
|
|
|
<Card.Content> |
|
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
|
<View style={[styles.row, styles.precenseDate]}> |
|
|
|
|
<Icon name="calendar" size={20} color={colors.blue} /> |
|
|
|
|
<Text style={{ color: colors.blue, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
|
{moment(item.date).format('dddd, DD MMMM - YYYY')} |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
<View style={[styles.precenseDate, { backgroundColor: colors.semigreen }]}> |
|
|
|
|
<Text style={{ color: colors.green, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
|
Shift {item.shift} |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
<ScrollView |
|
|
|
|
style={{ flex: 1, marginTop: 5 }} |
|
|
|
|
refreshControl={ |
|
|
|
|
<RefreshControl |
|
|
|
|
refreshing={refreshing} |
|
|
|
|
onRefresh={onRefresh} |
|
|
|
|
colors={[colors.blue]} |
|
|
|
|
progressBackgroundColor={colors.pureWhite} |
|
|
|
|
/> |
|
|
|
|
} |
|
|
|
|
onScroll={handleScroll} |
|
|
|
|
scrollEventThrottle={400} |
|
|
|
|
> |
|
|
|
|
{loading ? ( |
|
|
|
|
// renderSkeleton(10)
|
|
|
|
|
null |
|
|
|
|
) : ( |
|
|
|
|
dataPresence.map((item) => ( |
|
|
|
|
<TouchableRipple onPress={() => item.attachments && item.attachments[0] && handleOpenSheetImage(item.attachments[0].url)} key={item.id}> |
|
|
|
|
<Card key={item.id} style={styles.cardPrecense}> |
|
|
|
|
<Card.Content> |
|
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
|
<View style={[styles.row, styles.precenseDate]}> |
|
|
|
|
<Icon name="calendar" size={20} color={colors.blue} /> |
|
|
|
|
|
|
|
|
|
<Text style={{ color: colors.blue, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
|
{moment(item.datePresence).format('dddd, DD MMMM - YYYY')} |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
<View style={[styles.precenseDate, { backgroundColor: colors.semigreen }]}> |
|
|
|
|
<Text style={{ color: colors.green, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
|
Shift {item.shift} |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
|
|
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Icon name="clockcircleo" size={20} color={colors.black} /> |
|
|
|
|
<Text style={{ color: colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
|
Durasi Kerja |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Text style={{ color: colors.amethystSmoke }}>Masuk</Text> |
|
|
|
|
<Icon name="minus" size={20} color={colors.amethystSmoke} /> |
|
|
|
|
<Text style={{ color: colors.amethystSmoke }}>Keluar</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Text style={{ color: colors.black, paddingLeft: 5 }}>{item.timeIn} Jam</Text> |
|
|
|
|
</View> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Text>{item.timeIn}</Text> |
|
|
|
|
<Icon name="minus" size={20} color={colors.black} /> |
|
|
|
|
<Text>{item.timeOut}</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
</Card.Content> |
|
|
|
|
</Card> |
|
|
|
|
))} |
|
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Icon name="clockcircleo" size={20} color={colors.black} /> |
|
|
|
|
<Text style={{ color: colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
|
Durasi Kerja |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Text style={{ color: colors.amethystSmoke }}>Masuk</Text> |
|
|
|
|
<Icon name="minus" size={20} color={colors.amethystSmoke} /> |
|
|
|
|
<Text style={{ color: colors.amethystSmoke }}>Keluar</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Text style={{ color: colors.black, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text> |
|
|
|
|
</View> |
|
|
|
|
<View style={styles.row}> |
|
|
|
|
<Text>{moment(item.in_time).format('HH:mm')}</Text> |
|
|
|
|
<Icon name="minus" size={20} color={colors.black} /> |
|
|
|
|
<Text>{item.out_time ? moment(item.out_time).format('HH:mm') : ''}</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
<Text>{item.attachments && item.attachments.length > 0 ? item.attachments[0].url : 'No attachments'}</Text> |
|
|
|
|
</Card.Content> |
|
|
|
|
|
|
|
|
|
</Card> |
|
|
|
|
</TouchableRipple> |
|
|
|
|
)) |
|
|
|
|
)} |
|
|
|
|
</ScrollView> |
|
|
|
|
|
|
|
|
|
<BottomSheetModalProvider> |
|
|
|
|
<BottomSheetModal |
|
|
|
|
ref={bottomSheetImage} |
|
|
|
|
index={0} |
|
|
|
|
snapPoints={['50%']} |
|
|
|
|
> |
|
|
|
|
<BottomSheetView style={{ alignItems: 'center' }} > |
|
|
|
|
{renderImage} |
|
|
|
|
</BottomSheetView> |
|
|
|
|
</BottomSheetModal> |
|
|
|
|
</BottomSheetModalProvider> |
|
|
|
|
</> |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|