Browse Source

feat(project asset - ppe): ppe page

master
farhantock 7 months ago
parent
commit
3407859b86
  1. 458
      src/screens/activity/projectAset/ppe/dialogForm.js
  2. 68
      src/screens/activity/projectAset/ppe/index.js

458
src/screens/activity/projectAset/ppe/dialogForm.js

@ -0,0 +1,458 @@
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react';
import { StatusBar } from 'react-native';
import { Button, IconButton, Text, Appbar, TextInput, TouchableRipple, Card, List, Searchbar, RadioButton } from 'react-native-paper';
import { StyleSheet, View, ScrollView, PermissionsAndroid, Image } from 'react-native';
import Icon from 'react-native-vector-icons/AntDesign';
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 {
BottomSheetModal,
BottomSheetModalProvider,
BottomSheetView,
BottomSheetScrollView
} 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({ route, navigation }) {
const [images, setImages] = useState([]);
const [equipment, setEquipment] = useState([]);
const [selectedImageUri, setSelectedImageUri] = useState(null);
const [filter, setFilter] = useState('')
const bottomSheetModal = useRef(null);
const bottomSheetImage = useRef(null);
const [showDamageDatePicker, setShowDamageDatePicker] = useState(false);
const dummyData = [
{ name: 'HT', description: "", status: "", damageDate: "" },
{ name: 'Borgol', description: "", status: "", damageDate: "" },
{ name: 'Mobil', description: "", status: "", damageDate: "" },
{ name: 'Senter', description: "", status: "", damageDate: "" },
{ name: 'Traffic cone (stick)', description: "", status: "", damageDate: "" },
{ name: 'Laptop', description: "", status: "", damageDate: "" },
];
const handleSelectEquipment = (data) => {
setEquipment(prevEquipment => {
if (prevEquipment.some(item => item.name === data.name)) {
return prevEquipment;
}
return [...prevEquipment, data];
});
setFilter('')
bottomSheetModal.current?.dismiss();
};
const handleStatus = (data, newStatus) => {
setEquipment(prevEquipment => {
return prevEquipment.map(item => {
if (item.name === data) {
return { ...item, status: newStatus };
}
return item;
});
});
}
const handleDamageDateChange = (data, selectedDate) => {
const currentDate = selectedDate;
setShowDamageDatePicker(false);
setEquipment(prevEquipment => {
return prevEquipment.map(item => {
if (item.name === data) {
return { ...item, damageDate: currentDate };
}
return item;
});
});
};
const showDamageDatePickerFunc = () => {
setShowDamageDatePicker(true);
};
console.log("equipment", equipment);
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 handleOpenSheet = useCallback(() => {
bottomSheetModal.current?.present();
}, []);
const handleOpenSheetImage = useCallback((uri) => {
setSelectedImageUri(uri);
bottomSheetImage.current?.present();
}, []);
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}/equipment/${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]);
const renderEquipment = (data) => (
<View>
<TouchableRipple key={data.name} onPress={() => handleSelectEquipment(data)}>
<List.Item
title={data.name}
/>
</TouchableRipple>
</View>
);
const filteredData = dummyData.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
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('activity.ppe')} />
</Appbar.Header>
<View style={{ flex: 1, backgroundColor: colors.pureWhite }}>
<View style={styles.container}>
<TouchableRipple onPress={handleOpenSheet}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
editable={false}
underlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
label="Pilih Perlengkapan Personel"
mode="outlined"
/>
</TouchableRipple>
<Text variant="titleSmall" style={{ marginTop: 10, marginLeft: 10 }}>{strings('activity.ppeUse')}</Text>
<ScrollView style={[styles.scrollViewContainer, { backgroundColor: colors.pureWhite }]}>
{equipment.map(item =>
<List.Accordion
titleStyle={{ color: colors.blue }}
rippleColor={colors.semiBlue}
title={item.name}
style={styles.accordion}
>
<View style={{ marginHorizontal: 10, marginVertical: 5 }}>
<View style={{ flexDirection: 'row', marginHorizontal: 10, marginVertical: 5 }}>
<RadioButton
status={item.status === 'Baik' ? 'checked' : 'unchecked'}
onPress={() => handleStatus(item.name, 'Baik')}
color={colors.blue}
uncheckedColor={colors.amethystSmoke}
/>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => handleStatus(item.name, 'Baik')}>
<Text variant="bodySmall" style={{ color: colors.black, fontSize: 14, marginBottom: 6, marginTop: 10 }}>
Baik
</Text>
</TouchableRipple>
<RadioButton
status={item.status === 'Rusak' ? 'checked' : 'unchecked'}
onPress={() => handleStatus(item.name, 'Rusak')}
color={colors.blue}
uncheckedColor={colors.amethystSmoke}
/>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => handleStatus(item.name, 'Rusak')}>
<Text variant="bodySmall" style={{ color: colors.black, fontSize: 14, marginBottom: 6, marginTop: 10 }}>
Rusak
</Text>
</TouchableRipple>
</View>
{item.status === 'Rusak' &&
<>
<TouchableRipple rippleColor={colors.pureWhite} onPress={showDamageDatePickerFunc}>
<TextInput
value={item.damageDate ? moment(item.damageDate).format("DD-MM-YYYY") : ''}
dense={true}
editable={false}
underlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
label="Tanggal kerusakan"
mode="outlined"
/>
</TouchableRipple>
<TextInput
style={{ marginTop: 10 }}
dense={true}
underlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
label="Deskripsi"
mode="outlined"
multiline={true}
numberOfLines={5}
/>
</>
}
{showDamageDatePicker && (
<DateTimePicker
value={item.damageDate || new Date()}
mode="date"
display="default"
onChange={handleDamageDateChange(item.name)}
/>
)}
{renderImages}
</View>
</List.Accordion>
)}
</ScrollView>
<Button icon="camera-plus" style={{ borderRadius: 10, backgroundColor: colors.semiBlue, marginVertical: 10, marginHorizontal: 5, }} textColor={colors.blue} mode="contained-tonal" onPress={handleTakePicture}>
{strings('global.addImage')}
</Button>
</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={['50%']}
bottomInset={10}
detached={true}
keyboardBehavior="fillParent"
style={{ marginHorizontal: 15 }}
>
<Searchbar
mode='bar'
placeholder="Cari Peralatan"
onChangeText={text => setFilter(text)}
value={filter}
traileringIconColor={colors.white}
style={{ backgroundColor: colors.white }}
showDivider={true}
elevation={1}
/>
<BottomSheetScrollView contentContainerStyle={styles.scrollView}>
{filteredData.map(item => renderEquipment(item))}
</BottomSheetScrollView>
</BottomSheetModal>
<BottomSheetModal
ref={bottomSheetImage}
index={0}
snapPoints={['50%']}
>
<BottomSheetView style={{ alignItems: 'center' }} >
<Image
source={{ uri: selectedImageUri }}
style={styles.imageDetail}
resizeMode="cover"
/>
</BottomSheetView>
</BottomSheetModal>
</BottomSheetModalProvider>
</>
)
}
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: {
marginVertical: 10,
alignItems: 'center',
},
imageContainer: {
flexDirection: 'row',
alignItems: 'center',
},
image: {
width: 330,
height: 150,
borderRadius: 7
},
imageDetail: {
width: 330,
height: 300,
borderRadius: 7
},
accordion: {
backgroundColor: colors.semiBlue,
color: colors.blue,
marginHorizontal: 10,
marginTop: 10,
borderRadius: 5
},
})

68
src/screens/activity/projectAset/ppe/index.js

@ -0,0 +1,68 @@
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react';
import { Card, Text, TouchableRipple, Button, Appbar } from 'react-native-paper';
import { RefreshControl, StyleSheet, View, ScrollView, StatusBar } from 'react-native';
import { colors } from '../../../../utils/color';
import Icon from 'react-native-vector-icons/AntDesign';
import { strings } from '../../../../utils/i18n';
import moment from 'moment';
export default function PPEScreen({ route, navigation }) {
const data = [
{ id: 1, type: "Gada Pratama", position: "Manager", date: "Senin, 02-05-2024", startTime: "21:03", title: "Pengawalan", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean non elit ut leo malesuada porttitor.", visitor: "Farhan" },
{ id: 2, type: "Gada Utama", position: "Danru", date: "Selasa, 03-05-2024", startTime: "10:15", title: "Investigasi", description: "Morbi suscipit massa mollis porttitor fringilla. Integer rutrum ipsum lorem, ut convallis nisl consequat nec. Proin vel elit vitae sapien convallis molestie. Donec id feugiat lectus. Aenean ut dui ut mi semper facilisis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam luctus convallis tellus, quis malesuada felis viverra mattis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam erat volutpat. Mauris eu posuere sapien. Aliquam in nisi at tortor faucibus interdum eget non quam. Aenean non elit ut leo malesuada porttitor.", visitor: "Ibnu" },
{ id: 3, type: "Latihan Tongkat", position: "BKO Kepolisian", date: "Rabu, 04-05-2024", startTime: "15:20", title: "Sidang Pengadilan", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean non elit ut leo malesuada porttitor.", visitor: "Khaidir" },
];
return (
<View style={styles.container}>
<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('activity.ppe')} />
</Appbar.Header>
<View style={{ width: '100%', marginTop: 10, marginBottom: 5 }}>
<Button icon="plus" style={{ borderRadius: 10, backgroundColor: colors.semiBlue, marginHorizontal: 5, }} textColor={colors.blue} mode="contained-tonal" onPress={() => { navigation.navigate('DialogFormPPE') }}>
{strings('activity.addPpe')}
</Button>
</View>
<ScrollView style={{ backgroundColor: colors.pureWhite }}>
{data.map((item) => (
<TouchableRipple onPress={() => { navigation.navigate('DialogFormPPE') }}>
<Card style={styles.card}>
<Card.Cover source={{ uri: 'https://picsum.photos/700' }} style={{ height: 130 }} resizeMode='cover' />
<Card.Content style={{ marginTop: 10 }}>
<Text variant="bodyMedium" style={{ color: colors.amethystSmoke }}>{item.date}</Text>
<Text variant="titleLarge" style={{ color: colors.black, fontWeight: '695' }}>{strings('activity.ppe')}</Text>
</Card.Content>
</Card>
</TouchableRipple>
))}
</ScrollView>
</View >
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
backgroundColor: colors.pureWhite
},
card: {
flex: 1,
marginTop: 20,
marginHorizontal: 8,
marginVertical: 5,
backgroundColor: colors.pureWhite,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 5,
},
shift: {
backgroundColor: colors.semiBlue,
borderRadius: 10,
padding: 5,
paddingHorizontal: 10
},
})
Loading…
Cancel
Save