farhantock
5 months ago
3 changed files with 859 additions and 0 deletions
@ -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) => ( |
||||||
|
<View> |
||||||
|
<TouchableRipple key={data.location_name} onPress={() => handleSelectArea(data)}> |
||||||
|
<List.Item |
||||||
|
title={data.location_name} |
||||||
|
/> |
||||||
|
</TouchableRipple> |
||||||
|
</View> |
||||||
|
); |
||||||
|
|
||||||
|
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('patrol.list')} /> |
||||||
|
</Appbar.Header> |
||||||
|
<View style={styles.container}> |
||||||
|
<TouchableRipple onPress={handleOpenSheet}> |
||||||
|
<TextInput |
||||||
|
style={{ marginTop: 10, marginBottom: 15, marginHorizontal: 10 }} |
||||||
|
dense={true} |
||||||
|
editable={false} |
||||||
|
value={area} |
||||||
|
outlineColor={colors.amethystSmoke} |
||||||
|
activeOutlineColor={colors.blue} |
||||||
|
mode="outlined" |
||||||
|
label='Area Patroli' |
||||||
|
placeholder='Area Patroli' |
||||||
|
/> |
||||||
|
</TouchableRipple> |
||||||
|
|
||||||
|
<ScrollView style={[styles.scrollViewContainer, { backgroundColor: colors.pureWhite }]}> |
||||||
|
{children.map((item) => ( |
||||||
|
<TouchableRipple onPress={() => { navigation.navigate('DialogFormReport') }} rippleColor={colors.pureWhite}> |
||||||
|
<Card style={styles.card}> |
||||||
|
<Card.Cover source={{ uri: 'https://picsum.photos/700' }} /> |
||||||
|
<Card.Content style={{ marginTop: 10 }}> |
||||||
|
<Text variant="titleLarge" style={{ color: colors.black }}>{item.location_name}</Text> |
||||||
|
</Card.Content> |
||||||
|
</Card> |
||||||
|
</TouchableRipple> |
||||||
|
))} |
||||||
|
</ScrollView> |
||||||
|
|
||||||
|
</View> |
||||||
|
|
||||||
|
<BottomSheetModalProvider> |
||||||
|
<BottomSheetModal |
||||||
|
ref={bottomSheetModal} |
||||||
|
index={1} |
||||||
|
snapPoints={snapPoints} |
||||||
|
> |
||||||
|
<BottomSheetScrollView contentContainerStyle={styles.scrollView}> |
||||||
|
{data.map(item => renderArea(item))} |
||||||
|
</BottomSheetScrollView> |
||||||
|
</BottomSheetModal> |
||||||
|
</BottomSheetModalProvider> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
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, |
||||||
|
}, |
||||||
|
}) |
@ -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 ( |
||||||
|
<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('patrol.title')} /> |
||||||
|
</Appbar.Header> |
||||||
|
|
||||||
|
<View style={{ width: '100%', marginTop: 10, marginBottom: 5 }}> |
||||||
|
<Button icon="plus" style={{ borderRadius: 10, backgroundColor: colors.semiBlue, marginHorizontal: 8, }} textColor={colors.blue} mode="contained-tonal" onPress={() => { navigation.navigate('DialogFormPatroli') }}> |
||||||
|
{strings('patrol.add')} |
||||||
|
</Button> |
||||||
|
</View> |
||||||
|
|
||||||
|
<ScrollView style={{ backgroundColor: colors.pureWhite }}> |
||||||
|
{data.map((item) => ( |
||||||
|
<TouchableRipple onPress={() => { navigation.navigate('DialogFormPatroli') }} rippleColor={colors.pureWhite}> |
||||||
|
<Card style={styles.card}> |
||||||
|
<Card.Cover source={{ uri: 'https://picsum.photos/700' }} /> |
||||||
|
<Card.Content style={{ marginTop: 10 }}> |
||||||
|
<Text variant="bodyMedium" style={{ color: colors.amethystSmoke }}>{item.date} {item.startTime} - {item.endTime}</Text> |
||||||
|
<Text variant="titleLarge" style={{ color: colors.black }}>{item.title}</Text> |
||||||
|
<Text variant="bodyMedium" style={{ color: colors.amethystSmoke }}>{item.description}</Text> |
||||||
|
</Card.Content> |
||||||
|
</Card> |
||||||
|
</TouchableRipple> |
||||||
|
))} |
||||||
|
</ScrollView> |
||||||
|
</View > |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
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, |
||||||
|
}, |
||||||
|
|
||||||
|
}) |
@ -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) => ( |
||||||
|
<View key={index} style={styles.imageBlock}> |
||||||
|
<View style={styles.imageContainer}> |
||||||
|
<Image |
||||||
|
source={{ uri: image.uri }} |
||||||
|
style={styles.image} |
||||||
|
resizeMode="cover" |
||||||
|
/> |
||||||
|
<IconButton |
||||||
|
iconColor={colors.beanRed} |
||||||
|
icon="delete" |
||||||
|
style={styles.deleteButton} |
||||||
|
size={30} |
||||||
|
onPress={() => handleDeleteImage(index)} |
||||||
|
/> |
||||||
|
</View> |
||||||
|
<TextInput |
||||||
|
mode='outlined' |
||||||
|
style={styles.descriptionInput} |
||||||
|
outlineColor={colors.amethystSmoke} |
||||||
|
activeOutlineColor={colors.blue} |
||||||
|
placeholder="Deskripsi gambar" |
||||||
|
label="Deskripsi gambar" |
||||||
|
multiline={true} |
||||||
|
value={image.description} |
||||||
|
onChangeText={(text) => { |
||||||
|
const updatedImages = [...images]; |
||||||
|
updatedImages[index].description = text; |
||||||
|
setImages(updatedImages); |
||||||
|
}} |
||||||
|
/> |
||||||
|
</View> |
||||||
|
)), [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('patrol.add')} /> |
||||||
|
</Appbar.Header> |
||||||
|
<View style={styles.container}> |
||||||
|
<Button |
||||||
|
icon="plus" |
||||||
|
style={styles.addButton} |
||||||
|
mode="contained-tonal" |
||||||
|
onPress={handleTakePicture} |
||||||
|
textColor={colors.blue} |
||||||
|
> |
||||||
|
Tambah Gambar |
||||||
|
</Button> |
||||||
|
<ScrollView> |
||||||
|
<View style={styles.scrollViewContainer}> |
||||||
|
{renderImages} |
||||||
|
</View> |
||||||
|
</ScrollView> |
||||||
|
<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> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
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, |
||||||
|
}, |
||||||
|
}) |
Loading…
Reference in new issue