diff --git a/src/screens/incident-page/dialogForm.js b/src/screens/incident-page/dialogForm.js index 81be85d..7cfa13a 100644 --- a/src/screens/incident-page/dialogForm.js +++ b/src/screens/incident-page/dialogForm.js @@ -1,6 +1,6 @@ import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'; -import { StatusBar } from 'react-native'; -import { Button, Text, Appbar, } from 'react-native-paper'; +import { BackHandler, StatusBar } from 'react-native'; +import { Button, Dialog, Appbar, useTheme, Text, } from 'react-native-paper'; import { StyleSheet, View, } from 'react-native'; import { colors } from '../../utils/color'; import { strings } from '../../utils/i18n'; @@ -11,10 +11,97 @@ import ReportScreen from './stepComponent/report'; import IncidentScreen from './stepComponent/incident'; import ChronologyScreen from './stepComponent/chronology'; import MediaScreen from './stepComponent/media'; - +import { useFocusEffect } from '@react-navigation/native'; +import { resetIncidentReport } from '../../appredux/actions'; +import { useDispatch } from 'react-redux'; +import { + setIncidentDate, setIncidentTime, setReportDate, setReportTime, setDataRecepient, setDataReporter, + setArea, setIncidentLocation, setReportLocation, setDataIncident, setDataAchievement, setDataTypeRisk, + setIncidentRiskPotential, setRiskLevel, setChronology, setRootCase, setDataPrevention +} from '../../appredux/modules/incidentReport/actions'; export default function DialogForm({ route, navigation }) { + const theme = useTheme() + const dispatch = useDispatch() + const isDarkTheme = theme.dark const [active, setActive] = useState(0); - const labels = ["Waktu", "Lokasi", "Pelapor", "Kejadian", "Kronologis", "Media"]; + const [visible, setVisible] = useState(false); + + const showDialog = () => setVisible(true); + const hideDialog = () => setVisible(false); + const labels = [strings('incidentReport.timeStep'), strings('incidentReport.locationStep'), strings('incidentReport.reportStep'), strings("incidentReport.incidentStep"), strings('incidentReport.chronologyStep'), "Media"]; + + useEffect(() => { + if (route.params?.item) { + const { item } = route.params; + console.log("item.incidentDate", item.info); + dispatch(setIncidentDate(item.info.incidentDate)); + dispatch(setReportDate(item.info.reportDate)); + dispatch(setDataRecepient(item.info.dataRecipient)); + dispatch(setDataReporter(item.info.dataReporter)); + dispatch(setArea(item.info.areaType)); + dispatch(setIncidentLocation(item.info.incidentLocation)); + dispatch(setReportLocation(item.info.reportLocation)); + dispatch(setDataIncident(item.info.incidentList)); + dispatch(setDataAchievement(item.info.achievement)); + dispatch(setIncidentRiskPotential(item.info.incidentRiskPotential)); + dispatch(setRiskLevel(item.info.riskLevel)); + dispatch(setChronology(item.info.chronology)); + dispatch(setRootCase(item.info.rootCase)); + dispatch(setDataPrevention(item.info.prevention)); + dispatch(setDataTypeRisk(item.info.typeRiskIncident)); + } + }, [route.params?.item]); + + + useFocusEffect( + useCallback(() => { + const onBackPress = () => { + if (active === 0) { + showDialog(); + return true; // prevent default behavior (navigation back) + } else { + setActive(prevActive => prevActive - 1); + return true; + } + }; + + BackHandler.addEventListener('hardwareBackPress', onBackPress); + + return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress); + }, [active]) + ); + + + const handleSendIncidentReport = async () => { + const payload = { + project_charter_id: projectCharter, + info: { + achievement: dataAchievment, + incidentList: dataIncidentReport, + incidentDate: incidentDate, + reportDate: reportDate, + incidentLocation: incidentLocation, + reportLocation: reportLocation, + prevention: dataPrevention, + incidentRiskPotential: incidentRiskPotential, + areaType: area, + chronology: chronology, + rootCase: rootCase, + riskLevel: riskLevel, + supervisor: supervisor, + reporter: reporter, + typeRiskIncident: typeRiskIncident, + date: date, + dataReporter: dataReporter, + dataRecipient: dataRecipient, + etc: otherText, + etcAchievment: otherTextAchievment + }, + status: statusReport + }; + + } + const renderStepContent = useMemo(() => { switch (active) { @@ -47,13 +134,13 @@ export default function DialogForm({ route, navigation }) { } }; return ( - + - { navigation.goBack() }} /> + - + {active === labels.length - 1 ? ( - ) : ( - )} + + {strings('global.confirm')} + + {strings('global.backMessage')} + {strings('global.message')} + + + + + ) } @@ -90,7 +194,6 @@ const styles = StyleSheet.create({ container: { flex: 1, marginTop: 20, - backgroundColor: colors.pureWhite }, cardView: { marginTop: 10, @@ -114,7 +217,14 @@ const styles = StyleSheet.create({ padding: 10, paddingBottom: 20, }, - + dialog: { + marginVertical: 20, + padding: 20, + marginHorizontal: 20, + borderRadius: 10, + justifyContent: "center", + alignItems: "center" + } }) const stepIndicatorStyles = { @@ -133,12 +243,12 @@ const stepIndicatorStyles = { stepIndicatorFinishedColor: colors.beanRed, stepIndicatorUnFinishedColor: colors.semiRed, stepIndicatorCurrentColor: colors.beanRed, - stepIndicatorLabelFontSize: 12, - currentStepIndicatorLabelFontSize: 12, + stepIndicatorLabelFontSize: 11, + currentStepIndicatorLabelFontSize: 11, stepIndicatorLabelCurrentColor: colors.pureWhite, stepIndicatorLabelFinishedColor: colors.pureWhite, stepIndicatorLabelUnFinishedColor: colors.beanRed, labelColor: colors.semiRed, - labelSize: 12, + labelSize: 11, currentStepLabelColor: colors.beanRed } \ No newline at end of file diff --git a/src/screens/incident-page/index.js b/src/screens/incident-page/index.js index 1654132..34d1f56 100644 --- a/src/screens/incident-page/index.js +++ b/src/screens/incident-page/index.js @@ -4,24 +4,77 @@ import { RefreshControl, StyleSheet, View, ScrollView, Image, Dimensions, Status import { colors } from '../../utils/color'; import Icon from 'react-native-vector-icons/AntDesign'; import { strings } from '../../utils/i18n'; +import RequestModule from '../../services/api/request'; +import moment from 'moment'; +import 'moment/locale/id'; +import { useSelector } from 'react-redux'; +moment.locale('id'); +const PAGE_SIZE = 25; export default function IncidentScreen({ route, navigation }) { - const incidentData = [ - { id: 1, date: "Senin, 02-05-2024 21:03", location: "Kangean Site Indonesia", company: "Kangean Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "KEI2301M" }, - { id: 2, date: "Selasa, 03-05-2024 10:15", location: "Sumatra Site Indonesia", company: "Sumatra Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "SEI2402M" }, - { id: 3, date: "Rabu, 04-05-2024 15:20", location: "Java Site Indonesia", company: "Java Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "JEI2503M" }, - { id: 4, date: "Kamis, 05-05-2024 18:30", location: "Bali Site Indonesia", company: "Bali Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "BEI2604M" }, - { id: 5, date: "Jumat, 06-05-2024 08:00", location: "Sulawesi Site Indonesia", company: "Sulawesi Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "SEI2705M" }, - { id: 5, date: "Sabtu, 07-05-2024 08:00", location: "Sulawesi Site Indonesia", company: "Sulawesi Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "SEI2705M" }, - { id: 5, date: "Minggu, 08-05-2024 08:00", location: "Sulawesi Site Indonesia", company: "Sulawesi Energy Indonesia, BUT. Ltd.", icn: "KEI2301M", sicn: "SEI2705M" } - ]; + const theme = useTheme(); + const isDarkTheme = theme.dark; + const { user } = useSelector(state => state.userReducer); + const request = new RequestModule('incident-report'); + const [refreshing, setRefreshing] = useState(false); + const [page, setPage] = useState(0); + const [dataIncident, setDataIncident] = useState([]); + const [loading, setLoading] = useState(false); + + const onRefresh = useCallback(() => { + setRefreshing(true); + setPage(0); + getIncidentData(0); + }, []); + + useEffect(() => { + getIncidentData(page); + }, [page]); + + const getIncidentData = async (pageNum) => { + if (loading) return; + setLoading(true); + const payload = { + paging: { start: pageNum * PAGE_SIZE, length: PAGE_SIZE }, + joins: [ + { name: 'project_charter', column_join: 'project_charter_id', column_results: ['sicn', 'project_name', 'icn', 'regional', 'client_name'] }, + { name: "m_regional", column_join: "id", references: "project_charter.regional", column_results: ['name'] }, + ], + columns: [ + { name: 'project_charter_id', logic_operator: '=', value: user?.assigment_hr?.project_charter_id.toString(), operator: 'AND' }, + { name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' }, + ], + orders: { columns: ['created_at', 'id'], ascending: false }, + }; + const result = await request.getDataSearch(payload); + setDataIncident((prevData) => + pageNum === 0 ? result.data.data : [...prevData, ...result.data.data] + ); + setPage(pageNum + 1); + setRefreshing(false); + setLoading(false); + }; + + const handleScroll = ({ nativeEvent }) => { + if (isCloseToBottom(nativeEvent)) { + getPresenceHistory(page); + } + }; + + const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => { + return layoutMeasurement.height + contentOffset.y >= contentSize.height - 20; + }; + + const handleEdit = (item) => { + navigation.navigate('DialogFormIncident', { item }); + }; return ( - - - + + + { navigation.goBack() }} /> - + @@ -29,23 +82,34 @@ export default function IncidentScreen({ route, navigation }) { {strings('incidentReport.add')} - - - {incidentData.map((incident) => ( - { navigation.navigate('DialogFormIncident') }}> - + + } + onScroll={handleScroll} + scrollEventThrottle={400} + > + {dataIncident.map((incident) => ( + handleEdit(incident)}> + - + - {incident.date} - {incident.location} - {incident.company} - {incident.icn} - {incident.sicn} + {moment(incident.created_at).format('dddd, DD-MM-YYYY HH:mm')} + {incident.join.project_charter_project_name} + {incident.join.project_charter_client_name} + {incident.join.project_charter_icn} - {incident.join.project_charter_sicn} - + @@ -60,22 +124,19 @@ 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', diff --git a/src/screens/incident-page/stepComponent/chronology.js b/src/screens/incident-page/stepComponent/chronology.js index de962d1..69f6294 100644 --- a/src/screens/incident-page/stepComponent/chronology.js +++ b/src/screens/incident-page/stepComponent/chronology.js @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; -import { List, TouchableRipple, TextInput, Card, Text, Button } from 'react-native-paper'; -import { View, StyleSheet, ScrollView } from 'react-native'; +import { List, TouchableRipple, TextInput, Card, Text, Button, useTheme } from 'react-native-paper'; +import { View, StyleSheet, ScrollView, useWindowDimensions } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import moment from 'moment'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; @@ -11,12 +11,26 @@ import { BottomSheetScrollView } from '@gorhom/bottom-sheet'; import { strings } from '../../../utils/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import { RichEditor, RichToolbar, actions } from 'react-native-pell-rich-editor'; +import { setIncidentRiskPotential, setRiskLevel, setChronology, setRootCase, setDataPrevention } from '../../../appredux/actions'; + export default function ChronologyScreen({ route, navigation }) { - const [chronology, setChronology] = useState('') - const [incidentRiskPotential, setIncidentRiskPotential] = useState({ impact: "", likelihood: "", value: "", impactDescription: "", likelihoodDescription: "" }) - const [riskLevel, setRiskLevel] = useState({ level: "", backgroundColor: '', color: '' }) + const theme = useTheme(); + const isDarkTheme = theme.dark; + const dispatch = useDispatch(); + const colorsheet = isDarkTheme ? theme.colors.black : theme.colors.pureWhite; + const typeRiskIncident = useSelector(state => state.incidentReportReducer.typeRiskIncident); + const riskLevel = useSelector(state => state.incidentReportReducer.riskLevel); + const incidentRiskPotential = useSelector(state => state.incidentReportReducer.incidentRiskPotential); + const chronology = useSelector(state => state.incidentReportReducer.chronology); + const rootCase = useSelector(state => state.incidentReportReducer.rootCase); + const dataPrevention = useSelector(state => state.incidentReportReducer.dataPrevention); const bottomSheetModalImpactRef = useRef(null); const bottomSheetModalLikelihoodRef = useRef(null); + const chronologyText = useRef(); + const rootcaseText = useRef(); + const snapPoints = useMemo(() => ['25%', '50%'], []); @@ -28,6 +42,20 @@ export default function ChronologyScreen({ route, navigation }) { bottomSheetModalLikelihoodRef.current?.present(); }, []); + // useEffect(() => { + // if (route.params?.isSaved) { + // Toast.show({ type: 'success', text1: 'Berhasil di simpan' }); // Show success toast + // } + // }, [route.params?.isSaved]); + + const handleEdit = (item) => { + navigation.navigate('ContainedActionScreen', { item }); + }; + + const handleDelete = (id) => { + const updatedData = dataPrevention.filter(prevention => prevention.id !== id); + dispatch(setDataPrevention(updatedData)); + }; useEffect(() => { if (incidentRiskPotential.impact !== "" && incidentRiskPotential.likelihood !== "") { @@ -37,48 +65,39 @@ export default function ChronologyScreen({ route, navigation }) { const val = impactValue * likelihoodValue; let newRiskLevel, backgroundColor, color; if (val >= 1 && val <= 5) { - backgroundColor = '#E3F8E8' - color = '#17C13E' + backgroundColor = colors.semigreen + color = colors.green newRiskLevel = 'Rendah'; } else if (val >= 6 && val <= 10) { - backgroundColor = "#F8DC49" + backgroundColor = colors.yellow color = "white" newRiskLevel = 'Sedang'; } else if (val >= 11 && val <= 15) { - backgroundColor = '#FFD9AF' - color = '#EC9C3D' + backgroundColor = colors.semiRed + color = colors.beanRed newRiskLevel = 'Tinggi'; } else { - backgroundColor = '#FFC5C3' - color = '#D9534F' + backgroundColor = colors.red + color = colors.white newRiskLevel = 'Bencana'; } if (incidentRiskPotential.value !== val.toString() || riskLevel.level !== newRiskLevel || riskLevel.backgroundColor !== backgroundColor || riskLevel.color !== color) { - setIncidentRiskPotential(prevState => ({ ...prevState, value: val.toString() })); - setRiskLevel({ level: newRiskLevel, backgroundColor: backgroundColor, color: color }); + dispatch(setIncidentRiskPotential({ ...incidentRiskPotential, value: val.toString() })); + dispatch(setRiskLevel({ level: newRiskLevel, backgroundColor: backgroundColor, color: color })); } } - }, [incidentRiskPotential, riskLevel]); + }, [typeRiskIncident, incidentRiskPotential, riskLevel]); const handleSelectImpact = (impact) => { - setIncidentRiskPotential(prevState => ({ - ...prevState, - impact: impact.label, - value: impact.value, - impactDescription: impact.description - })); + dispatch(setIncidentRiskPotential({ ...incidentRiskPotential, impact: impact.label, value: impact.value, impactDescription: impact.description })); bottomSheetModalImpactRef.current?.dismiss(); }; const handleSelectLikelihood = (likelihood) => { - setIncidentRiskPotential(prevState => ({ - ...prevState, - likelihood: likelihood.label, - likelihoodDescription: likelihood.description - })); + dispatch(setIncidentRiskPotential({ ...incidentRiskPotential, likelihood: likelihood.label, likelihoodDescription: likelihood.description })); bottomSheetModalLikelihoodRef.current?.dismiss(); }; @@ -118,6 +137,7 @@ export default function ChronologyScreen({ route, navigation }) { handleSelectImpact(data)}> @@ -127,54 +147,77 @@ export default function ChronologyScreen({ route, navigation }) { handleSelectLikelihood(data)}> ); + + return ( <> - + Kronologis kejadian - + Jelaskan bagaimana terjadinya insiden - { - setChronology(text) + + Kronologis Singkat Kejadian + + { + dispatch(setChronology(text)); }} /> - - { - setChronology(text) + + + Penjelasan Singkat Kejadian + + { + dispatch(setRootCase(text)); }} /> + + + {incidentRiskPotential.impactDescription && ( - - + + {incidentRiskPotential.impactDescription} @@ -209,50 +252,54 @@ export default function ChronologyScreen({ route, navigation }) { {incidentRiskPotential.likelihoodDescription && ( - - + + {incidentRiskPotential.likelihoodDescription} )} - - - Critical - - - - - {riskLevel.level} {incidentRiskPotential.value} - - + {typeRiskIncident && + + + {typeRiskIncident} + + + } + {incidentRiskPotential.value && riskLevel.level && + + + {riskLevel.level} {incidentRiskPotential.value} + + + } - + Rencana tindakan pencegahan - - - - - + {dataPrevention.map(item => ( + + + + + + + {moment(item.date).format('DD-MM-YYYY')} + {item.status} + {item.who} + {item.plan} + - - 01-03-2024 - Farhan - Melakukan pengamatan dan patroli area pagar luar Depo MRT + + + - - - - - - - - {/* color: (typeRiskIncident === 'Critical' ? '#D9534F' : (typeRiskIncident === 'Major' ? '#EC9C3D' : 'black')) */} + + ))} - - { showIncidentDatePicker && ( ) } - - + handleStatus("Open")}> } /> handleStatus("Close")}> } /> - ) + ); } const styles = StyleSheet.create({ diff --git a/src/screens/incident-page/stepComponent/incident.js b/src/screens/incident-page/stepComponent/incident.js index bb3df9c..28b8395 100644 --- a/src/screens/incident-page/stepComponent/incident.js +++ b/src/screens/incident-page/stepComponent/incident.js @@ -1,63 +1,193 @@ -import React, { useState } from 'react'; -import { TouchableRipple, Chip, Card, Text } from 'react-native-paper'; +import React, { useCallback, useRef, useState } from 'react'; +import { TouchableRipple, Chip, Card, Text, useTheme, Searchbar, List, Button } from 'react-native-paper'; import { View, StyleSheet, ScrollView } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import moment from 'moment'; import { colors } from '../../../utils/color'; import { jenisKejadian } from '../../../config/ApiConst' import Icon from 'react-native-vector-icons/MaterialCommunityIcons' +import { setDataIncident, setDataAchievement, setDataTypeRisk } from '../../../appredux/actions'; +import { useDispatch, useSelector } from 'react-redux'; +import { + BottomSheetModal, + BottomSheetModalProvider, + BottomSheetScrollView +} from '@gorhom/bottom-sheet'; +import { strings } from '../../../utils/i18n'; export default function IncidentScreen() { - const [selectedItems, setSelectedItems] = useState([]); - const [dataIncidentReport, setDataIncidentReport] = useState(jenisKejadian[0].data) - const [typeRiskIncident, setTypeRiskIncident] = useState("") + const theme = useTheme(); + const isDarkTheme = theme.dark; + const dispatch = useDispatch(); + const [filter, setFilter] = useState(''); + const bottomSheetIncindentModal = useRef(null); + const bottomSheetAchievementModal = useRef(null); + const dataIncidentReport = useSelector(state => state.incidentReportReducer.dataIncidentReport); + const dataAchievement = useSelector(state => state.incidentReportReducer.dataAchievement); + const colorsheet = isDarkTheme ? theme.colors.black : theme.colors.pureWhite; - const handleCheckboxChange = (item) => { + const handleOpenSheetIncident = useCallback(() => { + bottomSheetIncindentModal.current?.present(); + }, []); + + const handleOpenSheetAchievement = useCallback(() => { + bottomSheetAchievementModal.current?.present(); + }, []); + + const handleSelectAchievement = (item) => { + const updatedDataAchievement = dataAchievement.map(dataItem => { + if (dataItem.label === item.label) { + return { ...dataItem, checked: !dataItem.checked }; + } + return dataItem; + }); + dispatch(setDataAchievement(updatedDataAchievement)); + setFilter(''); + bottomSheetAchievementModal.current?.dismiss(); + } + + const handleSelectIncident = (item) => { const updatedDataIncidentReport = dataIncidentReport.map(dataItem => { if (dataItem.label === item.label) { return { ...dataItem, checked: !dataItem.checked }; } return dataItem; }); - setDataIncidentReport(updatedDataIncidentReport); const hasCritical = updatedDataIncidentReport.some(item => item.level === 'Critical' && item.checked); - setTypeRiskIncident(updatedDataIncidentReport.some(item => item.checked) ? (hasCritical ? 'Critical' : 'Major') : ''); - }; + dispatch(setDataTypeRisk(updatedDataIncidentReport.some(item => item.checked) ? (hasCritical ? 'Critical' : 'Major') : '')); + dispatch(setDataIncident(updatedDataIncidentReport)); + setFilter(''); + bottomSheetIncindentModal.current?.dismiss(); + } + const dataFilterIncident = dataIncidentReport.filter(item => item.label.toLowerCase().includes(filter.toLowerCase())); + const dataFilterAchievement = dataAchievement.filter(item => item.label.toLowerCase().includes(filter.toLowerCase())); - const isItemSelected = (label) => { - return dataIncidentReport.some(item => item.label === label && item.checked); - }; + const renderAchievement = (data) => ( + + handleSelectAchievement(data)}> + + + + ); + + const renderIncident = (data) => ( + + handleSelectIncident(data)}> + + + + ); return ( <> - Jenis kejadian + {strings('incidentReport.achievement')} & {strings('incidentReport.incident')} - Kejadian apa saja yang terjadi di lokasi, bisa pilih lebih dari satu + Capaian/Kejadian apa saja yang terjadi di lokasi, bisa pilih lebih dari satu - - Pilih Jenis Kejadian + + {strings('incidentReport.achievement')} + + + + + + + {dataAchievement.filter(item => item.checked).map(item => ( + ( + + )} textStyle={{ color: colors.blue }} onPress={() => handleSelectAchievement(item)} style={{ backgroundColor: colors.semiBlue, marginBottom: 5 }}>{item.label} + ))} + + + + + {strings('incidentReport.incident')} + + + - {dataIncidentReport.map(item => ( - <> - ( - - )} textStyle={{ color: isItemSelected(item.label) ? colors.pureWhite : colors.blue }} onPress={() => handleCheckboxChange(item)} style={{ backgroundColor: isItemSelected(item.label) ? colors.beanRed : colors.semiBlue, marginBottom: 5 }}>{item.label} - + {dataIncidentReport.filter(item => item.checked).map(item => ( + ( + + )} textStyle={{ color: colors.blue }} onPress={() => handleSelectIncident(item)} style={{ backgroundColor: colors.semiBlue, marginBottom: 5 }}>{item.label} ))} - - + + + + + + setFilter(text)} + value={filter} + traileringIconColor={isDarkTheme ? theme.colors.pureWhite : theme.colors.surface} + style={{ marginHorizontal: 5, backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.background }} + showDivider={true} + elevation={1} + /> + + {dataFilterIncident.map(item => renderIncident(item))} + + + + + setFilter(text)} + value={filter} + traileringIconColor={isDarkTheme ? theme.colors.pureWhite : theme.colors.surface} + style={{ marginHorizontal: 5, backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.background }} + showDivider={true} + elevation={1} + /> + + {dataFilterAchievement.map(item => renderAchievement(item))} + + + ) } @@ -71,5 +201,5 @@ const styles = StyleSheet.create({ listData: { flex: 1, marginTop: 10, - }, + } }); diff --git a/src/screens/incident-page/stepComponent/location.js b/src/screens/incident-page/stepComponent/location.js index ac7590b..3cc2f68 100644 --- a/src/screens/incident-page/stepComponent/location.js +++ b/src/screens/incident-page/stepComponent/location.js @@ -1,58 +1,44 @@ import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'; -import { TextInput, Card, Checkbox, Text, TouchableRipple } from 'react-native-paper'; +import { TextInput, Card, Checkbox, Text, TouchableRipple, useTheme } from 'react-native-paper'; import { StyleSheet, View, ScrollView } from 'react-native'; import { colors } from '../../../utils/color' -import { useNavigation } from '@react-navigation/native'; +import { useDispatch, useSelector } from 'react-redux'; +import { setArea, setIncidentLocation, setReportLocation } from '../../../appredux/modules/incidentReport/actions'; export default function LocationScreen() { - const navigation = useNavigation(); - const [selectedProject, setSelectedProject] = useState(null); - const [area, setArea] = useState({ external: false, internal: false }) - const handleProjectPress = () => { - navigation.navigate('SearchPage', { dummyData, onSelect: handleProjectSelect }); - }; - + const theme = useTheme(); + const dispatch = useDispatch(); + const { user } = useSelector(state => state.userReducer); + const area = useSelector(state => state.incidentReportReducer.area); + const incidentLocation = useSelector(state => state.incidentReportReducer.incidentLocation); + const reportLocation = useSelector(state => state.incidentReportReducer.reportLocation); + const isDarkTheme = theme.dark; + console.log("area", area); const handleCheckboxArea = (data) => { const { name } = data; - setArea(prevState => ({ - ...prevState, - [name]: !prevState[name] - })); + const updatedArea = { ...area, [name]: !area[name] }; + dispatch(setArea(updatedArea)); + }; + + const handleIncidentLocationChange = (location) => { + dispatch(setIncidentLocation({ location })); }; - console.log("Area :", area); - const handleProjectSelect = (project) => { - setSelectedProject(project); + + const handleIncidentSubLocationChange = (subLocation) => { + dispatch(setIncidentLocation({ subLocation })); + }; + + const handleReportProjectLocationChange = (projectLocation) => { + dispatch(setReportLocation({ projectLocation })); }; - 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 handleReportLocationChange = (reportLocation) => { + dispatch(setReportLocation({ reportLocation })); + }; + + const handleReportSubLocationChange = (subLocation) => { + dispatch(setReportLocation({ subLocation })); + }; return ( <> @@ -68,22 +54,20 @@ export default function LocationScreen() { - + Lokasi Kejadian - - } - /> - + + + handleCheckboxArea({ name: 'internal', checked: !area.internal })} /> handleCheckboxArea({ name: 'internal', checked: !area.internal })}> - + Internal (Didalam area/kawasan/parimeter Project) handleCheckboxArea({ name: 'external', checked: !area.external })} /> handleCheckboxArea({ name: 'external', checked: !area.internal })}> - + External (Diluar area/kawasan/parimeter Project) - + Lokasi Pelaporan - - - + - ) + ); } const styles = StyleSheet.create({ diff --git a/src/screens/incident-page/stepComponent/media.js b/src/screens/incident-page/stepComponent/media.js index b07a5ca..80f067b 100644 --- a/src/screens/incident-page/stepComponent/media.js +++ b/src/screens/incident-page/stepComponent/media.js @@ -1,4 +1,4 @@ -import React, { useRef, useCallback, useState, useMemo } from 'react'; +import React, { useRef, useCallback, useState, useEffect } from 'react'; import { launchCamera, launchImageLibrary } from 'react-native-image-picker'; import { BottomSheetModal, @@ -6,29 +6,41 @@ import { BottomSheetView } from '@gorhom/bottom-sheet'; import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker'; -import { TextInput, IconButton, Button } from 'react-native-paper'; +import { TextInput, IconButton, Button, useTheme } from 'react-native-paper'; import { StyleSheet, View, ScrollView, PermissionsAndroid, Image } from 'react-native'; -import { colors } from '../../../utils/color'; -import { strings } from '../../../utils/i18n'; -import { requestAccessStoragePermission } from '../../../utils/storage'; +import { useDispatch, useSelector } from 'react-redux'; +import uuid from 'react-native-uuid'; +import { setImages } from '../../../appredux/actions'; import { getCoords } from '../../../utils/geolocation'; -export default function MediaScreen() { - const [images, setImages] = useState([]); +export default function MediaScreen() { + const images = useSelector(state => state.incidentReportReducer.dataImage || []); + const theme = useTheme(); + const isDarkTheme = theme.dark; + const dispatch = useDispatch(); + const colorsheet = isDarkTheme ? theme.colors.black : theme.colors.pureWhite; + const [existingAttachmentNumber, setExistingAttachmentNumber] = useState(''); const bottomSheetModal = useRef(null); + console.log("images", useSelector(state => state.incidentReportReducer.dataImage)); - const handleSetImageUri = (uri, description) => { - const newImage = { uri: uri, description: description || "" }; - setImages(prevImages => [...prevImages, newImage]); - } + const handleSetImageUri = (newImage) => { + const updatedImages = [...images, newImage]; + dispatch(setImages(updatedImages)); + }; const handleDeleteImage = (index) => { const updatedImages = [...images]; updatedImages.splice(index, 1); - setImages(updatedImages); + dispatch(setImages(updatedImages)); }; + useEffect(() => { + if (existingAttachmentNumber === '') { + setExistingAttachmentNumber(uuid.v4()) + } + }, []) + const handleOpenSheet = useCallback(() => { bottomSheetModal.current?.present(); }, []); @@ -38,21 +50,21 @@ export default function MediaScreen() { 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'), + title: 'Camera Permission', + message: 'App needs camera permission to take pictures.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', }, ); if (granted === PermissionsAndroid.RESULTS.GRANTED) { - handleLaunchCamera() + handleLaunchCamera(); } else { + console.log('Camera permission denied'); } } catch (err) { console.warn(err); } - bottomSheetModal.current?.dismiss(); }; const handleLaunchCamera = () => { @@ -61,93 +73,68 @@ export default function MediaScreen() { skipBackup: true, path: 'images', }, - cameraType: 'front', + cameraType: 'back', maxWidth: 768, maxHeight: 1024, saveToPhotos: false, includeBase64: true }; - launchCamera(options, (response) => { + launchCamera(options, async (response) => { if (response.didCancel) { + console.log('User cancelled image picker'); } else if (response.error) { - } else if (response.customButton) { - } else { - if (response.assets && response.assets.length > 0) { - handleSetImageUri(response.assets[0].uri); - getCoords(loc => { + console.log('ImagePicker Error: ', response.error); + } else if (response.assets && response.assets.length > 0) { + try { + getCoords(async (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 timestamp = new Date().toLocaleString(); + const location = { latitude: loc.lat, longitude: loc.lon }; + 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 newImageData = { + id: 0, + attachment_number: existingAttachmentNumber, + file: `file://${markedImage}`, + data: response.assets[0].uri, + type: response.assets[0].type, + name: response.assets[0].fileName + }; + handleSetImageUri(newImageData); } - }) + }); + } catch (error) { + console.error('Error adding overlay:', error); } } }); - } - - 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}/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 handleLaunchGallery = () => { let options = { @@ -157,16 +144,26 @@ export default function MediaScreen() { launchImageLibrary(options, (response) => { if (response.didCancel) { + console.log('User cancelled image picker'); } else if (response.error) { - } else if (response.customButton) { + console.log('ImagePicker Error: ', response.error); } else { - handleSetImageUri(response.assets[0].uri); + if (response.assets && response.assets.length > 0) { + const newImage = { + uri: response.assets[0].uri, + attachment_number: existingAttachmentNumber, + data: response.assets[0].uri, + type: response.assets[0].type, + name: response.assets[0].fileName + }; + handleSetImageUri(newImage); + } } }); bottomSheetModal.current?.dismiss(); - } + }; - const renderImages = useMemo(() => images.map((image, index) => ( + const renderImages = images.map((image, index) => ( { const updatedImages = [...images]; updatedImages[index].description = text; - setImages(updatedImages); + dispatch(setImages(updatedImages)); }} /> - )), [images]); + )); return ( <> @@ -207,7 +203,6 @@ export default function MediaScreen() { style={styles.addButton} mode="contained-tonal" onPress={handleOpenSheet} - textColor={colors.blue} > Tambah Gambar @@ -243,7 +238,7 @@ export default function MediaScreen() { - ) + ); } const styles = StyleSheet.create({ @@ -263,16 +258,15 @@ const styles = StyleSheet.create({ image: { width: 100, height: 100, - borderRadius: 7 + borderRadius: 7, }, descriptionInput: { - flex: 1 + flex: 1, }, addButton: { marginHorizontal: 8, marginVertical: 10, borderRadius: 10, - backgroundColor: colors.semiBlue, }, bottomSheet: { marginHorizontal: 15, diff --git a/src/screens/incident-page/stepComponent/report.js b/src/screens/incident-page/stepComponent/report.js index aa7f7b6..f7b4da7 100644 --- a/src/screens/incident-page/stepComponent/report.js +++ b/src/screens/incident-page/stepComponent/report.js @@ -1,11 +1,40 @@ -import React, { useState } from 'react'; -import { TouchableRipple, TextInput, Card, Text } from 'react-native-paper'; +import React from 'react'; +import { TextInput, Card, Text, useTheme } from 'react-native-paper'; import { View, StyleSheet, ScrollView } from 'react-native'; -import DateTimePicker from '@react-native-community/datetimepicker'; -import moment from 'moment'; +import { useDispatch, useSelector } from 'react-redux'; +import { setDataRecepient, setDataReporter } from '../../../appredux/modules/incidentReport/actions'; import { colors } from '../../../utils/color'; export default function ReportScreen() { + const theme = useTheme(); + const isDarkTheme = theme.dark; + const dispatch = useDispatch(); + const dataReporter = useSelector(state => state.incidentReportReducer.dataReporter); + const dataRecipient = useSelector(state => state.incidentReportReducer.dataRecipient); + + const handleReporterNameChange = (name) => { + dispatch(setDataReporter({ name })); + }; + + const handleReporterPositionChange = (position) => { + dispatch(setDataReporter({ position })); + }; + + const handleReporterContactChange = (noContact) => { + dispatch(setDataReporter({ noContact })); + }; + + const handleRecipientNameChange = (name) => { + dispatch(setDataRecepient({ name })); + }; + + const handleRecipientPositionChange = (position) => { + dispatch(setDataRecepient({ position })); + }; + + const handleRecipientContactChange = (noContact) => { + dispatch(setDataRecepient({ noContact })); + }; return ( <> @@ -21,77 +50,89 @@ export default function ReportScreen() { - + Pelapor - + Penerima Laporan - ) + ); } const styles = StyleSheet.create({ diff --git a/src/screens/incident-page/stepComponent/time.js b/src/screens/incident-page/stepComponent/time.js index 23e50ee..c435e9d 100644 --- a/src/screens/incident-page/stepComponent/time.js +++ b/src/screens/incident-page/stepComponent/time.js @@ -3,40 +3,49 @@ import { TouchableRipple, TextInput, Card, Text } from 'react-native-paper'; import { View, StyleSheet } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import moment from 'moment'; +import { useSelector, useDispatch } from 'react-redux'; +import { setIncidentDate, setIncidentTime, setReportDate, setReportTime } from '../../../appredux/modules/incidentReport/actions'; import { colors } from '../../../utils/color'; export default function TimeScreen() { - const [incidentDate, setIncidentDate] = useState(''); - const [incidentTime, setIncidentTime] = useState(''); - const [reportDate, setReportDate] = useState(''); - const [reportTime, setReportTime] = useState(''); + const dispatch = useDispatch(); + + const incidentDate = useSelector(state => state.incidentReportReducer.incidentDate); + const incidentTime = useSelector(state => state.incidentReportReducer.incidentTime); + const reportDate = useSelector(state => state.incidentReportReducer.reportDate); + const reportTime = useSelector(state => state.incidentReportReducer.reportTime); const [showIncidentDatePicker, setShowIncidentDatePicker] = useState(false); const [showIncidentTimePicker, setShowIncidentTimePicker] = useState(false); const [showReportDatePicker, setShowReportDatePicker] = useState(false); const [showReportTimePicker, setShowReportTimePicker] = useState(false); - + console.log("incidentDate", incidentDate ? moment(incidentDate).format("YYYY-MM-DD HH:mm:ss Z") : 'null'); + console.log("reportDate", reportDate ? moment(reportDate).format("YYYY-MM-DD HH:mm:ss Z") : 'null'); const handleIncidentDateChange = (event, selectedDate) => { - const currentDate = selectedDate; setShowIncidentDatePicker(false); - setIncidentDate(currentDate); + if (selectedDate) { + dispatch(setIncidentDate(selectedDate)); + } }; const handleIncidentTimeChange = (event, selectedDate) => { - const currentDate = selectedDate; setShowIncidentTimePicker(false); - setIncidentTime(currentDate); + if (selectedDate) { + dispatch(setIncidentTime(selectedDate)); + } }; const handleReportDateChange = (event, selectedDate) => { - const currentDate = selectedDate; setShowReportDatePicker(false); - setReportDate(currentDate); + if (selectedDate) { + dispatch(setReportDate(selectedDate)); + } }; const handleReportTimeChange = (event, selectedDate) => { - const currentDate = selectedDate; setShowReportTimePicker(false); - setReportTime(currentDate); + if (selectedDate) { + dispatch(setReportTime(selectedDate)); + } }; const showIncidentDatePickerFunc = () => {