diff --git a/src/screens/patroli/dialogForm.js b/src/screens/patroli/dialogForm.js new file mode 100644 index 0000000..fac5c8a --- /dev/null +++ b/src/screens/patroli/dialogForm.js @@ -0,0 +1,506 @@ +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 { + BottomSheetModal, + BottomSheetModalProvider, + BottomSheetScrollView +} from '@gorhom/bottom-sheet'; +import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; +export default function DialogForm({ route, navigation }) { + + const data = [ + { + "location_name": "Depo MRT Lebak Bulus PT MRT Jakarta (Perseroda), ", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 0, + "children": [ + { + "location_name": "Pos TImur", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 1, + "day_night_2": 1, + "day_night_do": 2, + "total": 4 + }, + { + "location_name": "Riksa Ran", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 2, + "day_night_2": 2, + "day_night_do": 4, + "total": 8 + }, + { + "location_name": "Pos Gd A", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 2, + "day_night_2": 1, + "day_night_do": 5, + "total": 8 + }, + { + "location_name": "Pos Gd B", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 1, + "day_night_2": 1, + "day_night_do": 1, + "total": 3 + }, + { + "location_name": "Pos Gd C", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 1, + "day_night_2": 1, + "day_night_do": 1, + "total": 3 + }, + { + "location_name": "Pos JPO", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 1, + "day_night_2": 2, + "day_night_do": 1, + "total": 4 + }, + { + "location_name": "Pos Barat", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 1, + "day_night_2": 1, + "day_night_do": 1, + "total": 3 + }, + { + "location_name": "Patroli", + "location_lat": -6.290149705592755, + "location_lon": 106.77577068583955, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 1, + "day_night_2": 1, + "day_night_do": 1, + "total": 3 + } + ] + }, + { + "location_name": "Receiving Sub Station (RSS) Jl. Taman Sambas", + "location_lat": -6.248884920623913, + "location_lon": 106.79742903090003, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 0, + "children": [ + { + "location_name": "Pos RSS Sambas", + "location_lat": -6.248884920623913, + "location_lon": 106.79742903090003, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 2, + "day_night_2": 2, + "day_night_do": 4, + "total": 8 + } + ] + }, + { + "location_name": "Area transisi antara stasiun layang dan bawah tanah", + "location_lat": -6.234288689010968, + "location_lon": 106.7984383166707, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 0, + "children": [ + { + "location_name": "Pos Transisi", + "location_lat": -6.234288689010968, + "location_lon": 106.7984383166707, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 2, + "day_night_2": 2, + "day_night_do": 4, + "total": 8 + } + ] + }, + { + "location_name": "Ratangga PT MRT Jakarta (Perseroda), Gedung Wisma Nusantara lantai 21, Jalan M.H. Thamrin Kavling 59, Jakarta Pusat 10350", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 0, + "children": [ + { + "location_name": "Ratangga 1", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga 2", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga 3", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga 4", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga 5", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga 6", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga 7", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 0, + "office_hour_1": 2, + "office_hour_2": 2, + "office_hour_3": 0, + "office_hour_do": 2, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 6 + }, + { + "location_name": "Ratangga Pick Hours", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 14, + "office_hour_1": 0, + "office_hour_2": 0, + "office_hour_3": 0, + "office_hour_do": 0, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 14 + }, + { + "location_name": "Danru Walka", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 1, + "office_hour_1": 1, + "office_hour_2": 1, + "office_hour_3": 0, + "office_hour_do": 1, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 4 + }, + { + "location_name": "Wa Danru Walka", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 1, + "office_hour_1": 0, + "office_hour_2": 1, + "office_hour_3": 0, + "office_hour_do": 1, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 3 + }, + { + "location_name": "Patroli Walka ", + "location_lat": -6.193848456812861, + "location_lon": 106.82352714314999, + "office_hour_ns": 1, + "office_hour_1": 1, + "office_hour_2": 1, + "office_hour_3": 0, + "office_hour_do": 1, + "day_night_ns": 0, + "day_night_1": 0, + "day_night_2": 0, + "day_night_do": 0, + "total": 4 + } + ] + } + ] + const snapPoints = useMemo(() => ['25%', '40%'], []); + const bottomSheetModal = useRef(null); + const [area, setArea] = useState(''); + const [children, setChildren] = useState([]); + + const handleOpenSheet = useCallback(() => { + bottomSheetModal.current?.present(); + }, []); + + const handleSelectArea = (area) => { + setArea(area.location_name) + setChildren(area.children || []); + bottomSheetModal.current?.dismiss(); + }; + + const renderArea = (data) => ( + + handleSelectArea(data)}> + + + + ); + + return ( + <> + + + { navigation.goBack() }} /> + + + + + + + + + {children.map((item) => ( + { navigation.navigate('DialogFormReport') }} rippleColor={colors.pureWhite}> + + + + {item.location_name} + + + + ))} + + + + + + + + {data.map(item => renderArea(item))} + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.pureWhite + }, + + scrollViewContainer: { + flex: 1, + marginVertical: 10, + marginHorizontal: 10, + }, + card: { + flex: 1, + marginHorizontal: 8, + marginVertical: 5, + backgroundColor: colors.pureWhite, + }, + subText: { + fontSize: 12, + color: colors.blue + }, + cardContent: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 10, + }, +}) \ No newline at end of file diff --git a/src/screens/patroli/index.js b/src/screens/patroli/index.js new file mode 100644 index 0000000..ca145a0 --- /dev/null +++ b/src/screens/patroli/index.js @@ -0,0 +1,93 @@ +import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'; +import { Card, Text, Avatar, useTheme, IconButton, Appbar, Button, TouchableRipple } 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'; + +export default function PatroliScreen({ route, navigation }) { + const data = [ + { id: 1, date: "Senin, 02-05-2024", startTime: "21:03", endTime: "21:03", title: "Patroli Area 1", description: "Melakukan Patroli pada Area 1", }, + { id: 2, date: "Selasa, 03-05-2024", startTime: "10:15", endTime: "10:15", title: "Patroli Area 2", description: "Melakukan Patroli pada Area 2", }, + { id: 3, date: "Rabu, 04-05-2024", startTime: "15:20", endTime: "15:20", title: "Patroli Area 3", description: "Melakukan Patroli pada Area 3", }, + { id: 4, date: "Kamis, 05-05-2024", startTime: "18:30", endTime: "18:30", title: "Patroli Area 4", description: "Melakukan Patroli pada Area 4", }, + { id: 5, date: "Jumat, 06-05-2024", startTime: "08:00", endTime: "08:00", title: "Patroli Area 5", description: "Melakukan Patroli pada Area 5", }, + { id: 5, date: "Sabtu, 07-05-2024", startTime: "08:00", endTime: "08:00", title: "Patroli Area 6", description: "Melakukan Patroli pada Area 6", }, + { id: 5, date: "Minggu, 08-05-2024", startTime: "08:00", endTime: "08:00", title: "Patroli Area 7", description: "Melakukan Patroli pada Area 7", } + ]; + return ( + + + + { navigation.goBack() }} /> + + + + + + + + + {data.map((item) => ( + { navigation.navigate('DialogFormPatroli') }} rippleColor={colors.pureWhite}> + + + + {item.date} {item.startTime} - {item.endTime} + {item.title} + {item.description} + + + + ))} + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + marginTop: 20, + backgroundColor: colors.pureWhite + }, + Text: { + fontSize: 16, + fontWeight: 'bold', + color: colors.blue + }, + card: { + flex: 1, + marginHorizontal: 8, + marginVertical: 5, + backgroundColor: colors.pureWhite, + }, + subText: { + fontSize: 12, + color: colors.blue + }, + cardContent: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 10, + }, + button: { + flex: 1, + margin: 5, + borderRadius: 5 + }, + row: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 5, + }, + buttonContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + padding: 10, + paddingBottom: 20, + }, + +}) \ No newline at end of file diff --git a/src/screens/patroli/report.js b/src/screens/patroli/report.js new file mode 100644 index 0000000..c219acb --- /dev/null +++ b/src/screens/patroli/report.js @@ -0,0 +1,260 @@ +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 ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; +export default function DialogFormReport({ route, navigation }) { + const [images, setImages] = useState([]); + + 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); + } + bottomSheetModal.current?.dismiss(); + }; + + 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}/patroli/${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) => ( + + + + handleDeleteImage(index)} + /> + + { + const updatedImages = [...images]; + updatedImages[index].description = text; + setImages(updatedImages); + }} + /> + + )), [images]); + + + return ( + <> + + + { navigation.goBack() }} /> + + + + + + + {renderImages} + + + + + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.pureWhite + }, + addButton: { + marginVertical: 5, + marginHorizontal: 5, + borderRadius: 10, + backgroundColor: colors.semiBlue, + }, + imageBlock: { + marginBottom: 10, + flexDirection: 'row', + }, + imageContainer: { + flexDirection: 'column', + alignItems: 'center', + }, + image: { + width: 100, + height: 100, + borderRadius: 7 + }, + descriptionInput: { + flex: 1 + }, + scrollViewContainer: { + flex: 1, + marginVertical: 10, + marginHorizontal: 10, + }, + button: { + flex: 1, + margin: 5, + borderRadius: 5 + }, + row: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 5, + }, + buttonContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + padding: 10, + paddingBottom: 20, + }, +}) \ No newline at end of file