farhantock
5 months ago
1 changed files with 460 additions and 0 deletions
@ -0,0 +1,460 @@
|
||||
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'; |
||||
import { StatusBar } from 'react-native'; |
||||
import { Button, IconButton, Text, Appbar, TextInput, TouchableRipple, Card, List } from 'react-native-paper'; |
||||
import { StyleSheet, View, ScrollView, PermissionsAndroid, Image } from 'react-native'; |
||||
import { colors } from '../../utils/color'; |
||||
import { strings } from '../../utils/i18n'; |
||||
import { launchCamera } from 'react-native-image-picker'; |
||||
import { requestAccessStoragePermission } from '../../utils/storage'; |
||||
import { getCoords } from '../../utils/geolocation'; |
||||
import { useNavigation } from '@react-navigation/native'; |
||||
import { |
||||
BottomSheetModal, |
||||
BottomSheetModalProvider, |
||||
BottomSheetView |
||||
} from '@gorhom/bottom-sheet'; |
||||
import RNFS from 'react-native-fs'; |
||||
import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; |
||||
import DateTimePicker from '@react-native-community/datetimepicker'; |
||||
import moment from 'moment'; |
||||
|
||||
export default function DialogForm() { |
||||
const navigation = useNavigation(); |
||||
const [images, setImages] = useState([]); |
||||
const [selectedImageUri, setSelectedImageUri] = useState(null); |
||||
const bottomSheetImage = useRef(null); |
||||
const [selectedProject, setSelectedProject] = useState(null); |
||||
const [area, setArea] = useState({ external: false, internal: false }) |
||||
const [showExpDatePicker, setShowExpDatePicker] = useState(false); |
||||
const [expDate, setExpDate] = useState(''); |
||||
const bottomSheetModal = useRef(null); |
||||
const handleOpenSheet = useCallback(() => { |
||||
bottomSheetModal.current?.present(); |
||||
}, []); |
||||
const handleProjectPress = () => { |
||||
navigation.navigate('SearchPage', { dummyData, onSelect: handleProjectSelect }); |
||||
}; |
||||
|
||||
const handleProjectSelect = (project) => { |
||||
setSelectedProject(project); |
||||
}; |
||||
|
||||
const handleExpDateChange = (event, selectedDate) => { |
||||
const currentDate = selectedDate; |
||||
setShowExpDatePicker(false); |
||||
setExpDate(currentDate); |
||||
}; |
||||
|
||||
const showExpDatePickerFunc = () => { |
||||
setShowExpDatePicker(true); |
||||
}; |
||||
const handleOpenSheetImage = useCallback((uri) => { |
||||
|
||||
setSelectedImageUri(uri); |
||||
bottomSheetImage.current?.present(); |
||||
}, []); |
||||
|
||||
const manpower = [ |
||||
{ id: '2', status: "Sakit", position: "Guard", name: "ibnu", date: '2024-02-10', timeIn: '08:15', timeOut: '17:30' }, |
||||
{ id: '3', status: "Cuti", position: "Guard", name: "ardhi", date: '2024-02-11', timeIn: '08:10', timeOut: '17:20' }, |
||||
{ id: '4', status: "", position: "Admin", name: "hana", date: '2024-02-12', timeIn: '08:10', timeOut: '17:20' }, |
||||
{ id: '5', status: "", position: "Guard", name: "satori", date: '2024-02-13', timeIn: '08:10', timeOut: '17:20' }, |
||||
{ id: '6', status: "", position: "Guard", name: "khaidir", date: '2024-02-14', timeIn: '08:10', timeOut: '17:20' }, |
||||
]; |
||||
|
||||
const dummyData = [ |
||||
{ id: '1', name: 'MRT', icn: 'test1', sicn: 'test"1' }, |
||||
{ 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' }, |
||||
{ id: '5', name: 'Jhonny', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '6', name: 'AIA', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '7', name: 'PLN Pulogadung', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '8', name: 'Google', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '9', name: 'Amazon', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '10', name: 'Microsoft', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '11', name: 'Facebook', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '12', name: 'Apple', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '13', name: 'Tesla', icn: 'test1', sicn: 'test"1' }, |
||||
{ 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' }, |
||||
{ id: '19', name: 'WhatsApp', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '20', name: 'Uber', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '21', name: 'Airbnb', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '22', name: 'Spotify', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '23', name: 'Pinterest', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '24', name: 'Reddit', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '25', name: 'Dropbox', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '26', name: 'Zoom', icn: 'test1', sicn: 'test"1' }, |
||||
{ id: '27', name: 'Etsy' } |
||||
]; |
||||
|
||||
const handleSetImageUri = (uri, description) => { |
||||
const newImage = { uri: uri, description: description || "" }; |
||||
setImages(prevImages => [...prevImages, newImage]); |
||||
} |
||||
|
||||
const handleDeleteImage = (index) => { |
||||
const updatedImages = [...images]; |
||||
updatedImages.splice(index, 1); |
||||
setImages(updatedImages); |
||||
}; |
||||
|
||||
const handleTakePicture = async () => { |
||||
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 { |
||||
} |
||||
} catch (err) { |
||||
console.warn(err); |
||||
} |
||||
}; |
||||
|
||||
const handleLaunchCamera = () => { |
||||
let options = { |
||||
storageOptions: { |
||||
skipBackup: true, |
||||
path: 'images', |
||||
}, |
||||
cameraType: 'front', |
||||
maxWidth: 768, |
||||
maxHeight: 1024, |
||||
saveToPhotos: false, |
||||
includeBase64: true |
||||
}; |
||||
|
||||
launchCamera(options, (response) => { |
||||
if (response.didCancel) { |
||||
} else if (response.error) { |
||||
} else if (response.customButton) { |
||||
} else { |
||||
if (response.assets && response.assets.length > 0) { |
||||
handleSetImageUri(response.assets[0].uri); |
||||
getCoords(loc => { |
||||
if (loc) { |
||||
let imageObject = { |
||||
"image_uri": response.assets[0].uri, |
||||
"image_blob": response.assets[0].base64, |
||||
"lat": loc.lat, |
||||
"lon": loc.lon |
||||
} |
||||
addOverlay(imageObject) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
const addOverlay = async (imageObject) => { |
||||
try { |
||||
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' |
||||
} |
||||
}, |
||||
}], |
||||
position: 'bottomLeft', |
||||
color: '#ffffff', |
||||
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}/KTA/${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 renderImages = useMemo(() => images.map((image, index) => ( |
||||
<> |
||||
<View key={index} style={styles.imageBlock}> |
||||
<TouchableRipple onPress={() => { handleOpenSheetImage(image.uri) }}> |
||||
<View style={styles.imageContainer}> |
||||
<Image |
||||
source={{ uri: image.uri }} |
||||
style={styles.image} |
||||
resizeMode="cover" |
||||
/> |
||||
</View> |
||||
</TouchableRipple> |
||||
</View> |
||||
|
||||
<Button icon="delete" style={{ borderRadius: 10, backgroundColor: colors.semiRed, marginHorizontal: 5, marginBottom: 10 }} textColor={colors.beanRed} mode="contained-tonal" onPress={() => handleDeleteImage(index)}> |
||||
{strings('global.delete')} |
||||
</Button> |
||||
</> |
||||
)), [images]); |
||||
|
||||
return ( |
||||
<> |
||||
<StatusBar backgroundColor={colors.blue} barStyle='light-content' translucent={true} /> |
||||
<Appbar.Header mode='center-aligned' style={{ backgroundColor: colors.blue, elevation: 4 }}> |
||||
<Appbar.BackAction color={colors.pureWhite} onPress={() => { navigation.goBack() }} /> |
||||
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={colors.pureWhite} title={strings('global.register')} /> |
||||
</Appbar.Header> |
||||
<View style={{ flex: 1, backgroundColor: colors.pureWhite }}> |
||||
<View style={styles.container}> |
||||
|
||||
<ScrollView style={[styles.scrollViewContainer, { backgroundColor: colors.pureWhite }]}> |
||||
<TouchableRipple onPress={handleProjectPress}> |
||||
<TextInput |
||||
dense={true} |
||||
outlineColor={colors.amethystSmoke} |
||||
activeOutlineColor={colors.blue} |
||||
mode="outlined" |
||||
editable={false} |
||||
label='Pilih Proyek' |
||||
placeholder='Pilih Proyek' |
||||
value={selectedProject ? selectedProject.name : ''} |
||||
right={<TextInput.Icon icon="chevron-down" />} |
||||
/> |
||||
</TouchableRipple> |
||||
|
||||
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> |
||||
<TextInput |
||||
style={{ marginTop: 10 }} |
||||
dense={true} |
||||
editable={false} |
||||
value='' |
||||
outlineColor={colors.amethystSmoke} |
||||
activeOutlineColor={colors.blue} |
||||
mode="outlined" |
||||
label='Shift' |
||||
placeholder='Shift' |
||||
right={<TextInput.Icon icon="chevron-down" />} |
||||
/> |
||||
</TouchableRipple> |
||||
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> |
||||
<TextInput |
||||
style={{ marginTop: 10 }} |
||||
dense={true} |
||||
editable={false} |
||||
value='' |
||||
outlineColor={colors.amethystSmoke} |
||||
activeOutlineColor={colors.blue} |
||||
mode="outlined" |
||||
label='Jabatan' |
||||
placeholder='Jabatan' |
||||
right={<TextInput.Icon icon="chevron-down" />} |
||||
/> |
||||
</TouchableRipple> |
||||
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> |
||||
<TextInput |
||||
style={{ marginTop: 10 }} |
||||
dense={true} |
||||
editable={false} |
||||
value='' |
||||
outlineColor={colors.amethystSmoke} |
||||
activeOutlineColor={colors.blue} |
||||
mode="outlined" |
||||
label='Area' |
||||
placeholder='Area' |
||||
right={<TextInput.Icon icon="chevron-down" />} |
||||
/> |
||||
</TouchableRipple> |
||||
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}> |
||||
<TextInput |
||||
style={{ marginTop: 10 }} |
||||
dense={true} |
||||
editable={false} |
||||
value='' |
||||
outlineColor={colors.amethystSmoke} |
||||
activeOutlineColor={colors.blue} |
||||
mode="outlined" |
||||
label='Pos' |
||||
placeholder='Pos' |
||||
right={<TextInput.Icon icon="chevron-down" />} |
||||
/> |
||||
</TouchableRipple> |
||||
<TouchableRipple rippleColor={colors.pureWhite} onPress={showExpDatePickerFunc}> |
||||
<TextInput |
||||
style={{ marginTop: 10 }} |
||||
dense={true} |
||||
editable={false} |
||||
value={expDate ? moment(expDate).format("DD-MM-YYYY") : ''} |
||||
outlineColor={colors.amethystSmoke} |
||||
activeOutlineColor={colors.blue} |
||||
mode="outlined" |
||||
label='Masa Berlaku KTA' |
||||
placeholder='Masa Berlaku KTA' |
||||
/> |
||||
</TouchableRipple> |
||||
{renderImages} |
||||
</ScrollView> |
||||
<View style={{ width: '100%', marginTop: 10, marginBottom: 5 }}> |
||||
<Button icon="camera-plus" style={{ borderRadius: 10, backgroundColor: colors.semiBlue, paddingVertical: 5, marginHorizontal: 5, }} textColor={colors.blue} mode="contained-tonal" onPress={handleTakePicture}> |
||||
{strings('global.addImage')} |
||||
</Button> |
||||
</View> |
||||
</View > |
||||
|
||||
<View style={styles.buttonContainer}> |
||||
<Button mode="outlined" style={styles.button} textColor={colors.mistBlue} onPress={() => { navigation.goBack() }} > |
||||
Kembali |
||||
</Button> |
||||
<Button mode="contained" style={[styles.button, { backgroundColor: colors.blue }]} onPress={() => console.log('Handle Saved')}> |
||||
Simpan |
||||
</Button> |
||||
</View> |
||||
</View > |
||||
<BottomSheetModalProvider> |
||||
<BottomSheetModal |
||||
ref={bottomSheetModal} |
||||
index={0} |
||||
snapPoints={['30%']} |
||||
bottomInset={10} |
||||
detached={true} |
||||
style={{ marginHorizontal: 15 }} |
||||
> |
||||
<BottomSheetView > |
||||
<TouchableRipple onPress={() => console.log("pagi")}> |
||||
<List.Item |
||||
title="PAGI" |
||||
left={props => <List.Icon {...props} icon="" />} |
||||
/> |
||||
</TouchableRipple> |
||||
<TouchableRipple onPress={() => console.log("siang")}> |
||||
<List.Item |
||||
title="SIANG" |
||||
left={props => <List.Icon {...props} icon="" />} |
||||
/> |
||||
</TouchableRipple> |
||||
<TouchableRipple onPress={() => console.log("malem")}> |
||||
<List.Item |
||||
title="MALEM" |
||||
left={props => <List.Icon {...props} icon="" />} |
||||
/> |
||||
</TouchableRipple> |
||||
</BottomSheetView> |
||||
</BottomSheetModal> |
||||
<BottomSheetModal |
||||
ref={bottomSheetImage} |
||||
index={0} |
||||
snapPoints={['50%']} |
||||
> |
||||
<BottomSheetView style={{ alignItems: 'center' }} > |
||||
<Image |
||||
source={{ uri: selectedImageUri }} |
||||
style={styles.imageDetail} |
||||
resizeMode="cover" |
||||
/> |
||||
</BottomSheetView> |
||||
</BottomSheetModal> |
||||
</BottomSheetModalProvider> |
||||
|
||||
{showExpDatePicker && ( |
||||
<DateTimePicker |
||||
value={expDate || new Date()} |
||||
mode="date" |
||||
display="default" |
||||
onChange={handleExpDateChange} |
||||
/> |
||||
)} |
||||
</> |
||||
) |
||||
} |
||||
|
||||
const styles = StyleSheet.create({ |
||||
container: { |
||||
flex: 1, |
||||
marginHorizontal: 10 |
||||
}, |
||||
|
||||
scrollViewContainer: { |
||||
flex: 1, |
||||
marginVertical: 10, |
||||
}, |
||||
card: { |
||||
flex: 1, |
||||
marginHorizontal: 8, |
||||
marginVertical: 5, |
||||
backgroundColor: colors.pureWhite, |
||||
}, |
||||
subText: { |
||||
fontSize: 12, |
||||
color: colors.blue |
||||
}, |
||||
button: { |
||||
flex: 1, |
||||
margin: 5, |
||||
borderRadius: 5 |
||||
}, |
||||
buttonContainer: { |
||||
flexDirection: 'row', |
||||
justifyContent: 'space-between', |
||||
padding: 10, |
||||
paddingBottom: 20, |
||||
backgroundColor: colors.pureWhite, |
||||
}, |
||||
imageBlock: { |
||||
marginTop: 10, |
||||
marginBottom: 10, |
||||
alignItems: 'center', |
||||
}, |
||||
imageContainer: { |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
}, |
||||
image: { |
||||
width: 330, |
||||
height: 150, |
||||
borderRadius: 7 |
||||
}, |
||||
imageDetail: { |
||||
width: 330, |
||||
height: 300, |
||||
borderRadius: 7 |
||||
} |
||||
}) |
Loading…
Reference in new issue