|
|
@ -1,32 +1,56 @@ |
|
|
|
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'; |
|
|
|
import React, { useEffect, useCallback, useState, useRef } from 'react'; |
|
|
|
import { Card, Text, Avatar, useTheme, IconButton, FAB, Button, Tooltip, TouchableRipple, Badge } from 'react-native-paper'; |
|
|
|
import { |
|
|
|
import { RefreshControl, StyleSheet, View, Alert, PermissionsAndroid, Platform, ToastAndroid, ScrollView, StatusBar } from 'react-native'; |
|
|
|
View, |
|
|
|
|
|
|
|
StyleSheet, |
|
|
|
|
|
|
|
ScrollView, |
|
|
|
|
|
|
|
StatusBar, |
|
|
|
|
|
|
|
PermissionsAndroid, |
|
|
|
|
|
|
|
RefreshControl |
|
|
|
|
|
|
|
} from 'react-native'; |
|
|
|
|
|
|
|
import { Card, Avatar, IconButton, Button, Text, TouchableRipple, ActivityIndicator } from 'react-native-paper'; |
|
|
|
import { launchCamera } from 'react-native-image-picker'; |
|
|
|
import { launchCamera } from 'react-native-image-picker'; |
|
|
|
import { useFocusEffect } from '@react-navigation/native'; |
|
|
|
import { useFocusEffect } from '@react-navigation/native'; |
|
|
|
import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; |
|
|
|
import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; |
|
|
|
import { useDispatch, useSelector } from 'react-redux'; |
|
|
|
import { useSelector } from 'react-redux'; |
|
|
|
import { setShipmentData } from '../appredux/actions'; |
|
|
|
|
|
|
|
import { store } from '../appredux/store'; |
|
|
|
|
|
|
|
import RNFS from 'react-native-fs'; |
|
|
|
import RNFS from 'react-native-fs'; |
|
|
|
import { getCoords } from '../utils/geolocation'; |
|
|
|
|
|
|
|
import { strings } from '../utils/i18n'; |
|
|
|
|
|
|
|
import moment from 'moment'; |
|
|
|
import moment from 'moment'; |
|
|
|
import 'moment/locale/id'; |
|
|
|
import 'moment/locale/id'; |
|
|
|
moment.locale('id'); |
|
|
|
import uuid from 'react-native-uuid'; |
|
|
|
import person from '../assets/images/courier_man.png' |
|
|
|
|
|
|
|
import { |
|
|
|
import { |
|
|
|
BottomSheetModal, |
|
|
|
BottomSheetModal, |
|
|
|
BottomSheetModalProvider, |
|
|
|
BottomSheetModalProvider, |
|
|
|
BottomSheetView |
|
|
|
BottomSheetView, |
|
|
|
} from '@gorhom/bottom-sheet'; |
|
|
|
} from '@gorhom/bottom-sheet'; |
|
|
|
import Icon from 'react-native-vector-icons/AntDesign'; |
|
|
|
import Icon from 'react-native-vector-icons/AntDesign'; |
|
|
|
import { requestAccessStoragePermission } from '../utils/storage'; |
|
|
|
import Toast from 'react-native-toast-message'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import person from '../assets/images/courier_man.png'; |
|
|
|
import { colors } from '../utils/color'; |
|
|
|
import { colors } from '../utils/color'; |
|
|
|
|
|
|
|
import { getCoords } from '../utils/geolocation'; |
|
|
|
|
|
|
|
import { strings } from '../utils/i18n'; |
|
|
|
|
|
|
|
import { PATH_PRESENCE } from '../config/imageFolder'; |
|
|
|
|
|
|
|
import { storeData, resendFailedAttachments } from '../services/sqlite/attachment'; |
|
|
|
|
|
|
|
import RequestModule from '../services/api/request'; |
|
|
|
|
|
|
|
import { requestAccessStoragePermission } from '../utils/storage'; |
|
|
|
|
|
|
|
// import renderSkeleton from '../components/renderSkeleton';
|
|
|
|
|
|
|
|
moment.locale('id'); |
|
|
|
|
|
|
|
|
|
|
|
const HomeScreen = ({ route, navigation }) => { |
|
|
|
const HomeScreen = ({ route, navigation }) => { |
|
|
|
const theme = useTheme(); |
|
|
|
const request = new RequestModule('presence') |
|
|
|
|
|
|
|
const { user } = useSelector(state => state.userReducer) |
|
|
|
const [currentTime, setCurrentTime] = useState(moment(new Date())); |
|
|
|
const [currentTime, setCurrentTime] = useState(moment(new Date())); |
|
|
|
|
|
|
|
const [existingAttachmentNumber, setExistingAttachmentNumber] = useState(''); |
|
|
|
|
|
|
|
const [imageData, setImageData] = useState([]); |
|
|
|
|
|
|
|
const [idData, setIdData] = useState(null); |
|
|
|
|
|
|
|
const [dataPresence, setDataPresence] = useState([]) |
|
|
|
|
|
|
|
const [lastPresence, setLastPresence] = useState(null) |
|
|
|
const bottomSheetModal = useRef(null); |
|
|
|
const bottomSheetModal = useRef(null); |
|
|
|
|
|
|
|
const [refreshing, setRefreshing] = useState(false); |
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false); |
|
|
|
|
|
|
|
const [loadingSkeleton, setLoadingSkeleton] = useState(false); |
|
|
|
|
|
|
|
const presenceText = lastPresence && lastPresence.length > 0 ? 'presenceOut' : 'presenceIn'; |
|
|
|
|
|
|
|
const messageText = lastPresence && lastPresence.length > 0 ? 'presenceOutMessage' : 'presenceInMessage'; |
|
|
|
|
|
|
|
|
|
|
|
useFocusEffect( |
|
|
|
useFocusEffect( |
|
|
|
useCallback(() => { |
|
|
|
useCallback(() => { |
|
|
|
let homeTimer = setInterval(() => { |
|
|
|
let homeTimer = setInterval(() => { |
|
|
@ -35,25 +59,189 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
return () => { |
|
|
|
return () => { |
|
|
|
clearInterval(homeTimer); |
|
|
|
clearInterval(homeTimer); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}, []) |
|
|
|
}, []) |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
const onRefresh = React.useCallback(() => { |
|
|
|
|
|
|
|
setRefreshing(true); |
|
|
|
|
|
|
|
setLoadingSkeleton(true); |
|
|
|
|
|
|
|
getPresenceHistory(); |
|
|
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const dummyData = [ |
|
|
|
useEffect(() => { |
|
|
|
{ id: '1', date: '2024-02-09', timeIn: '08:00', timeOut: '17:00', shift: "Pagi", duration: new Date(0, 0, 0, 8, 0, 0) }, |
|
|
|
if (existingAttachmentNumber === '') { |
|
|
|
{ id: '2', date: '2024-02-10', timeIn: '08:15', timeOut: '17:30', shift: "Pagi", duration: new Date(0, 0, 0, 8, 30, 0) }, |
|
|
|
setExistingAttachmentNumber(uuid.v4()) |
|
|
|
{ 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) }, |
|
|
|
getPresenceHistory() |
|
|
|
{ id: '5', date: '2024-02-13', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 11, 0, 0) }, |
|
|
|
getLastPresence() |
|
|
|
{ id: '6', date: '2024-02-14', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 9, 30, 0) }, |
|
|
|
resendFailedAttachments(); |
|
|
|
{ id: '7', date: '2024-02-15', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 12, 0, 0) }, |
|
|
|
}, []) |
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getLastPresence = async () => { |
|
|
|
|
|
|
|
const payload = { |
|
|
|
|
|
|
|
paging: { start: 0, length: 1 }, |
|
|
|
|
|
|
|
columns: [ |
|
|
|
|
|
|
|
{ name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' }, |
|
|
|
|
|
|
|
{ name: 'date_presence', logic_operator: '=', operator: 'AND', value: moment().format('YYYY-MM-DD') }, |
|
|
|
|
|
|
|
{ name: 'hr_to_project_id', logic_operator: '=', operator: 'AND', value: "1" } |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
orders: { columns: ['created_at', 'id'], ascending: false } |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
const result = await request.getDataSearch(payload); |
|
|
|
|
|
|
|
if (result && result.status === 200) { |
|
|
|
|
|
|
|
setLastPresence(result.data.data) |
|
|
|
|
|
|
|
setIdData(result.data.data[0]?.id) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const getPresenceHistory = async () => { |
|
|
|
|
|
|
|
if (loadingSkeleton) return; |
|
|
|
|
|
|
|
setLoadingSkeleton(true); |
|
|
|
|
|
|
|
const payload = { |
|
|
|
|
|
|
|
paging: { start: 0, length: 7 }, |
|
|
|
|
|
|
|
columns: [ |
|
|
|
|
|
|
|
{ name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' }, |
|
|
|
|
|
|
|
{ name: 'hr_to_project_id', logic_operator: '=', operator: 'AND', value: "1" } |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
orders: { columns: ['created_at', 'id'], ascending: false } |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
const result = await request.getDataSearch(payload); |
|
|
|
|
|
|
|
if (result && result.status === 200) { |
|
|
|
|
|
|
|
setDataPresence(result.data.data) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setRefreshing(false); |
|
|
|
|
|
|
|
setLoadingSkeleton(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const handlePresensiPress = useCallback(() => { |
|
|
|
const handlePresensiPress = useCallback(() => { |
|
|
|
bottomSheetModal.current?.present(); |
|
|
|
bottomSheetModal.current?.present(); |
|
|
|
}, []); |
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleSendPresenceData = async () => { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const currentDateTime = moment(new Date()); |
|
|
|
|
|
|
|
getCoords(async ({ lat, lon, timestamp }) => { |
|
|
|
|
|
|
|
if (lat === null || lon === null) { |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.errorFetchingLocation'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const payload = { |
|
|
|
|
|
|
|
assign_hr_id: 1, |
|
|
|
|
|
|
|
datePresence: currentDateTime, |
|
|
|
|
|
|
|
in_time: currentDateTime, |
|
|
|
|
|
|
|
in_lat: lat, |
|
|
|
|
|
|
|
in_lon: lon, |
|
|
|
|
|
|
|
shift: 'Pagi', |
|
|
|
|
|
|
|
status: 'Present', |
|
|
|
|
|
|
|
attachment_number: existingAttachmentNumber, |
|
|
|
|
|
|
|
send_time: currentDateTime |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const result = await request.addData(payload); |
|
|
|
|
|
|
|
if (result.status === 201) { |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'success', |
|
|
|
|
|
|
|
text1: strings('presence.dataSentSuccessfully'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
getLastPresence() |
|
|
|
|
|
|
|
getPresenceHistory() |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.failedSendDataPresence'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error("Network error sending presence data:", error); |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.errorMessage'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error("Error sending presence data:", error); |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.errorMessage'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handlePresensiConfirm = async () => { |
|
|
|
const handlePresensiConfirm = async () => { |
|
|
|
|
|
|
|
if (lastPresence && lastPresence.length > 0) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const currentDateTime = moment(new Date()); |
|
|
|
|
|
|
|
getCoords(async ({ lat, lon, timestamp }) => { |
|
|
|
|
|
|
|
if (lat === null || lon === null) { |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.errorFetchingLocation'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const inTime = moment(lastPresence[0].in_time); |
|
|
|
|
|
|
|
const outTime = moment(currentDateTime); |
|
|
|
|
|
|
|
const duration = outTime.diff(inTime, 'minutes'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const payloadEdit = { |
|
|
|
|
|
|
|
assign_hr_id: 1, |
|
|
|
|
|
|
|
out_time: currentDateTime, |
|
|
|
|
|
|
|
duration: duration, |
|
|
|
|
|
|
|
out_lat: lat, |
|
|
|
|
|
|
|
out_lon: lon, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const result = await request.updateData(idData, payloadEdit); |
|
|
|
|
|
|
|
console.log("result", result); |
|
|
|
|
|
|
|
if (result.status === 200) { |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'success', |
|
|
|
|
|
|
|
text1: strings('presence.dataSentOutSuccessfully'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
getLastPresence() |
|
|
|
|
|
|
|
getPresenceHistory() |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
bottomSheetModal.current?.dismiss(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.failedSendDataPresence'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error("Network error sending presence data:", error); |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.errorMessage'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error("Error sending presence data:", error); |
|
|
|
|
|
|
|
Toast.show({ |
|
|
|
|
|
|
|
type: 'error', |
|
|
|
|
|
|
|
text1: strings('presence.errorMessage'), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
try { |
|
|
|
try { |
|
|
|
const granted = await PermissionsAndroid.request( |
|
|
|
const granted = await PermissionsAndroid.request( |
|
|
|
PermissionsAndroid.PERMISSIONS.CAMERA, |
|
|
|
PermissionsAndroid.PERMISSIONS.CAMERA, |
|
|
@ -66,14 +254,31 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
}, |
|
|
|
}, |
|
|
|
); |
|
|
|
); |
|
|
|
if (granted === PermissionsAndroid.RESULTS.GRANTED) { |
|
|
|
if (granted === PermissionsAndroid.RESULTS.GRANTED) { |
|
|
|
handleLaunchCamera() |
|
|
|
handleLaunchCamera(); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
|
|
|
|
console.warn('Camera permission denied'); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (err) { |
|
|
|
} catch (err) { |
|
|
|
console.warn(err); |
|
|
|
console.warn(err); |
|
|
|
} |
|
|
|
} |
|
|
|
bottomSheetModal.current?.dismiss(); |
|
|
|
bottomSheetModal.current?.dismiss(); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleUploadImage = async (dataImage, imageCategory) => { |
|
|
|
|
|
|
|
const formData = new FormData(); |
|
|
|
|
|
|
|
for (const { attachment_number, imageFile, type, name, data, description } of dataImage) { |
|
|
|
|
|
|
|
formData.append('attachment_number', attachment_number); |
|
|
|
|
|
|
|
formData.append('description', description); |
|
|
|
|
|
|
|
formData.append('files', { |
|
|
|
|
|
|
|
uri: imageFile, |
|
|
|
|
|
|
|
type: type, |
|
|
|
|
|
|
|
name: name |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const result = await request.uploadImage(imageCategory, formData); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const handleLaunchCamera = () => { |
|
|
|
const handleLaunchCamera = () => { |
|
|
|
let options = { |
|
|
|
let options = { |
|
|
@ -81,76 +286,31 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
skipBackup: true, |
|
|
|
skipBackup: true, |
|
|
|
path: 'images', |
|
|
|
path: 'images', |
|
|
|
}, |
|
|
|
}, |
|
|
|
cameraType: 'front', |
|
|
|
cameraType: 'back', |
|
|
|
maxWidth: 768, |
|
|
|
maxWidth: 768, |
|
|
|
maxHeight: 1024, |
|
|
|
maxHeight: 1024, |
|
|
|
saveToPhotos: false, |
|
|
|
saveToPhotos: false, |
|
|
|
includeBase64: true |
|
|
|
includeBase64: true |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
launchCamera(options, (response) => { |
|
|
|
launchCamera(options, async (response) => { |
|
|
|
if (response.didCancel) { |
|
|
|
if (response.didCancel) { |
|
|
|
// console.log('User cancelled image picker');
|
|
|
|
console.log('User cancelled image picker'); |
|
|
|
} else if (response.error) { |
|
|
|
} else if (response.error) { |
|
|
|
// console.log('ImagePicker Error: ', response.error);
|
|
|
|
console.log('ImagePicker Error: ', response.error); |
|
|
|
} else if (response.customButton) { |
|
|
|
|
|
|
|
// console.log('User tapped custom button: ', response.customButton);
|
|
|
|
|
|
|
|
// alert(response.customButton);
|
|
|
|
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// console.log('response', response);
|
|
|
|
|
|
|
|
// response:
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
// "assets": [
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
// "base64": "/9j/4QBqRXhpZgAATU0AKgAAAAgABAEAAAQAAAABAAAEAAEBAAQ......"
|
|
|
|
|
|
|
|
// "fileName": "rn_image_picker_lib_temp_f5afd4ec-a31b-4854-8dc7-5c5dd750e3c6.jpg",
|
|
|
|
|
|
|
|
// "fileSize": 595470,
|
|
|
|
|
|
|
|
// "height": 1024,
|
|
|
|
|
|
|
|
// "originalPath": "file:///data/user/0/com.integrasiautama.ospodduta/cache/rn_image_picker_lib_temp_f5afd4ec-a31b-4854-8dc7-5c5dd750e3c6.jpg",
|
|
|
|
|
|
|
|
// "type": "image/jpeg",
|
|
|
|
|
|
|
|
// "uri": "file:///data/user/0/com.integrasiautama.ospodduta/cache/rn_image_picker_lib_temp_10e0c170-69ce-4a1d-8520-82f75945c125.jpg",
|
|
|
|
|
|
|
|
// "width": 768
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// ]
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (response.assets && response.assets.length > 0) { |
|
|
|
if (response.assets && response.assets.length > 0) { |
|
|
|
let user_id = user?.id; |
|
|
|
setLoading(true) |
|
|
|
getCoords(loc => { |
|
|
|
|
|
|
|
// console.log('loc', loc)
|
|
|
|
|
|
|
|
if (loc) { |
|
|
|
|
|
|
|
let imageObject = { |
|
|
|
|
|
|
|
"user_id": user_id, |
|
|
|
|
|
|
|
"drop_point_id": selectedDropPoint?.id, |
|
|
|
|
|
|
|
"image_uri": response.assets[0].uri, |
|
|
|
|
|
|
|
"image_blob": response.assets[0].base64, |
|
|
|
|
|
|
|
"lat": loc.lat, |
|
|
|
|
|
|
|
"lon": loc.lon |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
addOverlay(imageObject) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const addOverlay = async (imageObject) => { |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
|
|
|
|
getCoords(async (loc) => { |
|
|
|
|
|
|
|
if (loc) { |
|
|
|
const timestamp = new Date().toLocaleString(); |
|
|
|
const timestamp = new Date().toLocaleString(); |
|
|
|
const location = { latitude: imageObject.lat, longitude: imageObject.lon }; |
|
|
|
const location = { latitude: loc.lat, longitude: loc.lon }; |
|
|
|
|
|
|
|
|
|
|
|
const overlayText = `${timestamp}\nLatitude: ${location.latitude}\nLongitude: ${location.longitude}`; |
|
|
|
const overlayText = `${timestamp}\nLatitude: ${location.latitude}\nLongitude: ${location.longitude}\nCopyright Nawakara`; |
|
|
|
|
|
|
|
|
|
|
|
if (imageObject.image_uri) { |
|
|
|
|
|
|
|
const markedImage = await ImageMarker.markText({ |
|
|
|
const markedImage = await ImageMarker.markText({ |
|
|
|
backgroundImage: { |
|
|
|
backgroundImage: { |
|
|
|
src: { uri: imageObject.image_uri } |
|
|
|
src: { uri: response.assets[0].uri } |
|
|
|
}, |
|
|
|
}, |
|
|
|
watermarkTexts: [{ |
|
|
|
watermarkTexts: [{ |
|
|
|
text: overlayText, |
|
|
|
text: overlayText, |
|
|
@ -159,10 +319,10 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
}, |
|
|
|
}, |
|
|
|
style: { |
|
|
|
style: { |
|
|
|
color: '#ffffff', |
|
|
|
color: '#ffffff', |
|
|
|
fontSize: 8, |
|
|
|
fontSize: 9, |
|
|
|
fontName: 'Arial', |
|
|
|
fontName: 'Arial', |
|
|
|
textBackgroundStyle: { |
|
|
|
textBackgroundStyle: { |
|
|
|
padding: 10, |
|
|
|
padding: 12, |
|
|
|
type: TextBackgroundType.none, |
|
|
|
type: TextBackgroundType.none, |
|
|
|
color: '#00000080' |
|
|
|
color: '#00000080' |
|
|
|
} |
|
|
|
} |
|
|
@ -174,46 +334,58 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
fontSize: 16, |
|
|
|
fontSize: 16, |
|
|
|
scale: 1, |
|
|
|
scale: 1, |
|
|
|
}); |
|
|
|
}); |
|
|
|
await saveToTemporaryFolder(markedImage, imageObject); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// const tempPath = `file://${RNFS.TemporaryDirectoryPath}/prsensi/${moment().format('YYYYMMDDHHmmss')}.jpg`;
|
|
|
|
|
|
|
|
// await RNFS.copyFile(markedImage, tempPath);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const newImageData = { |
|
|
|
|
|
|
|
id: 0, |
|
|
|
|
|
|
|
attachment_number: existingAttachmentNumber, |
|
|
|
|
|
|
|
imageFile: `file://${markedImage}`, |
|
|
|
|
|
|
|
description: "-", |
|
|
|
|
|
|
|
data: response.assets[0].uri, |
|
|
|
|
|
|
|
type: response.assets[0].type, |
|
|
|
|
|
|
|
name: response.assets[0].fileName |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resultImage = await handleUploadImage([newImageData], PATH_PRESENCE); |
|
|
|
|
|
|
|
handleSendPresenceData(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
console.error('Error adding overlay:', error); |
|
|
|
console.error('Error adding overlay:', error); |
|
|
|
|
|
|
|
setLoading(false) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const saveToTemporaryFolder = async (markedImage, imageObject) => { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const tempPath = `file://${RNFS.TemporaryDirectoryPath}/presensi/${moment().format('YYYYMMDDHHmmss')}.jpg`; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!requestAccessStoragePermission()) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
await RNFS.copyFile(markedImage, tempPath); |
|
|
|
|
|
|
|
const imageBase64 = await RNFS.readFile(tempPath, 'base64'); |
|
|
|
|
|
|
|
storePhoto(imageObject.user_id, imageObject.drop_point_id, tempPath, imageBase64, imageObject.lat, imageObject.lon) |
|
|
|
|
|
|
|
reloadPhotos(); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error('Error saving image to temporary folder:', error); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handlePresensiCancel = () => { |
|
|
|
const handlePresensiCancel = () => { |
|
|
|
bottomSheetModal.current?.dismiss(); |
|
|
|
bottomSheetModal.current?.dismiss(); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const convertDuration = (data) => { |
|
|
|
|
|
|
|
const hours = Math.floor(data / 60); |
|
|
|
|
|
|
|
const minutes = data % 60; |
|
|
|
|
|
|
|
return `${hours} jam ${minutes} menit`; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
|
|
|
|
|
|
|
|
<View style={styles.container}> |
|
|
|
<View style={styles.container}> |
|
|
|
<StatusBar backgroundColor={colors.blue} barStyle='ligth-content' translucent={true} /> |
|
|
|
<StatusBar backgroundColor={colors.blue} barStyle='ligth-content' translucent={true} /> |
|
|
|
<View style={[styles.header, { backgroundColor: colors.blue }]}> |
|
|
|
<View style={[styles.header, { backgroundColor: colors.blue }]}> |
|
|
|
<Avatar.Image style={styles.avatar} source={person} /> |
|
|
|
<Avatar.Image style={styles.avatar} source={{ uri: 'https://nawakara-staging-api.ospro.id/assets/files/presence/2024-06/OQ8oglNiCIoCrPX1718098448814614133-0.jpg' }} /> |
|
|
|
<View style={styles.textContainer}> |
|
|
|
<View style={styles.textContainer}> |
|
|
|
<Text style={[styles.welcomeText, { color: theme.colors.surface }]}> |
|
|
|
<Text style={[styles.welcomeText, { color: colors.pureWhite }]}> |
|
|
|
{strings('home.welcomeMessage')}, Farhan! |
|
|
|
{strings('home.welcomeMessage')}, {user?.name} |
|
|
|
</Text> |
|
|
|
</Text> |
|
|
|
<Text style={[styles.subWelcomeText, { color: theme.colors.surface }]}> |
|
|
|
<Text style={[styles.subWelcomeText, { color: colors.pureWhite }]}> |
|
|
|
penuh semangat dan produktivitas |
|
|
|
penuh semangat dan produktivitas |
|
|
|
</Text> |
|
|
|
</Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
@ -234,8 +406,8 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
<Text variant='titleMedium' style={[styles.boldText, { textAlign: 'center', color: colors.amethystSmoke }]}>Keluar </Text> |
|
|
|
<Text variant='titleMedium' style={[styles.boldText, { textAlign: 'center', color: colors.amethystSmoke }]}>Keluar </Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<Text variant='bodyMedium' style={{ textAlign: 'center', color: colors.blue }}>Jam </Text> |
|
|
|
<Text variant='bodyMedium' style={{ textAlign: 'center', color: colors.blue }}>{lastPresence && lastPresence.length > 0 ? moment(lastPresence[0].in_time).format('HH:mm') : '-'}</Text> |
|
|
|
<Text variant='bodyMedium' style={{ textAlign: 'center', color: colors.blue }}>Jam </Text> |
|
|
|
<Text variant='bodyMedium' style={{ textAlign: 'center', color: colors.blue }}>{lastPresence && lastPresence.length > 0 && lastPresence[0].out_time ? moment(lastPresence[0].out_time).format('HH:mm') : '-'}</Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<View style={styles.row}> |
|
|
|
<View style={styles.row}> |
|
|
|
<View style={styles.iconButtonContainer}> |
|
|
|
<View style={styles.iconButtonContainer}> |
|
|
@ -264,15 +436,28 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
<Text style={{ color: colors.amethystSmoke }}>Lihat Semua</Text> |
|
|
|
<Text style={{ color: colors.amethystSmoke }}>Lihat Semua</Text> |
|
|
|
</TouchableRipple> |
|
|
|
</TouchableRipple> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<ScrollView style={{ flex: 1 }}> |
|
|
|
<ScrollView style={{ flex: 1 }} |
|
|
|
{dummyData.map((item) => ( |
|
|
|
refreshControl={ |
|
|
|
|
|
|
|
<RefreshControl |
|
|
|
|
|
|
|
refreshing={refreshing} |
|
|
|
|
|
|
|
onRefresh={onRefresh} |
|
|
|
|
|
|
|
colors={[colors.blue]} |
|
|
|
|
|
|
|
progressBackgroundColor={colors.pureWhite} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{loadingSkeleton ? ( |
|
|
|
|
|
|
|
// renderSkeleton(2)
|
|
|
|
|
|
|
|
null |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
dataPresence.map((item) => ( |
|
|
|
<Card key={item.id} style={styles.cardPrecense}> |
|
|
|
<Card key={item.id} style={styles.cardPrecense}> |
|
|
|
<Card.Content> |
|
|
|
<Card.Content> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<View style={[styles.row, styles.precenseDate]}> |
|
|
|
<View style={[styles.row, styles.precenseDate]}> |
|
|
|
<Icon name="calendar" size={20} color={colors.blue} /> |
|
|
|
<Icon name="calendar" size={20} color={colors.blue} /> |
|
|
|
<Text style={{ color: colors.blue, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
<Text style={{ color: colors.blue, fontWeight: 'bold', paddingLeft: 3 }}> |
|
|
|
{moment(item.date).format('dddd, DD MMMM - YYYY')} |
|
|
|
{moment(item.datePresence).format('dddd, DD MMMM - YYYY')} |
|
|
|
</Text> |
|
|
|
</Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<View style={[styles.precenseDate, { backgroundColor: colors.semigreen }]}> |
|
|
|
<View style={[styles.precenseDate, { backgroundColor: colors.semigreen }]}> |
|
|
@ -297,43 +482,49 @@ const HomeScreen = ({ route, navigation }) => { |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<View style={styles.row}> |
|
|
|
<View style={styles.row}> |
|
|
|
<Text style={{ color: colors.black, paddingLeft: 5 }}>{item.timeIn} Jam</Text> |
|
|
|
<Text style={{ color: colors.black, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<View style={styles.row}> |
|
|
|
<View style={styles.row}> |
|
|
|
<Text>{item.timeIn}</Text> |
|
|
|
<Text>{moment(item.in_time).format('HH:mm')}</Text> |
|
|
|
<Icon name="minus" size={20} color={colors.black} /> |
|
|
|
<Icon name="minus" size={20} color={colors.black} /> |
|
|
|
<Text>{item.timeOut}</Text> |
|
|
|
<Text>{item.out_time ? moment(item.out_time).format('HH:mm') : ''}</Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
</Card.Content> |
|
|
|
</Card.Content> |
|
|
|
</Card> |
|
|
|
</Card> |
|
|
|
))} |
|
|
|
)) |
|
|
|
|
|
|
|
)} |
|
|
|
</ScrollView> |
|
|
|
</ScrollView> |
|
|
|
|
|
|
|
|
|
|
|
<BottomSheetModalProvider> |
|
|
|
<BottomSheetModalProvider> |
|
|
|
<BottomSheetModal |
|
|
|
<BottomSheetModal |
|
|
|
ref={bottomSheetModal} |
|
|
|
ref={bottomSheetModal} |
|
|
|
index={0} |
|
|
|
index={0} |
|
|
|
snapPoints={['25%']} |
|
|
|
snapPoints={['27%']} |
|
|
|
bottomInset={10} |
|
|
|
|
|
|
|
detached={true} |
|
|
|
detached={true} |
|
|
|
> |
|
|
|
> |
|
|
|
<BottomSheetView > |
|
|
|
<BottomSheetView > |
|
|
|
<View style={styles.cardContent}> |
|
|
|
<View style={styles.cardContent}> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<View style={[styles.row, { justifyContent: 'space-between' }]}> |
|
|
|
<Text variant='titleMedium' style={[styles.boldText, { textAlign: 'center', color: colors.black }]}>Ingin presensi sekarang? </Text> |
|
|
|
<Text variant='titleMedium' style={[styles.boldText, { textAlign: 'center', color: colors.black }]}>{strings(`presence.${presenceText}`)} </Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
<Text variant='bodyMedium' style={{ textAlign: 'center', padding: 3, color: colors.amethystSmoke }}>Semoga harimu menyenangkan dengan penuh produktivitas dan semangat </Text> |
|
|
|
<Text variant='bodyMedium' style={{ textAlign: 'center', padding: 3, color: colors.amethystSmoke }}>{strings(`presence.${messageText}`)}</Text> |
|
|
|
<Button mode="contained" buttonColor={colors.blue} onPress={handlePresensiConfirm} style={{ borderRadius: 15 }}> |
|
|
|
<Button mode="contained" buttonColor={colors.blue} onPress={handlePresensiConfirm} style={{ borderRadius: 15 }}> |
|
|
|
Presensi Sekarang |
|
|
|
{strings('global.confirm')} |
|
|
|
</Button> |
|
|
|
</Button> |
|
|
|
<Button onPress={handlePresensiCancel} textColor={colors.amethystSmoke}> |
|
|
|
<Button onPress={handlePresensiCancel} textColor={colors.amethystSmoke}> |
|
|
|
Batal |
|
|
|
{strings('global.cancel')} |
|
|
|
</Button> |
|
|
|
</Button> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
</BottomSheetView> |
|
|
|
</BottomSheetView> |
|
|
|
</BottomSheetModal> |
|
|
|
</BottomSheetModal> |
|
|
|
</BottomSheetModalProvider> |
|
|
|
</BottomSheetModalProvider> |
|
|
|
|
|
|
|
{loading && ( |
|
|
|
|
|
|
|
<View style={styles.loading}> |
|
|
|
|
|
|
|
<ActivityIndicator size="large" animating={true} color={colors.mistBlue} /> |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
)} |
|
|
|
</View > |
|
|
|
</View > |
|
|
|
); |
|
|
|
); |
|
|
|
}; |
|
|
|
}; |
|
|
@ -356,7 +547,7 @@ const styles = StyleSheet.create({ |
|
|
|
alignItems: 'center', |
|
|
|
alignItems: 'center', |
|
|
|
borderBottomLeftRadius: 30, |
|
|
|
borderBottomLeftRadius: 30, |
|
|
|
borderBottomRightRadius: 30, |
|
|
|
borderBottomRightRadius: 30, |
|
|
|
paddingHorizontal: 20, |
|
|
|
paddingHorizontal: 10, |
|
|
|
paddingVertical: 30, |
|
|
|
paddingVertical: 30, |
|
|
|
}, |
|
|
|
}, |
|
|
|
avatar: { |
|
|
|
avatar: { |
|
|
@ -404,6 +595,17 @@ const styles = StyleSheet.create({ |
|
|
|
elevation: 4, |
|
|
|
elevation: 4, |
|
|
|
backgroundColor: colors.pureWhite |
|
|
|
backgroundColor: colors.pureWhite |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
loading: { |
|
|
|
|
|
|
|
position: 'absolute', |
|
|
|
|
|
|
|
top: 0, |
|
|
|
|
|
|
|
bottom: 0, |
|
|
|
|
|
|
|
left: 0, |
|
|
|
|
|
|
|
right: 0, |
|
|
|
|
|
|
|
alignItems: 'center', |
|
|
|
|
|
|
|
justifyContent: 'center', |
|
|
|
|
|
|
|
backgroundColor: 'rgba(255, 255, 255, 0.3)', // Transparent white background
|
|
|
|
|
|
|
|
zIndex: 1, |
|
|
|
|
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
export default HomeScreen; |
|
|
|
export default HomeScreen; |
|
|
|