Browse Source

update

master
farhantock 3 months ago
parent
commit
9dca055585
  1. 586
      src/screens/Home.js
  2. 15
      src/screens/Profile.js
  3. 17
      src/screens/Service.js
  4. 439
      src/screens/registerPage/index.js
  5. 39
      src/services/api/base.js

586
src/screens/Home.js

@ -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,185 +59,333 @@ const HomeScreen = ({ route, navigation }) => {
return () => { return () => {
clearInterval(homeTimer); clearInterval(homeTimer);
} }
}, []) }, [])
); );
const onRefresh = React.useCallback(() => {
setRefreshing(true);
setLoadingSkeleton(true);
getPresenceHistory();
}, []);
useEffect(() => {
if (existingAttachmentNumber === '') {
setExistingAttachmentNumber(uuid.v4())
}
getPresenceHistory()
getLastPresence()
resendFailedAttachments();
}, [])
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) }, const getLastPresence = async () => {
{ id: '2', date: '2024-02-10', timeIn: '08:15', timeOut: '17:30', shift: "Pagi", duration: new Date(0, 0, 0, 8, 30, 0) }, const payload = {
{ id: '3', date: '2024-02-11', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 9, 0, 0) }, paging: { start: 0, length: 1 },
{ id: '4', date: '2024-02-12', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 10, 0, 0) }, columns: [
{ id: '5', date: '2024-02-13', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 11, 0, 0) }, { name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' },
{ id: '6', date: '2024-02-14', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 9, 30, 0) }, { name: 'date_presence', logic_operator: '=', operator: 'AND', value: moment().format('YYYY-MM-DD') },
{ id: '7', date: '2024-02-15', timeIn: '08:10', timeOut: '17:20', shift: "Pagi", duration: new Date(0, 0, 0, 12, 0, 0) }, { 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 handlePresensiConfirm = async () => { const handleSendPresenceData = async () => {
try { try {
const granted = await PermissionsAndroid.request( const currentDateTime = moment(new Date());
PermissionsAndroid.PERMISSIONS.CAMERA, getCoords(async ({ lat, lon, timestamp }) => {
{ if (lat === null || lon === null) {
title: strings('takePicture.cameraPermissionTitle'), Toast.show({
message: strings('takePicture.cameraPermissionMessage'), type: 'error',
buttonNeutral: strings('takePicture.cameraPermissionBtnNeutral'), text1: strings('presence.errorFetchingLocation'),
buttonNegative: strings('takePicture.cameraPermissionBtnCancel'), });
buttonPositive: strings('takePicture.cameraPermissionBtnPositive'), return;
}, }
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) { const payload = {
handleLaunchCamera() assign_hr_id: 1,
} else { datePresence: currentDateTime,
} in_time: currentDateTime,
} catch (err) { in_lat: lat,
console.warn(err); 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)
} }
bottomSheetModal.current?.dismiss();
}; };
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 {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
{
title: strings('takePicture.cameraPermissionTitle'),
message: strings('takePicture.cameraPermissionMessage'),
buttonNeutral: strings('takePicture.cameraPermissionBtnNeutral'),
buttonNegative: strings('takePicture.cameraPermissionBtnCancel'),
buttonPositive: strings('takePicture.cameraPermissionBtnPositive'),
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
handleLaunchCamera();
} else {
console.warn('Camera permission denied');
}
} catch (err) {
console.warn(err);
}
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 = {
storageOptions: { storageOptions: {
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 => { try {
// console.log('loc', loc) getCoords(async (loc) => {
if (loc) { if (loc) {
let imageObject = { const timestamp = new Date().toLocaleString();
"user_id": user_id, const location = { latitude: loc.lat, longitude: loc.lon };
"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 overlayText = `${timestamp}\nLatitude: ${location.latitude}\nLongitude: ${location.longitude}\nCopyright Nawakara`;
const markedImage = await ImageMarker.markText({
backgroundImage: {
src: { uri: response.assets[0].uri }
},
watermarkTexts: [{
text: overlayText,
positionOptions: {
position: Position.bottomLeft,
},
style: {
color: '#ffffff',
fontSize: 9,
fontName: 'Arial',
textBackgroundStyle: {
padding: 12,
type: TextBackgroundType.none,
color: '#00000080'
}
},
}],
position: 'bottomLeft',
color: '#ffffff',
fontName: 'Arial-BoldMT',
fontSize: 16,
scale: 1,
});
}) // 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 addOverlay = async (imageObject) => { const resultImage = await handleUploadImage([newImageData], PATH_PRESENCE);
try { handleSendPresenceData();
const timestamp = new Date().toLocaleString();
const location = { latitude: imageObject.lat, longitude: imageObject.lon };
const overlayText = `${timestamp}\nLatitude: ${location.latitude}\nLongitude: ${location.longitude}`;
if (imageObject.image_uri) {
const markedImage = await ImageMarker.markText({
backgroundImage: {
src: { uri: imageObject.image_uri }
},
watermarkTexts: [{
text: overlayText,
positionOptions: {
position: Position.bottomLeft,
},
style: {
color: '#ffffff',
fontSize: 8,
fontName: 'Arial',
textBackgroundStyle: {
padding: 10,
type: TextBackgroundType.none,
color: '#00000080'
} }
}, });
}], } catch (error) {
position: 'bottomLeft', console.error('Error adding overlay:', error);
color: '#ffffff', setLoading(false)
fontName: 'Arial-BoldMT', }
fontSize: 16, }
scale: 1,
});
await saveToTemporaryFolder(markedImage, imageObject);
} }
});
};
} catch (error) {
console.error('Error adding overlay:', error);
}
}
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>
@ -227,15 +399,15 @@ const HomeScreen = ({ route, navigation }) => {
<Card elevation={4} style={[styles.card, { backgroundColor: 'white' }]}> <Card elevation={4} style={[styles.card, { backgroundColor: 'white' }]}>
<Card.Content> <Card.Content>
<Text variant='displaySmall' style={[styles.boldText, { paddingBottom: 10, textAlign: 'center', color: colors.blue }]}>{moment(currentTime).format('HH:mm')} </Text> <Text variant='displaySmall' style={[styles.boldText, { paddingBottom: 10, textAlign: 'center', color: colors.blue }]}>{moment(currentTime).format('HH:mm')}</Text>
<Text variant='titleSmall' style={{ paddingBottom: 10 }}>{moment(currentTime).format('dddd, DD MMMM - YYYY')} </Text> <Text variant='titleSmall' style={{ paddingBottom: 10 }}>{moment(currentTime).format('dddd, DD MMMM - YYYY')} </Text>
<View style={[styles.row, { justifyContent: 'space-between' }]}> <View style={[styles.row, { justifyContent: 'space-between' }]}>
<Text variant='titleMedium' style={[styles.boldText, { textAlign: 'center', color: colors.amethystSmoke }]}>Masuk </Text> <Text variant='titleMedium' style={[styles.boldText, { textAlign: 'center', color: colors.amethystSmoke }]}>Masuk </Text>
<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,76 +436,95 @@ 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={
<Card key={item.id} style={styles.cardPrecense}> <RefreshControl
<Card.Content> refreshing={refreshing}
<View style={[styles.row, { justifyContent: 'space-between' }]}> onRefresh={onRefresh}
<View style={[styles.row, styles.precenseDate]}> colors={[colors.blue]}
<Icon name="calendar" size={20} color={colors.blue} /> progressBackgroundColor={colors.pureWhite}
<Text style={{ color: colors.blue, fontWeight: 'bold', paddingLeft: 3 }}> />
{moment(item.date).format('dddd, DD MMMM - YYYY')} }
</Text> >
</View> {loadingSkeleton ? (
<View style={[styles.precenseDate, { backgroundColor: colors.semigreen }]}> // renderSkeleton(2)
<Text style={{ color: colors.green, fontWeight: 'bold', paddingLeft: 3 }}> null
Shift {item.shift} ) : (
</Text> dataPresence.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.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>
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}> <View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}> <View style={styles.row}>
<Icon name="clockcircleo" size={20} color={colors.black} /> <Icon name="clockcircleo" size={20} color={colors.black} />
<Text style={{ color: colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3 }}> <Text style={{ color: colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3 }}>
Durasi Kerja Durasi Kerja
</Text> </Text>
</View> </View>
<View style={styles.row}> <View style={styles.row}>
<Text style={{ color: colors.amethystSmoke }}>Masuk</Text> <Text style={{ color: colors.amethystSmoke }}>Masuk</Text>
<Icon name="minus" size={20} color={colors.amethystSmoke} /> <Icon name="minus" size={20} color={colors.amethystSmoke} />
<Text style={{ color: colors.amethystSmoke }}>Keluar</Text> <Text style={{ color: colors.amethystSmoke }}>Keluar</Text>
</View> </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>
<View style={styles.row}> <View style={[styles.row, { justifyContent: 'space-between' }]}>
<Text>{item.timeIn}</Text> <View style={styles.row}>
<Icon name="minus" size={20} color={colors.black} /> <Text style={{ color: colors.black, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text>
<Text>{item.timeOut}</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> </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;

15
src/screens/Profile.js

@ -1,16 +1,17 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Alert, View, ScrollView, StyleSheet, TouchableOpacity, StatusBar } from 'react-native'; import { Alert, View, ScrollView, StyleSheet, TouchableOpacity, StatusBar } from 'react-native';
import { Avatar, Text, Appbar, List, Button, Switch, Modal, TouchableRipple } from 'react-native-paper'; import { Avatar, Text, Appbar, List, Button, Switch, Modal, Dialog, TouchableRipple } from 'react-native-paper';
import AntDesign from 'react-native-vector-icons/AntDesign'; import AntDesign from 'react-native-vector-icons/AntDesign';
import Ionicons from 'react-native-vector-icons/Ionicons'; import Ionicons from 'react-native-vector-icons/Ionicons';
import { clearAllState } from '../utils/Auth'; import { clearAllState } from '../utils/Auth';
import { strings } from '../utils/i18n'; import { strings } from '../utils/i18n';
import person from '../assets/images/courier_man.png' import person from '../assets/images/courier_man.png'
import { colors } from '../utils/color'; import { colors } from '../utils/color';
import { useSelector } from 'react-redux';
const ProfileScreen = ({ navigation }) => { const ProfileScreen = ({ navigation }) => {
const [isSwitchOn, setIsSwitchOn] = React.useState(false); const [isSwitchOn, setIsSwitchOn] = React.useState(false);
const { user } = useSelector(state => state.userReducer)
const onToggleSwitch = () => setIsSwitchOn(!isSwitchOn); const onToggleSwitch = () => setIsSwitchOn(!isSwitchOn);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@ -18,7 +19,7 @@ const ProfileScreen = ({ navigation }) => {
const showModal = () => setVisible(true); const showModal = () => setVisible(true);
const hideModal = () => setVisible(false); const hideModal = () => setVisible(false);
console.log("user", user.join);
return ( return (
<> <>
<View style={styles.container}> <View style={styles.container}>
@ -29,13 +30,13 @@ const ProfileScreen = ({ navigation }) => {
</Appbar.Header> </Appbar.Header>
<View style={styles.header}> <View style={styles.header}>
<Avatar.Image size={80} style={styles.avatar} source={person} /> <Avatar.Image style={styles.avatar} size={80} 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: colors.black }]}> <Text style={[styles.welcomeText, { color: colors.black }]}>
Farhan {user.name}
</Text> </Text>
<Text style={[styles.subWelcomeText, { color: colors.amethystSmoke }]}> <Text style={[styles.subWelcomeText, { color: colors.amethystSmoke }]}>
Land Encroachment Prevention Officer {user.join.m_role_name}
</Text> </Text>
</View> </View>
</View> </View>
@ -96,7 +97,7 @@ const ProfileScreen = ({ navigation }) => {
<Modal visible={visible} onDismiss={hideModal} contentContainerStyle={styles.modal}> <Modal visible={visible} onDismiss={hideModal} contentContainerStyle={styles.modal}>
<Text variant='titleLarge' >Konfirmasi</Text> <Text variant='titleLarge' >Konfirmasi</Text>
<Text variant='bodyMedium' >{strings('profile.signoutMessage')}</Text> <Text variant='bodyMedium' >{strings('profile.signoutMessage')}</Text>
<Button buttonColor={colors.blue} style={{ borderRadius: 10, marginVertical: 20, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} mode="contained" onPress={clearAllState()}> <Button buttonColor={colors.blue} style={{ borderRadius: 10, marginVertical: 20, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} mode="contained" onPress={() => { clearAllState() }}>
{strings('global.yes')} {strings('global.yes')}
</Button> </Button>
<Button style={{ borderRadius: 10, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} textColor={colors.mistBlue} mode="outlined" onPress={hideModal}> <Button style={{ borderRadius: 10, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} textColor={colors.mistBlue} mode="outlined" onPress={hideModal}>

17
src/screens/Service.js

@ -8,6 +8,7 @@ import danger2 from '../assets/images/danger2.png';
import patroli from '../assets/images/patroli.png'; import patroli from '../assets/images/patroli.png';
import calender from '../assets/images/calender.png'; import calender from '../assets/images/calender.png';
import { colors } from '../utils/color'; import { colors } from '../utils/color';
import { strings } from '../utils/i18n';
export default function ServiceScreen({ route, navigation }) { export default function ServiceScreen({ route, navigation }) {
const theme = useTheme(); const theme = useTheme();
@ -20,7 +21,7 @@ export default function ServiceScreen({ route, navigation }) {
<Text style={[styles.Text, { color: colors.pureWhite }]}>LAPORAN KEJADIAN</Text> <Text style={[styles.Text, { color: colors.pureWhite }]}>LAPORAN KEJADIAN</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Laporan kejadian wajib diisi jika ada kejadian di tempat project</Text> <Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Laporan kejadian wajib diisi jika ada kejadian di tempat project</Text>
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 7, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.beanRed }} onPress={() => { navigation.navigate('IncidentScreen') }}> <Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 7, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.beanRed }} onPress={() => { navigation.navigate('IncidentScreen') }}>
ISI {strings('global.fill')}
</Button> </Button>
</View> </View>
<View style={styles.rightContent}> <View style={styles.rightContent}>
@ -48,7 +49,7 @@ export default function ServiceScreen({ route, navigation }) {
<Text style={[styles.Text, { color: colors.pureWhite }]}>Laporan Kegiatan</Text> <Text style={[styles.Text, { color: colors.pureWhite }]}>Laporan Kegiatan</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Wajib mengisi Kegiatan</Text> <Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Wajib mengisi Kegiatan</Text>
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('ActivityScreen') }}> <Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('ActivityScreen') }}>
ISI {strings('global.fill')}
</Button> </Button>
</View> </View>
</TouchableRipple> </TouchableRipple>
@ -65,7 +66,7 @@ export default function ServiceScreen({ route, navigation }) {
<Text style={[styles.Text, { color: colors.pureWhite }]}>Laporan Harian</Text> <Text style={[styles.Text, { color: colors.pureWhite }]}>Laporan Harian</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Wajib di isi setiap hari</Text> <Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Wajib di isi setiap hari</Text>
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('DailyReportScreen') }}> <Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('DailyReportScreen') }}>
ISI {strings('global.fill')}
</Button> </Button>
</View> </View>
</TouchableRipple> </TouchableRipple>
@ -85,7 +86,7 @@ export default function ServiceScreen({ route, navigation }) {
<Text style={[styles.Text, { color: colors.pureWhite }]}>Patroli</Text> <Text style={[styles.Text, { color: colors.pureWhite }]}>Patroli</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Patroli setiap Pos</Text> <Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Patroli setiap Pos</Text>
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('PatroliScreen') }}> <Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('PatroliScreen') }}>
ISI {strings('global.fill')}
</Button> </Button>
</View> </View>
</TouchableRipple> </TouchableRipple>
@ -102,7 +103,7 @@ export default function ServiceScreen({ route, navigation }) {
<Text style={[styles.Text, { color: colors.pureWhite }]}>Kehadiran</Text> <Text style={[styles.Text, { color: colors.pureWhite }]}>Kehadiran</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Lihat riwayat kehadiran</Text> <Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Lihat riwayat kehadiran</Text>
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('PresenceScreen') }}> <Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('PresenceScreen') }}>
ISI {strings('global.see')}
</Button> </Button>
</View> </View>
</TouchableRipple> </TouchableRipple>
@ -118,10 +119,10 @@ export default function ServiceScreen({ route, navigation }) {
style={{ width: '100%', height: Dimensions.get('window').height / 12 }} style={{ width: '100%', height: Dimensions.get('window').height / 12 }}
resizeMode="contain" resizeMode="contain"
/> />
<Text style={[styles.Text, { color: colors.pureWhite }]}>TOMBOL PANIK</Text> <Text style={[styles.Text, { color: colors.pureWhite }]}> {strings('global.panicButton')}</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Tekan tombol ini jika terjadi insiden</Text> <Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} > {strings('global.panicButtonMessage')}</Text>
<Button mode="contained" style={{ marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%', borderRadius: 10 }} labelStyle={{ color: colors.beanRed }} onPress={() => { navigation.navigate('DialogFormIncident') }}> <Button mode="contained" style={{ marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%', borderRadius: 10 }} labelStyle={{ color: colors.beanRed }} onPress={() => { navigation.navigate('DialogFormIncident') }}>
TEKAN {strings('global.press')}
</Button> </Button>
</View> </View>
</TouchableRipple> </TouchableRipple>

439
src/screens/registerPage/index.js

@ -8,34 +8,106 @@ import { launchCamera } from 'react-native-image-picker';
import { requestAccessStoragePermission } from '../../utils/storage'; import { requestAccessStoragePermission } from '../../utils/storage';
import { getCoords } from '../../utils/geolocation'; import { getCoords } from '../../utils/geolocation';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { import { BottomSheetModal, BottomSheetModalProvider, BottomSheetView } from '@gorhom/bottom-sheet';
BottomSheetModal,
BottomSheetModalProvider,
BottomSheetView
} from '@gorhom/bottom-sheet';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker';
import DateTimePicker from '@react-native-community/datetimepicker'; import DateTimePicker from '@react-native-community/datetimepicker';
import moment from 'moment'; import moment from 'moment';
import uuid from 'react-native-uuid';
import RequestModule
from '../../services/api/request';
import { PATH_ID } from '../../config/imageFolder';
import { useSelector } from 'react-redux';
export default function DialogForm() { export default function DialogForm() {
const request = new RequestModule('project-charter')
const requestAssign = new RequestModule('assign-hr-to-project')
const navigation = useNavigation(); const navigation = useNavigation();
const { user } = useSelector(state => state.userReducer)
const [images, setImages] = useState([]); const [images, setImages] = useState([]);
const [selectedImageUri, setSelectedImageUri] = useState(null); const [selectedImageUri, setSelectedImageUri] = useState(null);
const bottomSheetImage = useRef(null); const bottomSheetImage = useRef(null);
const [selectedProject, setSelectedProject] = useState(null); const [selectedProject, setSelectedProject] = useState(null);
const [area, setArea] = useState({ external: false, internal: false }) const [listArea, setListArea] = useState([])
const [area, setArea] = useState('')
const [children, setChildren] = useState([]);
const [selectedPost, setSelectedPost] = useState('')
const [showExpDatePicker, setShowExpDatePicker] = useState(false); const [showExpDatePicker, setShowExpDatePicker] = useState(false);
const [expDate, setExpDate] = useState(''); const [expDate, setExpDate] = useState('');
const [dataListProjectCharters, setDataListProjectCharters] = useState([]);
const [shift, setShift] = useState('')
const [position, setPosition] = useState('')
const [existingAttachmentNumber, setExistingAttachmentNumber] = useState('');
const bottomSheetModal = useRef(null); const bottomSheetModal = useRef(null);
const bottomSheetModalArea = useRef(null);
const bottomSheetModalPost = useRef(null);
const bottomSheetModalPosition = useRef(null);
const handleOpenSheet = useCallback(() => { const handleOpenSheet = useCallback(() => {
bottomSheetModal.current?.present(); bottomSheetModal.current?.present();
}, []); }, []);
const handleOpenSheetArea = useCallback(() => {
bottomSheetModalArea.current?.present();
}, []);
const handleOpenSheetPost = useCallback(() => {
bottomSheetModalPost.current?.present();
}, []);
const handleOpenSheetPosition = useCallback(() => {
bottomSheetModalPosition.current?.present();
}, []);
useEffect(() => {
if (existingAttachmentNumber === '') {
setExistingAttachmentNumber(uuid.v4())
}
handleGetProjectCharter()
}, [])
const handleProjectPress = () => { const handleProjectPress = () => {
navigation.navigate('SearchPage', { dummyData, onSelect: handleProjectSelect }); navigation.navigate('SearchPage', { dataListProjectCharters, onSelect: handleProjectSelect });
}; };
const handleSendRegisterData = async () => {
const payload = {
project_charter_id: selectedProject.id,
hr_id: user.id,
Position: position,
Area: area,
Post: selectedPost,
attachment_number: existingAttachmentNumber
};
try {
const result = await requestAssign.addData(payload);
if (result.status === 201) {
Toast.show({
type: 'success',
text1: strings('presence.dataSentSuccessfully'),
});
navigation.navigate('App');
} else {
Toast.show({
type: 'error',
text1: strings('presence.failedSendDataPresence'),
});
}
} catch (error) {
console.error("Network error sending presence data:", error);
Toast.show({
type: 'error',
text1: strings('presence.errorMessage'),
});
} finally {
setLoading(false); // Ensure setLoading is called once in the end
}
};
const handleProjectSelect = (project) => { const handleProjectSelect = (project) => {
handleGetDataManPowers(project.id)
setSelectedProject(project); setSelectedProject(project);
}; };
@ -54,43 +126,65 @@ export default function DialogForm() {
bottomSheetImage.current?.present(); bottomSheetImage.current?.present();
}, []); }, []);
const manpower = [ const handleGetProjectCharter = async () => {
{ id: '2', status: "Sakit", position: "Guard", name: "ibnu", date: '2024-02-10', timeIn: '08:15', timeOut: '17:30' }, const payload = {
{ id: '3', status: "Cuti", position: "Guard", name: "ardhi", date: '2024-02-11', timeIn: '08:10', timeOut: '17:20' }, paging: { start: 0, length: -1 },
{ id: '4', status: "", position: "Admin", name: "hana", date: '2024-02-12', timeIn: '08:10', timeOut: '17:20' }, columns: [
{ id: '5', status: "", position: "Guard", name: "satori", date: '2024-02-13', timeIn: '08:10', timeOut: '17:20' }, { name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND', table_name: '' },
{ id: '6', status: "", position: "Guard", name: "khaidir", date: '2024-02-14', timeIn: '08:10', timeOut: '17:20' }, { name: 'phase', logic_operator: 'like', operator: 'AND', table_name: '', value: 'Active' },
]; { name: 'sicn', logic_operator: 'like', value: '', operator: 'AND', table_name: '' }
],
const dummyData = [ joins: [{ name: 'm_regional', column_join: 'regional', column_results: ['abbreviation'] }],
{ id: '1', name: 'MRT', icn: 'test1', sicn: 'test"1' }, orders: { columns: ['created_at', 'id'], ascending: false }
{ id: '2', name: 'PLN', icn: 'test1', sicn: 'test"1' }, };
{ id: '3', name: 'Pertamina', icn: 'test1', sicn: 'test"1' },
{ id: '4', name: 'MD Entertaiment', icn: 'test1', sicn: 'test"1' }, const result = await request.getDataSearch(payload);
{ id: '5', name: 'Jhonny', icn: 'test1', sicn: 'test"1' }, if (result && result.status === 200) {
{ id: '6', name: 'AIA', icn: 'test1', sicn: 'test"1' }, let integratedData = result.data.data.map(async (el) => {
{ id: '7', name: 'PLN Pulogadung', icn: 'test1', sicn: 'test"1' }, el.sicnonly = el.sicn
{ id: '8', name: 'Google', icn: 'test1', sicn: 'test"1' }, if (el.sicn)
{ id: '9', name: 'Amazon', icn: 'test1', sicn: 'test"1' }, el.sicn = el.sicn + " (" + el.project_name + ")";
{ id: '10', name: 'Microsoft', icn: 'test1', sicn: 'test"1' }, return el;
{ id: '11', name: 'Facebook', icn: 'test1', sicn: 'test"1' }, })
{ id: '12', name: 'Apple', icn: 'test1', sicn: 'test"1' }, setDataListProjectCharters(result.data.data)
{ id: '13', name: 'Tesla', icn: 'test1', sicn: 'test"1' }, } else {
{ id: '14', name: 'Netflix', icn: 'test1', sicn: 'test"1' },
{ id: '15', name: 'Twitter', icn: 'test1', sicn: 'test"1' }, }
{ id: '16', name: 'LinkedIn', icn: 'test1', sicn: 'test"1' }, }
{ id: '17', name: 'Instagram', icn: 'test1', sicn: 'test"1' },
{ id: '18', name: 'Snapchat', icn: 'test1', sicn: 'test"1' }, const handleSelectArea = (area) => {
{ id: '19', name: 'WhatsApp', icn: 'test1', sicn: 'test"1' }, if (area !== '') {
{ id: '20', name: 'Uber', icn: 'test1', sicn: 'test"1' }, setArea(area.location_name)
{ id: '21', name: 'Airbnb', icn: 'test1', sicn: 'test"1' }, }
{ id: '22', name: 'Spotify', icn: 'test1', sicn: 'test"1' }, setChildren(area.children || []);
{ id: '23', name: 'Pinterest', icn: 'test1', sicn: 'test"1' }, setSelectedPost(null)
{ id: '24', name: 'Reddit', icn: 'test1', sicn: 'test"1' }, bottomSheetModalArea.current?.dismiss();
{ id: '25', name: 'Dropbox', icn: 'test1', sicn: 'test"1' }, };
{ id: '26', name: 'Zoom', icn: 'test1', sicn: 'test"1' },
{ id: '27', name: 'Etsy' } const handleSelectShift = (shift) => {
]; setShift(shift)
bottomSheetModal.current?.dismiss();
};
const handleSelectPost = (post) => {
setSelectedPost(post)
bottomSheetModalPost.current?.dismiss();
};
const handleSelectPosition = (post) => {
setPosition(post)
bottomSheetModalPosition.current?.dismiss();
};
const handleGetDataManPowers = async (projectCharterId) => {
const result = await request.getDataById(`${projectCharterId}?q=area&t=area`);
if (result.data.code == 200) {
setListArea(result.data.data)
} else {
}
};
const handleSetImageUri = (uri, description) => { const handleSetImageUri = (uri, description) => {
const newImage = { uri: uri, description: description || "" }; const newImage = { uri: uri, description: description || "" };
@ -124,99 +218,102 @@ export default function DialogForm() {
} }
}; };
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 = {
storageOptions: { storageOptions: {
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');
} else if (response.error) { } else if (response.error) {
} else if (response.customButton) { console.log('ImagePicker Error: ', response.error);
} else { } else {
if (response.assets && response.assets.length > 0) { if (response.assets && response.assets.length > 0) {
handleSetImageUri(response.assets[0].uri); setLoading(true)
getCoords(loc => { try {
if (loc) { getCoords(async (loc) => {
let imageObject = { if (loc) {
"image_uri": response.assets[0].uri, const timestamp = new Date().toLocaleString();
"image_blob": response.assets[0].base64, const location = { latitude: loc.lat, longitude: loc.lon };
"lat": loc.lat,
"lon": loc.lon
}
addOverlay(imageObject)
}
})
}
}
});
}
const addOverlay = async (imageObject) => { const overlayText = `${timestamp}\nLatitude: ${location.latitude}\nLongitude: ${location.longitude}\nCopyright Nawakara`;
try { const markedImage = await ImageMarker.markText({
const timestamp = new Date().toLocaleString(); backgroundImage: {
const location = { latitude: imageObject.lat, longitude: imageObject.lon }; src: { uri: response.assets[0].uri }
},
const overlayText = `${timestamp}\nLatitude: ${location.latitude}\nLongitude: ${location.longitude}`; watermarkTexts: [{
text: overlayText,
if (imageObject.image_uri) { positionOptions: {
const markedImage = await ImageMarker.markText({ position: Position.bottomLeft,
backgroundImage: { },
src: { uri: imageObject.image_uri } style: {
}, color: '#ffffff',
watermarkTexts: [{ fontSize: 9,
text: overlayText, fontName: 'Arial',
positionOptions: { textBackgroundStyle: {
position: Position.bottomLeft, padding: 12,
}, type: TextBackgroundType.none,
style: { color: '#00000080'
color: '#ffffff', }
fontSize: 8, },
fontName: 'Arial', }],
textBackgroundStyle: { position: 'bottomLeft',
padding: 10, color: '#ffffff',
type: TextBackgroundType.none, fontName: 'Arial-BoldMT',
color: '#00000080' fontSize: 16,
} scale: 1,
}, });
}],
position: 'bottomLeft',
color: '#ffffff',
fontName: 'Arial-BoldMT',
fontSize: 16,
scale: 1,
});
await saveToTemporaryFolder(markedImage, imageObject);
}
} catch (error) { // const tempPath = `file://${RNFS.TemporaryDirectoryPath}/prsensi/${moment().format('YYYYMMDDHHmmss')}.jpg`;
console.error('Error adding overlay:', error); // await RNFS.copyFile(markedImage, tempPath);
}
}
const saveToTemporaryFolder = async (markedImage, imageObject) => { const newImageData = {
try { id: 0,
const tempPath = `file://${RNFS.TemporaryDirectoryPath}/KTA/${moment().format('YYYYMMDDHHmmss')}.jpg`; attachment_number: existingAttachmentNumber,
imageFile: `file://${markedImage}`,
description: "-",
data: response.assets[0].uri,
type: response.assets[0].type,
name: response.assets[0].fileName
};
if (!requestAccessStoragePermission()) { const resultImage = await handleUploadImage([newImageData], PATH_ID);
return; handleSendRegisterData();
}
});
} catch (error) {
console.error('Error adding overlay:', error);
setLoading(false)
}
}
} }
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 renderImages = useMemo(() => images.map((image, index) => ( const renderImages = useMemo(() => images.map((image, index) => (
<> <>
<View key={index} style={styles.imageBlock}> <View key={index} style={styles.imageBlock}>
@ -237,6 +334,26 @@ export default function DialogForm() {
</> </>
)), [images]); )), [images]);
const renderArea = useMemo(() => listArea?.manpower_planning?.info?.data.map((data, index) => (
<>
<TouchableRipple onPress={() => handleSelectArea(data)}>
<List.Item
title={data.location_name}
/>
</TouchableRipple>
</>
)), [listArea]);
const renderPost = useMemo(() => children?.map((data, index) => (
<>
<TouchableRipple onPress={() => handleSelectPost(data.location_name)}>
<List.Item
title={data.location_name}
/>
</TouchableRipple>
</>
)), [children]);
return ( return (
<> <>
<StatusBar backgroundColor={colors.blue} barStyle='light-content' translucent={true} /> <StatusBar backgroundColor={colors.blue} barStyle='light-content' translucent={true} />
@ -257,7 +374,7 @@ export default function DialogForm() {
editable={false} editable={false}
label='Pilih Proyek' label='Pilih Proyek'
placeholder='Pilih Proyek' placeholder='Pilih Proyek'
value={selectedProject ? selectedProject.name : ''} value={selectedProject ? selectedProject.project_name : ''}
right={<TextInput.Icon icon="chevron-down" />} right={<TextInput.Icon icon="chevron-down" />}
/> />
</TouchableRipple> </TouchableRipple>
@ -267,7 +384,7 @@ export default function DialogForm() {
style={{ marginTop: 10 }} style={{ marginTop: 10 }}
dense={true} dense={true}
editable={false} editable={false}
value='' value={shift ? shift : ''}
outlineColor={colors.amethystSmoke} outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue} activeOutlineColor={colors.blue}
mode="outlined" mode="outlined"
@ -276,12 +393,12 @@ export default function DialogForm() {
right={<TextInput.Icon icon="chevron-down" />} right={<TextInput.Icon icon="chevron-down" />}
/> />
</TouchableRipple> </TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> <TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheetPosition}>
<TextInput <TextInput
style={{ marginTop: 10 }} style={{ marginTop: 10 }}
dense={true} dense={true}
editable={false} editable={false}
value='' value={position ? position : ''}
outlineColor={colors.amethystSmoke} outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue} activeOutlineColor={colors.blue}
mode="outlined" mode="outlined"
@ -290,12 +407,12 @@ export default function DialogForm() {
right={<TextInput.Icon icon="chevron-down" />} right={<TextInput.Icon icon="chevron-down" />}
/> />
</TouchableRipple> </TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> <TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheetArea}>
<TextInput <TextInput
style={{ marginTop: 10 }} style={{ marginTop: 10 }}
dense={true} dense={true}
editable={false} editable={false}
value='' value={area ? area : ''}
outlineColor={colors.amethystSmoke} outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue} activeOutlineColor={colors.blue}
mode="outlined" mode="outlined"
@ -304,12 +421,12 @@ export default function DialogForm() {
right={<TextInput.Icon icon="chevron-down" />} right={<TextInput.Icon icon="chevron-down" />}
/> />
</TouchableRipple> </TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> <TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheetPost}>
<TextInput <TextInput
style={{ marginTop: 10 }} style={{ marginTop: 10 }}
dense={true} dense={true}
editable={false} editable={false}
value='' value={selectedPost ? selectedPost : ''}
outlineColor={colors.amethystSmoke} outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue} activeOutlineColor={colors.blue}
mode="outlined" mode="outlined"
@ -349,36 +466,94 @@ export default function DialogForm() {
</Button> </Button>
</View> </View>
</View > </View >
<BottomSheetModalProvider> <BottomSheetModalProvider>
<BottomSheetModal
ref={bottomSheetModalPosition}
index={0}
snapPoints={['30%']}
bottomInset={10}
detached={true}
style={{ marginHorizontal: 15, shadowOpacity: 10 }}
>
<BottomSheetView >
<TouchableRipple onPress={() => handleSelectPosition("Guard")}>
<List.Item
title="GUARD"
left={props => <List.Icon {...props} icon="account-circle" />}
/>
</TouchableRipple>
<TouchableRipple onPress={() => handleSelectPosition("Danru")}>
<List.Item
title="DANRU"
left={props => <List.Icon {...props} icon="account-group" />}
/>
</TouchableRipple>
<TouchableRipple onPress={() => handleSelectPosition("spv")}>
<List.Item
title="PIMPINAN PROJECT / SPV"
left={props => <List.Icon {...props} icon="account-supervisor-circle" />}
/>
</TouchableRipple>
</BottomSheetView>
</BottomSheetModal>
<BottomSheetModal <BottomSheetModal
ref={bottomSheetModal} ref={bottomSheetModal}
index={0} index={0}
snapPoints={['30%']} snapPoints={['30%']}
bottomInset={10} bottomInset={10}
detached={true} detached={true}
style={{ marginHorizontal: 15 }} style={{ marginHorizontal: 15, shadowOpacity: 10 }}
> >
<BottomSheetView > <BottomSheetView >
<TouchableRipple onPress={() => console.log("pagi")}> <TouchableRipple onPress={() => handleSelectShift("pagi")}>
<List.Item <List.Item
title="PAGI" title="PAGI"
left={props => <List.Icon {...props} icon="" />} left={props => <List.Icon {...props} icon="arrow-top-left" />}
/> />
</TouchableRipple> </TouchableRipple>
<TouchableRipple onPress={() => console.log("siang")}> <TouchableRipple onPress={() => handleSelectShift("siang")}>
<List.Item <List.Item
title="SIANG" title="SIANG"
left={props => <List.Icon {...props} icon="" />} left={props => <List.Icon {...props} icon="arrow-top-left" />}
/> />
</TouchableRipple> </TouchableRipple>
<TouchableRipple onPress={() => console.log("malem")}> <TouchableRipple onPress={() => handleSelectShift("malem")}>
<List.Item <List.Item
title="MALEM" title="MALEM"
left={props => <List.Icon {...props} icon="" />} left={props => <List.Icon {...props} icon="arrow-top-left" />}
/> />
</TouchableRipple> </TouchableRipple>
</BottomSheetView> </BottomSheetView>
</BottomSheetModal> </BottomSheetModal>
<BottomSheetModal
ref={bottomSheetModalArea}
index={0}
snapPoints={['50%']}
bottomInset={10}
detached={true}
style={{ marginHorizontal: 15 }}
>
<BottomSheetView >
{renderArea}
</BottomSheetView>
</BottomSheetModal>
<BottomSheetModal
ref={bottomSheetModalPost}
index={0}
snapPoints={['50%']}
bottomInset={10}
detached={true}
style={{ marginHorizontal: 15 }}
>
<BottomSheetView >
{renderPost}
</BottomSheetView>
</BottomSheetModal>
<BottomSheetModal <BottomSheetModal
ref={bottomSheetImage} ref={bottomSheetImage}
index={0} index={0}

39
src/services/api/base.js

@ -1,8 +1,36 @@
import axios from 'axios'; import axios from 'axios';
import { store } from '../../appredux/store';
export default class RequestApi { export default class RequestApi {
static Request() { static Hedaer() {
const token = localStorage.getItem('token')
let instance = axios.create({
headers: {
'Content-Type': 'application/json',
}
})
instance.interceptors.response.use(
(response) => response,
async (error) => {
// const originalRequest = error.config;
if (error.response.status === 307 || error.response.status === 403) {
console.log(error.response);
}
return Promise.reject(error);
}
);
return instance;
}
static Request() {
const { user } = store.getState().userReducer
let token = ''
if (user && user.token) {
token = user.token
}
let instance = axios.create({ let instance = axios.create({
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -26,8 +54,11 @@ export default class RequestApi {
} }
static RequestUploadImage() { static RequestUploadImage() {
const token = localStorage.getItem('token') const { user } = store.getState().userReducer
let token = ''
if (user && user.token) {
token = user.token
}
let instance = axios.create({ let instance = axios.create({
headers: { headers: {
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",

Loading…
Cancel
Save