From 882aa3db61e81307d5de8ddeac6196c1e688405c Mon Sep 17 00:00:00 2001 From: Antoine Bagnaud Date: Sat, 13 Jul 2024 00:22:03 +0200 Subject: [PATCH 01/32] feat(barcode): create basic ui & scanner component --- src/components/BarcodeCameraView.js | 109 ++++++++++++++++++ src/navigation/courier/Barcode.js | 90 +++++++++++++++ src/navigation/index.js | 2 + src/navigation/navigators/CourierNavigator.js | 28 ++++- 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 src/components/BarcodeCameraView.js create mode 100644 src/navigation/courier/Barcode.js diff --git a/src/components/BarcodeCameraView.js b/src/components/BarcodeCameraView.js new file mode 100644 index 000000000..e409fb86d --- /dev/null +++ b/src/components/BarcodeCameraView.js @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; +import { Dimensions } from 'react-native'; +import { View, Vibration } from 'react-native'; +import { CameraView, useCameraPermissions } from 'expo-camera/next'; +import _ from 'lodash'; +import { Icon } from 'native-base'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import { TouchableOpacity } from 'react-native-gesture-handler'; + +function getBoxBoundaries(points) { + const xs = points.map(point => point.x); + const ys = points.map(point => point.y); + const minX = Math.min(...xs); + const maxX = Math.max(...xs); + const minY = Math.min(...ys); + const maxY = Math.max(...ys); + return { minX, maxX, minY, maxY }; +} + +function horizontalLineIntersectsBox(lineY, lineStartX, lineEndX, points) { + const { minX, maxX, minY, maxY } = getBoxBoundaries(points); + + if (lineY < minY || lineY > maxY) { + return false; + } + if (lineStartX > lineEndX) { + [lineStartX, lineEndX] = [lineEndX, lineStartX]; + } + return lineEndX >= minX && lineStartX <= maxX; +} + +export default function BarcodeCameraView(props) { + const [hasPermission, requestPermission] = useCameraPermissions(); + + const { width: CameraWidth } = Dimensions.get('window'); + const CameraHeight = CameraWidth * 0.55; + const PaddingHorizontal = CameraWidth * 0.1618; + + const [barcode, setBarcode] = useState(null); + const [flash, setFlash] = useState(false); + + if (!hasPermission) { + return ; + } + + if (!hasPermission.granted) { + requestPermission(); + return ; + } + + const onScanned = _.throttle(result => { + const { data, cornerPoints } = result; + if ( + !horizontalLineIntersectsBox( + CameraHeight / 2, + PaddingHorizontal, + CameraWidth - PaddingHorizontal, + cornerPoints, + ) + ) { + return; + } + + if (data !== barcode) { + Vibration.vibrate(); + setBarcode(data); + props.onScanned(data); + } + }, 100); + + return ( + + + + setFlash(!flash)}> + + + + + ); +} diff --git a/src/navigation/courier/Barcode.js b/src/navigation/courier/Barcode.js new file mode 100644 index 000000000..3fb62432f --- /dev/null +++ b/src/navigation/courier/Barcode.js @@ -0,0 +1,90 @@ +import React, { useState } from 'react'; +import { withTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import { View, Text, ScrollView, StyleSheet } from 'react-native'; +import { Button, IconButton } from 'native-base'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import BarcodeCameraView from '../../components/BarcodeCameraView'; + +function TextSection({ title, value }) { + return ( + + {title} + {value ?? '-'} + + ); +} + +function BarcodePage() { + const [barcode, setBarcode] = useState(null); + + return ( + + + + + + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + data: { + fontWeight: '600', + color: 'black', + fontSize: 16, + }, + section: { paddingVertical: 8 }, + title: { fontSize: 16, paddingBottom: 3 }, +}); + +function mapStateToProps(state) { + return { + ...state, + }; +} + +function mapDispatchToProps(dispatch) { + return { + dispatch, + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withTranslation()(BarcodePage)); diff --git a/src/navigation/index.js b/src/navigation/index.js index 4da4588f3..ce08e0baf 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -41,6 +41,7 @@ import CourierSettings from './courier/Settings'; import CourierSettingsTags from './courier/Tags'; import CourierTaskListPage from './courier/TaskListPage'; import CourierTasksPage from './courier/TasksPage'; +import CourierBarcodePage from './courier/Barcode'; import CheckoutPayment from './checkout/Payment'; import CheckoutLogin from './checkout/Login'; @@ -98,6 +99,7 @@ export default { CheckoutMercadopago, CheckoutMoreInfos, CourierTasksPage, + CourierBarcodePage, CourierTaskListPage, CourierSettings, CourierSettingsTags, diff --git a/src/navigation/navigators/CourierNavigator.js b/src/navigation/navigators/CourierNavigator.js index da9f42784..5ba32b396 100644 --- a/src/navigation/navigators/CourierNavigator.js +++ b/src/navigation/navigators/CourierNavigator.js @@ -73,7 +73,11 @@ const MainStack = createStackNavigator(); const headerButtons = nav => ( nav.navigate('CourierBarcode')} + /> + nav.navigate('CourierSettings')} /> @@ -111,6 +115,21 @@ const MainNavigator = () => ( ); + +const BarcodeStack = createStackNavigator(); +const BarcodeNavigator = () => ( + + + +); + + const SettingsStack = createStackNavigator(); const SettingsNavigator = () => ( @@ -151,5 +170,12 @@ export default () => ( headerShown: false, }} /> + ); From e38677879b0fbbd211a1628eddd1c761e1d526cf Mon Sep 17 00:00:00 2001 From: Antoine Bagnaud Date: Wed, 17 Jul 2024 15:51:04 +0200 Subject: [PATCH 02/32] wip --- src/components/BarcodeCameraView.js | 10 ++++-- src/navigation/courier/Barcode.js | 55 +++++++++++++++++------------ 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/components/BarcodeCameraView.js b/src/components/BarcodeCameraView.js index e409fb86d..a6292ddf1 100644 --- a/src/components/BarcodeCameraView.js +++ b/src/components/BarcodeCameraView.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Dimensions } from 'react-native'; +import { Dimensions, Text } from 'react-native'; import { View, Vibration } from 'react-native'; import { CameraView, useCameraPermissions } from 'expo-camera/next'; import _ from 'lodash'; @@ -40,7 +40,11 @@ export default function BarcodeCameraView(props) { const [flash, setFlash] = useState(false); if (!hasPermission) { - return ; + return ( + + No camera permission + + ); } if (!hasPermission.granted) { @@ -66,7 +70,7 @@ export default function BarcodeCameraView(props) { setBarcode(data); props.onScanned(data); } - }, 100); + }, 200); return ( {title} - {value ?? '-'} + {value ?? '-'} ); } -function BarcodePage() { +function BarcodePage({ httpClient }) { const [barcode, setBarcode] = useState(null); + const [entity, setEntity] = useState(null); return ( - - - - - - + { + const { entity } = await _fetchBarcode(httpClient, code); + setBarcode(code); + setEntity(entity); + }} /> + + + + + + Date: Tue, 27 Aug 2024 18:06:44 +0200 Subject: [PATCH 03/32] feat(ui): build all the UI and small logic --- package.json | 1 + src/i18n/locales/en.json | 4 +- src/i18n/locales/fr.json | 4 +- src/navigation/courier/SelectRange.js | 62 +++++++++ src/navigation/courier/UpdateParcel.js | 131 ++++++++++++++++++ .../courier/{ => barcode}/Barcode.js | 28 ++-- .../courier/barcode/BarcodeReport.js | 25 ++++ src/navigation/index.js | 8 +- src/navigation/navigators/CourierNavigator.js | 15 ++ yarn.lock | 52 ++++++- 10 files changed, 315 insertions(+), 15 deletions(-) create mode 100644 src/navigation/courier/SelectRange.js create mode 100644 src/navigation/courier/UpdateParcel.js rename src/navigation/courier/{ => barcode}/Barcode.js (76%) create mode 100644 src/navigation/courier/barcode/BarcodeReport.js diff --git a/package.json b/package.json index dd959e653..00895a6d5 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "i18next": "^22", "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.11.4", + "little-date": "^1.0.0", "lodash": "^4.17.21", "moment": "^2.30.1", "moment-business-days": "^1.2.0", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 7012dc3b2..8a99c308b 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -505,6 +505,8 @@ "FEATURED": "Featured", "EXCLUSIVE": "Exclusive", "NEW": "New", - "RESTAURANT_MORE_INFOS": "More information" + "RESTAURANT_MORE_INFOS": "More information", + "UPDATE_PARCEL_DETAILS": "Update parcel details", + "REPORT_AN_INCIDENT": "Report an incident", } } diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 2c8c803a8..715dfa7a3 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -480,6 +480,8 @@ "CART_ZERO_WASTE_POPUP_BUTTON_TEXT": "Jʼai compris", "TERMS_OF_SERVICE": "Conditions générales", "PRIVACY": "Confidentialité", - "RESTAURANT_MORE_INFOS": "En savoir plus" + "RESTAURANT_MORE_INFOS": "En savoir plus", + "UPDATE_PARCEL_DETAILS": "Mettre à jour les données de la livraison", + "REPORT_AN_INCIDENT": "Signaler un incident", } } diff --git a/src/navigation/courier/SelectRange.js b/src/navigation/courier/SelectRange.js new file mode 100644 index 000000000..e3b4afaed --- /dev/null +++ b/src/navigation/courier/SelectRange.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { FormControl, Input, KeyboardAvoidingView, Button } from 'native-base'; +import { withTranslation } from 'react-i18next'; +import moment from 'moment'; +import DateTimePicker from 'react-native-modal-datetime-picker'; + +function SelectRange({ t, route, navigation }) { + const { after, before, navigateTo } = route.params; + const [afterDate, setAfterDate] = useState(moment(after)); + const [beforeDate, setBeforeDate] = useState(moment(before)); + + const [showAfterDatePicker, setShowAfterDatePicker] = useState(false); + const [showBeforeDatePicker, setShowBeforeDatePicker] = useState(false); + + return ( + + + {t('TASK_FORM_DONE_AFTER_LABEL')} + setShowAfterDatePicker(true)} + value={afterDate.format('ll LT')} + /> + + {t('TASK_FORM_DONE_BEFORE_LABEL')} + + setShowBeforeDatePicker(true)} + value={beforeDate.format('ll LT')} + /> + + + + setAfterDate(moment(date))} + onCancel={() => setShowAfterDatePicker(false)} + /> + setBeforeDate(moment(date))} + onCancel={() => setShowBeforeDatePicker(false)} + /> + + ); +} + +export default withTranslation()(SelectRange); diff --git a/src/navigation/courier/UpdateParcel.js b/src/navigation/courier/UpdateParcel.js new file mode 100644 index 000000000..ed30657b1 --- /dev/null +++ b/src/navigation/courier/UpdateParcel.js @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react'; + +import { + Input, + FormControl, + KeyboardAvoidingView, + HStack, + VStack, + Divider, + Select, + Heading, + View, +} from 'native-base'; +import { withTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import DateTimePicker from 'react-native-modal-datetime-picker'; +import { formatDateRange } from 'little-date'; + +function UpdateParcel({ route: { params }, t, navigation }) { + const { entity } = params; + + const [showAfterDatePicker, setShowAfterDatePicker] = useState(false); + const [showBeforeDatePicker, setShowBeforeDatePicker] = useState(false); + + const [afterDate, setAfterDate] = useState( + new Date(params?.after ?? entity?.after), + ); + const [beforeDate, setBeforeDate] = useState( + new Date(params?.before ?? entity?.before), + ); + + useEffect(() => { + if (params?.selectRange) { + const [after, before] = params?.selectRange; + setAfterDate(new Date(after)); + setBeforeDate(new Date(before)); + } + }, [params?.selectRange]); + + if (showAfterDatePicker) { + const onConfirm = date => { + setAfterDate(date); + setShowAfterDatePicker(false); + setShowBeforeDatePicker(true); + }; + return ( + + {t('TASK_FORM_DONE_AFTER_LABEL')} + setShowAfterDatePicker(false)} + /> + + ); + } + + if (showBeforeDatePicker) { + const onConfirm = date => { + setBeforeDate(date); + setShowBeforeDatePicker(false); + }; + return ( + + {t('TASK_FORM_DONE_BEFORE_LABEL')} + setShowBeforeDatePicker(false)} + /> + + ); + } + + return ( + + + Address + + + + Weight + + Volume + + + + + Package + + + + + New appointment + + navigation.navigate('CourierSelectRange', { + after: entity?.after, + before: entity?.before, + navigateTo: 'CourierUpdateParcel', + }) + } + value={formatDateRange(afterDate, beforeDate)} + /> + + + ); +} + +function mapStateToProps(state) { + return { + httpClient: state.app.httpClient, + }; +} + +function mapDispatchToProps(dispatch) { + return {}; +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withTranslation()(UpdateParcel)); diff --git a/src/navigation/courier/Barcode.js b/src/navigation/courier/barcode/Barcode.js similarity index 76% rename from src/navigation/courier/Barcode.js rename to src/navigation/courier/barcode/Barcode.js index 36e59c059..10252c95b 100644 --- a/src/navigation/courier/Barcode.js +++ b/src/navigation/courier/barcode/Barcode.js @@ -4,7 +4,10 @@ import { connect } from 'react-redux'; import { ScrollView, StyleSheet, Text, View, Vibration } from 'react-native'; import { Button, IconButton } from 'native-base'; import Ionicons from 'react-native-vector-icons/Ionicons'; -import BarcodeCameraView from '../../components/BarcodeCameraView'; +import BarcodeCameraView from '../../../components/BarcodeCameraView'; + +import { CommonActions } from '@react-navigation/native'; +import NavigationHolder from '../../../NavigationHolder'; async function _fetchBarcode(httpClient, barcode) { if (barcode) { @@ -31,18 +34,20 @@ function BarcodePage({ httpClient }) { return ( - { - const { entity } = await _fetchBarcode(httpClient, code); - setBarcode(code); - setEntity(entity); - }} /> + { + const { entity } = await _fetchBarcode(httpClient, code); + setBarcode(code); + setEntity(entity); + }} + /> - + - + NavigationHolder.dispatch( + CommonActions.navigate("CourierBarcodeReport", { entity } ), + )} + disabled={entity === null} _icon={{ as: Ionicons, name: 'warning', @@ -77,7 +87,7 @@ const styles = StyleSheet.create({ }, note: { color: 'black', - fontSize: 14 + fontSize: 14, }, section: { paddingVertical: 8 }, title: { fontSize: 16, paddingBottom: 3 }, diff --git a/src/navigation/courier/barcode/BarcodeReport.js b/src/navigation/courier/barcode/BarcodeReport.js new file mode 100644 index 000000000..71877d4e5 --- /dev/null +++ b/src/navigation/courier/barcode/BarcodeReport.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { withTranslation } from 'react-i18next'; +import { Button, VStack, Text } from 'native-base'; + +function BarcodeReport({ route, t, navigation }) { + const { entity } = route.params; + return ( + + + {t('TASK')} #{entity?.id} + + + + + ); +} + +export default withTranslation()(BarcodeReport); diff --git a/src/navigation/index.js b/src/navigation/index.js index ce08e0baf..805970b9b 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -41,7 +41,10 @@ import CourierSettings from './courier/Settings'; import CourierSettingsTags from './courier/Tags'; import CourierTaskListPage from './courier/TaskListPage'; import CourierTasksPage from './courier/TasksPage'; -import CourierBarcodePage from './courier/Barcode'; +import CourierBarcodePage from './courier/barcode/Barcode'; +import CourierBarcodeReportPage from './courier/barcode/BarcodeReport'; +import CourierUpdateParcelPage from './courier/UpdateParcel'; +import CourierSelectRangePage from './courier/SelectRange.js' import CheckoutPayment from './checkout/Payment'; import CheckoutLogin from './checkout/Login'; @@ -100,6 +103,9 @@ export default { CheckoutMoreInfos, CourierTasksPage, CourierBarcodePage, + CourierBarcodeReportPage, + CourierUpdateParcelPage, + CourierSelectRangePage, CourierTaskListPage, CourierSettings, CourierSettingsTags, diff --git a/src/navigation/navigators/CourierNavigator.js b/src/navigation/navigators/CourierNavigator.js index 5ba32b396..c46b4e616 100644 --- a/src/navigation/navigators/CourierNavigator.js +++ b/src/navigation/navigators/CourierNavigator.js @@ -126,6 +126,21 @@ const BarcodeNavigator = () => ( title: i18n.t('COURIER_BARCODE'), }} /> + + + + + ); diff --git a/yarn.lock b/yarn.lock index cf47c8675..00cff2145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1707,6 +1707,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.21.0": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.4.tgz#6ef37d678428306e7d75f054d5b1bdb8cf8aa8ee" + integrity sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" @@ -7755,6 +7762,13 @@ dag-map@~1.0.0: resolved "https://registry.yarnpkg.com/dag-map/-/dag-map-1.0.2.tgz#e8379f041000ed561fc515475c1ed2c85eece8d7" integrity sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw== +date-fns@^2.0.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + dayjs@^1.8.15: version "1.10.7" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" @@ -11106,6 +11120,13 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" +little-date@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/little-date/-/little-date-1.0.0.tgz#be0243feb4eeb3765d333710a78d3613f3e650c3" + integrity sha512-41T/ktcwPzxC0OJ8E3wmaK0E1DL/QNR3n30kB9Dw6Ni6Eud24It8LZm70jK8lvDd+Mg+961fzKDcF6SQRL25cQ== + dependencies: + date-fns "^2.0.0" + localforage@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" @@ -14293,7 +14314,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14366,7 +14396,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14380,6 +14410,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -15285,7 +15322,7 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15303,6 +15340,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 1af156cf7596214b880cf09da7abc422d8cc38f7 Mon Sep 17 00:00:00 2001 From: Antoine Bagnaud Date: Wed, 4 Sep 2024 17:32:05 +0200 Subject: [PATCH 04/32] fix: make unused argument deprecated --- src/redux/Courier/taskActions.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/redux/Courier/taskActions.js b/src/redux/Courier/taskActions.js index e6e45d1f2..fdbfc5006 100644 --- a/src/redux/Courier/taskActions.js +++ b/src/redux/Courier/taskActions.js @@ -77,12 +77,10 @@ export const reportIncidentRequest = createAction(REPORT_INCIDENT_REQUEST); export const reportIncidentSuccess = createAction(REPORT_INCIDENT_SUCCESS); export const reportIncidentFailure = createAction(REPORT_INCIDENT_FAILURE); -export const addPicture = createAction(ADD_PICTURE, (task, base64) => ({ - task, +export const addPicture = createAction(ADD_PICTURE, (_task, base64) => ({ base64, })); -export const addSignature = createAction(ADD_SIGNATURE, (task, base64) => ({ - task, +export const addSignature = createAction(ADD_SIGNATURE, (_task, base64) => ({ base64, })); export const clearFiles = createAction(CLEAR_FILES); From 668dbe5bed155c68e0602a9f9dee384ef2ec38ef Mon Sep 17 00:00:00 2001 From: Antoine Bagnaud Date: Wed, 4 Sep 2024 17:33:29 +0200 Subject: [PATCH 05/32] feat: app fully functionnal Need some style fix --- src/components/BarcodeCameraView.js | 84 ++++--- src/navigation/courier/SelectRange.js | 33 ++- src/navigation/courier/barcode/Barcode.js | 133 ++++++++++- .../courier/barcode/BarcodeIncident.js | 212 ++++++++++++++++++ .../courier/barcode/BarcodeReport.js | 13 +- src/navigation/index.js | 2 + src/navigation/navigators/CourierNavigator.js | 4 + 7 files changed, 426 insertions(+), 55 deletions(-) create mode 100644 src/navigation/courier/barcode/BarcodeIncident.js diff --git a/src/components/BarcodeCameraView.js b/src/components/BarcodeCameraView.js index a6292ddf1..08fb8fb74 100644 --- a/src/components/BarcodeCameraView.js +++ b/src/components/BarcodeCameraView.js @@ -6,6 +6,7 @@ import _ from 'lodash'; import { Icon } from 'native-base'; import Ionicons from 'react-native-vector-icons/Ionicons'; import { TouchableOpacity } from 'react-native-gesture-handler'; +import FocusHandler from './FocusHandler'; function getBoxBoundaries(points) { const xs = points.map(point => point.x); @@ -39,6 +40,11 @@ export default function BarcodeCameraView(props) { const [barcode, setBarcode] = useState(null); const [flash, setFlash] = useState(false); + //The hide behavior is a hack to force the camera to re-render + //if another camera is opened on modal. If we don't do this, the + //camera will not render. + const [hide, setHide] = useState(false); + if (!hasPermission) { return ( @@ -73,41 +79,53 @@ export default function BarcodeCameraView(props) { }, 200); return ( - - + { + setHide(false); + }} + onBlur={() => { + setHide(true); }} /> - - setFlash(!flash)}> - + - - - + + setFlash(!flash)}> + + + + + )} + ); } diff --git a/src/navigation/courier/SelectRange.js b/src/navigation/courier/SelectRange.js index e3b4afaed..1a3e7b8f1 100644 --- a/src/navigation/courier/SelectRange.js +++ b/src/navigation/courier/SelectRange.js @@ -1,9 +1,11 @@ import React, { useState } from 'react'; -import { FormControl, Input, KeyboardAvoidingView, Button } from 'native-base'; +import { FormControl, Input, Button, ScrollView, View } from 'native-base'; import { withTranslation } from 'react-i18next'; import moment from 'moment'; import DateTimePicker from 'react-native-modal-datetime-picker'; +import KeyboardAdjustView from '../../components/KeyboardAdjustView'; + function SelectRange({ t, route, navigation }) { const { after, before, navigateTo } = route.params; const [afterDate, setAfterDate] = useState(moment(after)); @@ -12,23 +14,36 @@ function SelectRange({ t, route, navigation }) { const [showAfterDatePicker, setShowAfterDatePicker] = useState(false); const [showBeforeDatePicker, setShowBeforeDatePicker] = useState(false); + const isAfterInvalid = afterDate?.isBefore(moment()); + const isBeforeInvalid = beforeDate?.isBefore(afterDate); + return ( - - + + + {t('TASK_FORM_DONE_AFTER_LABEL')} setShowAfterDatePicker(true)} + onTouchStart={() => setShowAfterDatePicker(true)} value={afterDate.format('ll LT')} /> + + {t('TASK_FORM_DONE_BEFORE_LABEL')} setShowBeforeDatePicker(true)} + onTouchStart={() => setShowBeforeDatePicker(true)} value={beforeDate.format('ll LT')} /> - - + setAfterDate(moment(date))} onCancel={() => setShowAfterDatePicker(false)} /> @@ -52,10 +68,11 @@ function SelectRange({ t, route, navigation }) { mode="datetime" isVisible={showBeforeDatePicker} minimumDate={afterDate.toDate()} + minuteInterval={15} onConfirm={date => setBeforeDate(moment(date))} onCancel={() => setShowBeforeDatePicker(false)} /> - + ); } diff --git a/src/navigation/courier/barcode/Barcode.js b/src/navigation/courier/barcode/Barcode.js index 10252c95b..bfab6109b 100644 --- a/src/navigation/courier/barcode/Barcode.js +++ b/src/navigation/courier/barcode/Barcode.js @@ -1,13 +1,23 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; -import { ScrollView, StyleSheet, Text, View, Vibration } from 'react-native'; -import { Button, IconButton } from 'native-base'; +import { + ScrollView, + StyleSheet, + Text, + View, + Vibration, + Alert, +} from 'react-native'; +import { Button, IconButton, TextArea } from 'native-base'; import Ionicons from 'react-native-vector-icons/Ionicons'; import BarcodeCameraView from '../../../components/BarcodeCameraView'; import { CommonActions } from '@react-navigation/native'; import NavigationHolder from '../../../NavigationHolder'; +import { assignTask } from '../../../redux/Dispatch/actions'; +import { unassignTask } from '../../../redux/Dispatch/actions'; +import Modal from "react-native-modal" async function _fetchBarcode(httpClient, barcode) { if (barcode) { @@ -28,17 +38,108 @@ function TextSection({ title, value, variant = 'data' }) { ); } -function BarcodePage({ httpClient }) { +function BarcodePage({ httpClient, user, assignTask, unassignTask }) { const [barcode, setBarcode] = useState(null); const [entity, setEntity] = useState(null); + const [clientAction, setClientAction] = useState(null); + const [showNoteModal, setShowNoteModal] = useState(false); - return ( + const askUnassign = () => { + Alert.alert( + 'Task already assigned', + 'This task is already assigned to you', + [ + { + text: 'Unassign', + onPress: () => { + unassignTask(entity.id, user.username); + setClientAction(null); + }, + }, + { + text: 'Ok', + onPress: () => { + setClientAction(null); + }, + }, + ], + ); + }; + + const askAssign = () => { + Alert.alert( + 'Task already assigned', + 'This task is already assigned to another courier', + [ + { + text: 'Assign to me', + onPress: () => { + assignTask(entity.id, user.username); + setClientAction(null); + }, + }, + { + text: 'Ok', + onPress: () => { + setClientAction(null); + }, + }, + ], + ); + }; + + useEffect(() => { + return; + if (!clientAction) return; + switch (clientAction) { + case 'ask_unassign': + askUnassign(); + break; + case 'ask_assign': + askAssign(); + break; + default: + setClientAction(null); + } + }, [clientAction]); + + useEffect(() => { + if (!entity) return; + if (!entity?.barcodes?.packages) return; + const packages = entity?.barcodes?.packages.reduce( + (acc, p) => acc + p.barcodes.length, + 0, + ); + if (packages > 1) { + const details = entity?.barcodes?.packages + .map(p => `${p.barcodes.length}x ${p.name}`) + .join('\n'); + Alert.alert( + 'Multiple packages', + `${packages} packages:\n\n${details}\n\nNo need to scan the other packages`, + [{ text: 'Ok' }], + ); + } + }, [entity]); + + return <> + setShowNoteModal(false)}> + +