From 29bd2bc03eca1b66d2756773627f7fb940198ba4 Mon Sep 17 00:00:00 2001
From: Patrick Fic <>
Date: Tue, 9 Feb 2021 23:12:52 -0800
Subject: [PATCH] Added image picker and cleaned up main and job list screens.
---
App.js | 5 +-
babel-translations.babel | 78 +++++++++
.../camera-select-job.component.jsx | 18 +-
.../data-label/data-label.component.jsx | 2 +-
.../job-documents/job-documents.component.jsx | 6 +-
components/job-lines/job-lines.component.jsx | 2 +-
.../job-list-item/job-list-item.component.jsx | 91 ++++++++--
.../job-notes-item.component.jsx | 10 +-
components/job-notes/job-notes.component.jsx | 2 +-
components/screen-camera/screen-camera.jsx | 73 ++++----
.../screen-main/screen-main.component.jsx | 10 ++
.../screen-media-browser.component.jsx | 162 ++++++++++++++++++
.../screen-media-cache.component.jsx | 52 ++++--
.../screen-splash/screen-splash.component.jsx | 8 +-
.../sign-in-error-alert.component.jsx | 7 +-
components/styles.js | 1 -
.../upload-delete-switch.component.jsx | 48 ++++++
.../upload-progress.component.jsx | 46 +++++
graphql/jobs.queries.js | 6 +-
package-lock.json | 125 ++++++++++++++
package.json | 5 +-
redux/app/app.actions.js | 3 +
redux/app/app.reducer.js | 6 +
redux/app/app.selectors.js | 8 +
redux/app/app.types.js | 1 +
translations/en-US/common.json | 9 +
translations/es-MX/common.json | 9 +
translations/fr-CA/common.json | 9 +
util/document-upload.utility.js | 15 +-
29 files changed, 703 insertions(+), 114 deletions(-)
create mode 100644 components/screen-media-browser/screen-media-browser.component.jsx
create mode 100644 components/upload-delete-switch/upload-delete-switch.component.jsx
create mode 100644 components/upload-progress/upload-progress.component.jsx
diff --git a/App.js b/App.js
index bcfb4d9..f587127 100644
--- a/App.js
+++ b/App.js
@@ -4,6 +4,7 @@ import AppLoading from "expo-app-loading";
import * as FileSystem from "expo-file-system";
import * as Font from "expo-font";
import React from "react";
+import { SafeAreaView } from "react-native";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import * as Sentry from "sentry-expo";
@@ -54,7 +55,9 @@ export default class App extends React.Component {
-
+
+
+
diff --git a/babel-translations.babel b/babel-translations.babel
index 86eecca..9a19633 100644
--- a/babel-translations.babel
+++ b/babel-translations.babel
@@ -1015,6 +1015,84 @@
+
+ mediabrowser
+
+
+ actions
+
+
+ upload
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+ labels
+
+
+ deleteafterupload
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ selectjob
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+
mediacache
diff --git a/components/camera-select-job/camera-select-job.component.jsx b/components/camera-select-job/camera-select-job.component.jsx
index 3f2e5f1..b1e8a47 100644
--- a/components/camera-select-job/camera-select-job.component.jsx
+++ b/components/camera-select-job/camera-select-job.component.jsx
@@ -1,7 +1,7 @@
import { useQuery } from "@apollo/client";
import { Picker } from "native-base";
import React from "react";
-import { View } from "react-native";
+import { Text, View } from "react-native";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
@@ -10,6 +10,7 @@ import { selectCurrentCameraJobId } from "../../redux/app/app.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ErrorDisplay from "../error-display/error-display.component";
import LoadingDisplay from "../loading-display/loading-display.component";
+import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -27,17 +28,13 @@ export function CameraSelectJob({
setCameraJobId,
setCameraJob,
}) {
- const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
+ const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
},
skip: !bodyshop,
});
-
- const onRefresh = async () => {
- return refetch();
- };
-
+ const { t } = useTranslation();
if (loading) return ;
if (error) return ;
@@ -45,19 +42,14 @@ export function CameraSelectJob({
return (
+ {!cameraJobId && {t("mediabrowser.labels.selectjob")}}
{
- console.log(value, idx);
setCameraJobId(value);
setCameraJob(data.jobs[idx]);
}}
diff --git a/components/data-label/data-label.component.jsx b/components/data-label/data-label.component.jsx
index 37eb637..9abd8fd 100644
--- a/components/data-label/data-label.component.jsx
+++ b/components/data-label/data-label.component.jsx
@@ -1,9 +1,9 @@
+import { DateTime } from "luxon";
import { Input, Item, Label } from "native-base";
import React from "react";
import { StyleSheet } from "react-native";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
-import { DateTime } from "luxon";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
diff --git a/components/job-documents/job-documents.component.jsx b/components/job-documents/job-documents.component.jsx
index 5c38045..34f7c82 100644
--- a/components/job-documents/job-documents.component.jsx
+++ b/components/job-documents/job-documents.component.jsx
@@ -1,14 +1,11 @@
import { Container, Content, Thumbnail } from "native-base";
import React, { useState } from "react";
-import { useTranslation } from "react-i18next";
import {
FlatList,
- SafeAreaView,
+ RefreshControl,
StyleSheet,
Text,
TouchableOpacity,
- View,
- RefreshControl,
} from "react-native";
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
@@ -17,7 +14,6 @@ const REACT_APP_CLOUDINARY_IMAGE_ENDPOINT =
const REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS = "c_fill,f_auto,h_250,w_250";
export default function JobDocumentsComponent({ job, loading, refetch }) {
- const { t } = useTranslation();
const [previewVisible, setPreviewVisible] = useState(false);
const [imgIndex, setImgIndex] = useState(0);
const onRefresh = async () => {
diff --git a/components/job-lines/job-lines.component.jsx b/components/job-lines/job-lines.component.jsx
index 1729957..c16f7ee 100644
--- a/components/job-lines/job-lines.component.jsx
+++ b/components/job-lines/job-lines.component.jsx
@@ -6,7 +6,7 @@ import Dinero from "dinero.js";
export default function JobLines({ job, loading, refetch }) {
const { t } = useTranslation();
- if (!!!job) {
+ if (!job) {
Job is not defined.
;
diff --git a/components/job-list-item/job-list-item.component.jsx b/components/job-list-item/job-list-item.component.jsx
index 2335c16..d162240 100644
--- a/components/job-list-item/job-list-item.component.jsx
+++ b/components/job-list-item/job-list-item.component.jsx
@@ -1,9 +1,10 @@
import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
+import * as ImagePicker from "expo-image-picker";
import { Body, H3, Icon, ListItem, Right } from "native-base";
-import React from "react";
+import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
-import { Text } from "react-native";
+import { Animated, Platform, Text } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import Swipeable from "react-native-gesture-handler/Swipeable";
import { connect } from "react-redux";
@@ -23,9 +24,40 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) {
const { t } = useTranslation();
const navigation = useNavigation();
- const RenderRightAction = () => {
- const navigation = useNavigation();
- const { t } = useTranslation();
+ useEffect(() => {
+ (async () => {
+ if (Platform.OS !== "web") {
+ const {
+ status,
+ } = await ImagePicker.requestMediaLibraryPermissionsAsync();
+ if (status !== "granted") {
+ alert("Sorry, we need camera roll permissions to make this work!");
+ }
+ }
+ })();
+ }, []);
+
+ const pickImage = async () => {
+ let result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.All,
+ allowsEditing: true,
+ aspect: [4, 3],
+ quality: 1,
+ });
+
+ console.log(result);
+
+ if (!result.cancelled) {
+ // setImage(result.uri);
+ }
+ };
+
+ const RenderRightAction = (progress, dragX) => {
+ const scale = dragX.interpolate({
+ inputRange: [-100, 0],
+ outputRange: [0.7, 0],
+ });
+
return (
-
- {/*
- {t("joblist.actions.swipecamera")}
- */}
+
+
+
+
+ );
+ };
+ const RenderLeftAction = (progress, dragX) => {
+ const scale = dragX.interpolate({
+ inputRange: [0, 100],
+ outputRange: [0, 1],
+ extrapolate: "clamp",
+ });
+
+ return (
+ {
+ setCameraJobId(item.id);
+ setCameraJob(item);
+ navigation.push("MediaBrowser");
+ //pickImage(item.id);
+ }}
+ >
+
+ Add
+
);
};
@@ -51,7 +119,10 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) {
};
return (
- }>
+
{item.ro_number || t("general.labels.na")}
diff --git a/components/job-notes-item/job-notes-item.component.jsx b/components/job-notes-item/job-notes-item.component.jsx
index 1832820..3b7913a 100644
--- a/components/job-notes-item/job-notes-item.component.jsx
+++ b/components/job-notes-item/job-notes-item.component.jsx
@@ -1,16 +1,14 @@
-import { Ionicons } from "@expo/vector-icons";
+import { AntDesign, Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
-import { Card, CardItem, Text, View, Row } from "native-base";
+import { DateTime } from "luxon";
+import { Card, CardItem, Text, View } from "native-base";
import React from "react";
import { useTranslation } from "react-i18next";
import { TouchableOpacity } from "react-native-gesture-handler";
import Swipeable from "react-native-gesture-handler/Swipeable";
import styles from "../styles";
-import { AntDesign } from "@expo/vector-icons";
-import { DateTime } from "luxon";
-export default function NoteListItem({ item }) {
- const { t } = useTranslation();
+export default function NoteListItem({ item }) {
return (
}>
diff --git a/components/job-notes/job-notes.component.jsx b/components/job-notes/job-notes.component.jsx
index ebc6689..8723bde 100644
--- a/components/job-notes/job-notes.component.jsx
+++ b/components/job-notes/job-notes.component.jsx
@@ -6,7 +6,7 @@ import JobNotesItem from "../job-notes-item/job-notes-item.component";
export default function JobNotes({ job, loading, refetch }) {
const { t } = useTranslation();
- if (!!!job) {
+ if (!job) {
Job is not defined.
;
diff --git a/components/screen-camera/screen-camera.jsx b/components/screen-camera/screen-camera.jsx
index f94edda..03e0f99 100644
--- a/components/screen-camera/screen-camera.jsx
+++ b/components/screen-camera/screen-camera.jsx
@@ -2,7 +2,7 @@ import { useFocusEffect } from "@react-navigation/native";
import { Camera } from "expo-camera";
import * as FileSystem from "expo-file-system";
import React, { useEffect, useRef, useState } from "react";
-import { SafeAreaView, Text, View } from "react-native";
+import { Text, View } from "react-native";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
@@ -45,10 +45,8 @@ export function ScreenCamera({ cameraJobId, addPhoto }) {
useEffect(() => {
(async () => {
- const { status: cameraStatus } = await Camera.requestPermissionsAsync();
- setHasPermission(cameraStatus === "granted");
-
- console.log("Camera Perms:", await Camera.getPermissionsAsync());
+ const { status } = await Camera.requestPermissionsAsync();
+ setHasPermission(status === "granted");
})();
}, []);
@@ -77,13 +75,6 @@ export function ScreenCamera({ cameraJobId, addPhoto }) {
to: newUri,
});
setState({ ...state, capturing: false });
- console.log("Add Photo Object", {
- ...photo,
- id: filename,
- uri: newUri,
- jobId: cameraJobId,
- video: false,
- });
addPhoto({
...photo,
id: filename,
@@ -107,7 +98,13 @@ export function ScreenCamera({ cameraJobId, addPhoto }) {
to: newUri,
});
setState({ ...state, capturing: false });
-
+ console.log("Adding Photo", {
+ ...video,
+ id: filename,
+ uri: newUri,
+ jobId: cameraJobId,
+ video: true,
+ });
addPhoto({
...video,
id: filename,
@@ -129,34 +126,32 @@ export function ScreenCamera({ cameraJobId, addPhoto }) {
const { flashMode, cameraType, capturing } = state;
return (
-
-
+
-
-
+
-
-
-
-
+
+
+
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenCamera);
diff --git a/components/screen-main/screen-main.component.jsx b/components/screen-main/screen-main.component.jsx
index 5a0db8e..863400d 100644
--- a/components/screen-main/screen-main.component.jsx
+++ b/components/screen-main/screen-main.component.jsx
@@ -25,6 +25,7 @@ import ScreenMessagingList from "../screen-messaging-list/screen-messaging-list.
import ScreenSettingsComponent from "../screen-settings/screen-settings.component";
import ScreenSignIn from "../screen-sign-in/screen-sign-in.component";
import ScreenSplash from "../screen-splash/screen-splash.component";
+import ScreenMediaBrowser from "../screen-media-browser/screen-media-browser.component";
const JobStack = createStackNavigator();
const CameraStack = createStackNavigator();
@@ -63,6 +64,15 @@ const JobStackNavigator = () => (
i18n.t("joblist.labels.detail"),
})}
/>
+ ({
+ // title:
+ // (route.params && route.params.title) ||
+ // i18n.t("joblist.labels.detail"),
+ // })}
+ />
);
diff --git a/components/screen-media-browser/screen-media-browser.component.jsx b/components/screen-media-browser/screen-media-browser.component.jsx
new file mode 100644
index 0000000..a451cec
--- /dev/null
+++ b/components/screen-media-browser/screen-media-browser.component.jsx
@@ -0,0 +1,162 @@
+import { Ionicons } from "@expo/vector-icons";
+import { AssetsSelector } from "expo-images-picker";
+import _ from "lodash";
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { StyleSheet, Text, View } from "react-native";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import {
+ selectCurrentCameraJobId,
+ selectDeleteAfterUpload,
+} from "../../redux/app/app.selectors";
+import {
+ selectBodyshop,
+ selectCurrentUser,
+} from "../../redux/user/user.selectors";
+import { handleUpload } from "../../util/document-upload.utility";
+import CameraSelectJob from "../camera-select-job/camera-select-job.component";
+import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component";
+import UploadProgress from "../upload-progress/upload-progress.component";
+import * as FileSystem from "expo-file-system";
+
+const mapStateToProps = createStructuredSelector({
+ currentUser: selectCurrentUser,
+ bodyshop: selectBodyshop,
+ selectedCameraJobId: selectCurrentCameraJobId,
+ deleteAfterUpload: selectDeleteAfterUpload,
+});
+
+export default connect(mapStateToProps, null)(ImageBrowserScreen);
+export function ImageBrowserScreen({
+ currentUser,
+ bodyshop,
+ selectedCameraJobId,
+ deleteAfterUpload,
+}) {
+ const { t } = useTranslation();
+ const [uploads, setUploads] = useState({});
+
+ function handleOnProgress(uri, percent) {
+ setUploads((prevUploads) => ({ ...prevUploads, [uri]: { percent } }));
+ }
+ function handleOnSuccess(uri) {
+ console.log("onSucces!", uri);
+ setTimeout(async function () {
+ setUploads((prevUploads) => _.omit(prevUploads, uri));
+ if (deleteAfterUpload) {
+ await FileSystem.deleteAsync(uri);
+ }
+ }, 1000);
+ }
+
+ const onDone = async (data) => {
+ const actions = [];
+ console.log("Assets :>> ", data);
+ data.forEach(function (p) {
+ const uri = p.uri.split("/").pop();
+ actions.push(
+ handleUpload(
+ {
+ uri: p.uri,
+ onError: handleOnError,
+ onProgress: ({ percent }) => handleOnProgress(uri, percent),
+ onSuccess: () => handleOnSuccess(uri),
+ },
+ {
+ bodyshop: bodyshop,
+ jobId: selectedCameraJobId,
+ uploaded_by: currentUser.email,
+ photo: p,
+ }
+ )
+ );
+ });
+ Promise.all(actions);
+ };
+
+ return (
+
+
+
+ {selectedCameraJobId && (
+ {
+ console.log("Back Function", props);
+ },
+ doneFunction: onDone,
+ },
+ noAssets: {
+ Component: function NoAsset() {
+ return (
+
+ Looks like nothing here
+
+ );
+ },
+ },
+ }}
+ />
+ )}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ flex: {
+ flex: 1,
+ },
+ container: {
+ display: "flex",
+ // position: "relative",
+ },
+ buttonStyle: {
+ //backgroundColor: "tomato",
+ },
+ // eslint-disable-next-line react-native/no-color-literals
+ textStyle: {
+ color: "dodgerblue",
+ },
+});
+
+function handleOnError(...props) {
+ console.log("HandleOnError", props);
+}
diff --git a/components/screen-media-cache/screen-media-cache.component.jsx b/components/screen-media-cache/screen-media-cache.component.jsx
index 00018ac..0ee1283 100644
--- a/components/screen-media-cache/screen-media-cache.component.jsx
+++ b/components/screen-media-cache/screen-media-cache.component.jsx
@@ -1,9 +1,11 @@
-import { Button, Container, Spinner, Text as NBText, View } from "native-base";
+import _ from "lodash";
+import { Button, Spinner, Text as NBText, View } from "native-base";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {
FlatList,
Image,
+ SafeAreaView,
StyleSheet,
Text,
TouchableOpacity,
@@ -38,24 +40,14 @@ export function ScreenMediaCache({
const { t } = useTranslation();
const [previewVisible, setPreviewVisible] = useState(false);
const [imgIndex, setImgIndex] = useState(0);
- const [imagesInDir, setImagesInDir] = useState([]);
- console.log("Photos", photos);
-
- return (
-
-
-
-
-
+ const groupedPhotos = _.groupBy(photos, "jobId");
+ const RenderJobPictures = ({ jobId, jobPhotos }) => (
+
+ {jobId}
item.id}
@@ -83,6 +75,32 @@ export function ScreenMediaCache({
)
}
/>
+
+ );
+
+ return (
+
+
+
+
+
+ item}
+ renderItem={(object) => (
+
+ )}
+ />
{`${photos.length} Photos`}
-
+
);
}
diff --git a/components/screen-splash/screen-splash.component.jsx b/components/screen-splash/screen-splash.component.jsx
index 507a952..56e9eb6 100644
--- a/components/screen-splash/screen-splash.component.jsx
+++ b/components/screen-splash/screen-splash.component.jsx
@@ -1,10 +1,10 @@
+import { Container, Content, H1 } from "native-base";
import React from "react";
-import { StyleSheet, Image } from "react-native";
-import Logo from "../../assets/logo192.png";
import { useTranslation } from "react-i18next";
-import { H1, Container, Content } from "native-base";
-import styles from "../styles";
+import { Image, StyleSheet } from "react-native";
import { BarIndicator } from "react-native-indicators";
+import Logo from "../../assets/logo192.png";
+import styles from "../styles";
export default function ScreenSplash() {
const { t } = useTranslation();
diff --git a/components/sign-in-error-alert/sign-in-error-alert.component.jsx b/components/sign-in-error-alert/sign-in-error-alert.component.jsx
index 51c047f..462f839 100644
--- a/components/sign-in-error-alert/sign-in-error-alert.component.jsx
+++ b/components/sign-in-error-alert/sign-in-error-alert.component.jsx
@@ -1,10 +1,11 @@
-import React, { useState, useEffect } from "react";
+import { Text } from "native-base";
+import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { Container, Content, Text } from "native-base";
-import { View, StyleSheet } from "react-native";
+import { StyleSheet, View } from "react-native";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectSignInError } from "../../redux/user/user.selectors";
+
const mapStateToProps = createStructuredSelector({
signInError: selectSignInError,
});
diff --git a/components/styles.js b/components/styles.js
index 750a02c..1e4e8ad 100644
--- a/components/styles.js
+++ b/components/styles.js
@@ -1,5 +1,4 @@
import { StyleSheet } from "react-native";
-import { Row } from "native-base";
export default StyleSheet.create({
contentContainer__centered: {
diff --git a/components/upload-delete-switch/upload-delete-switch.component.jsx b/components/upload-delete-switch/upload-delete-switch.component.jsx
new file mode 100644
index 0000000..d1607d6
--- /dev/null
+++ b/components/upload-delete-switch/upload-delete-switch.component.jsx
@@ -0,0 +1,48 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { StyleSheet, Switch, Text, View } from "react-native";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { toggleDeleteAfterUpload } from "../../redux/app/app.actions";
+import { selectDeleteAfterUpload } from "../../redux/app/app.selectors";
+
+const mapStateToProps = createStructuredSelector({
+ //currentUser: selectCurrentUser
+ deleteAfterUpload: selectDeleteAfterUpload,
+});
+const mapDispatchToProps = (dispatch) => ({
+ toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
+});
+export default connect(mapStateToProps, mapDispatchToProps)(UploadDeleteSwitch);
+
+export function UploadDeleteSwitch({
+ deleteAfterUpload,
+ toggleDeleteAfterUpload,
+}) {
+ const { t } = useTranslation();
+ return (
+
+
+ {t("mediabrowser.labels.deleteafterupload")}
+
+ toggleDeleteAfterUpload()}
+ value={deleteAfterUpload}
+ />
+
+ );
+}
+const styles = StyleSheet.create({
+ container: {
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ },
+
+ text: {
+ flex: 1,
+ },
+});
diff --git a/components/upload-progress/upload-progress.component.jsx b/components/upload-progress/upload-progress.component.jsx
new file mode 100644
index 0000000..46be88a
--- /dev/null
+++ b/components/upload-progress/upload-progress.component.jsx
@@ -0,0 +1,46 @@
+import React from "react";
+import { ScrollView, StyleSheet, Text, View } from "react-native";
+import * as Progress from "react-native-progress";
+
+export default function UploadProgress({ uploads }) {
+ return (
+
+
+ {Object.keys(uploads).map((key) => (
+
+ {key}
+
+
+
+
+ ))}
+
+
+ );
+}
+const styles = StyleSheet.create({
+ container: {
+ display: "flex",
+ //flex: 1,
+ },
+ progressItem: {
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ marginBottom: 12,
+ },
+ progressText: {
+ flex: 1,
+ },
+ progressBarContainer: {
+ flex: 1,
+ marginLeft: 12,
+ marginRight: 12,
+ },
+});
diff --git a/graphql/jobs.queries.js b/graphql/jobs.queries.js
index 584f2fa..8c60a9d 100644
--- a/graphql/jobs.queries.js
+++ b/graphql/jobs.queries.js
@@ -269,7 +269,7 @@ export const GET_JOB_BY_PK = gql`
date_open
date_scheduled
date_invoiced
- date_closed
+
date_exported
status
owner_owing
@@ -370,7 +370,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
date_invoiced
date_open
date_exported
- date_closed
+
date_scheduled
date_estimated
@@ -426,7 +426,7 @@ export const QUERY_TECH_JOB_DETAILS = gql`
date_invoiced
date_open
date_exported
- date_closed
+
date_scheduled
date_estimated
employee_body
diff --git a/package-lock.json b/package-lock.json
index 280ed31..1c9753d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1108,6 +1108,29 @@
"@types/hammerjs": "^2.0.36"
}
},
+ "@emotion/is-prop-valid": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+ "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+ "requires": {
+ "@emotion/memoize": "0.7.4"
+ }
+ },
+ "@emotion/memoize": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
+ },
+ "@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
"@eslint/eslintrc": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
@@ -2529,6 +2552,16 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
+ "@react-native-community/art": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@react-native-community/art/-/art-1.2.0.tgz",
+ "integrity": "sha512-a+ZcRGl/BzLa89yi33Mbn5SHavsEXqKUMdbfLf3U8MDLElndPqUetoJyGkv63+BcPO49UMWiQRP1YUz6/zfJ+A==",
+ "requires": {
+ "art": "^0.10.3",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.7.2"
+ }
+ },
"@react-native-community/async-storage": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.12.1.tgz",
@@ -3905,6 +3938,11 @@
"function-bind": "^1.1.1"
}
},
+ "art": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/art/-/art-0.10.3.tgz",
+ "integrity": "sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ=="
+ },
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@@ -4007,6 +4045,22 @@
"resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.13.18.tgz",
"integrity": "sha512-f8pAxyKqXBNRIh8l4Sqju055BNec+DQlItdtutByYxULU0iJ1F7evIYE3skPKAkTB/xJH17l+n3Z8dVabGIIGg=="
},
+ "babel-plugin-styled-components": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz",
+ "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.0.0",
+ "@babel/helper-module-imports": "^7.0.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11"
+ }
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
"babel-plugin-syntax-trailing-function-commas": {
"version": "7.0.0-beta.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz",
@@ -4338,6 +4392,11 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
+ "camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ },
"caniuse-lite": {
"version": "1.0.30001185",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz",
@@ -4692,6 +4751,11 @@
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
},
+ "css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
+ },
"css-in-js-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
@@ -4701,6 +4765,16 @@
"isobject": "^3.0.1"
}
},
+ "css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"dayjs": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz",
@@ -5608,6 +5682,11 @@
"fontfaceobserver": "^2.1.0"
}
},
+ "expo-image-manipulator": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-9.0.0.tgz",
+ "integrity": "sha512-hziIcUONz7JtsonEehuYvXk8xCSGDHR6A4ynZcUTbZQCzxWYu7Xq2wahREz/DBdu0vz0LBw38wFLuSpfgaFojQ=="
+ },
"expo-image-picker": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-9.2.1.tgz",
@@ -5624,6 +5703,16 @@
}
}
},
+ "expo-images-picker": {
+ "version": "git+https://github.com/snaptsoft/expo-images-picker.git#9a423d67e4986742bb68b6d3e3118df262b68599",
+ "from": "git+https://github.com/snaptsoft/expo-images-picker.git",
+ "requires": {
+ "expo-image-manipulator": "^9.0.0",
+ "expo-media-library": "~10.0.0",
+ "expo-permissions": "~10.0.0",
+ "styled-components": "^5.1.1"
+ }
+ },
"expo-keep-awake": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-8.4.0.tgz",
@@ -9424,6 +9513,11 @@
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
+ "postcss-value-parser": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
+ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
+ },
"pouchdb-collections": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-1.0.1.tgz",
@@ -10066,6 +10160,15 @@
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz",
"integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg=="
},
+ "react-native-progress": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-4.1.2.tgz",
+ "integrity": "sha512-sFHs6k6npWDOyvQoL2NeyOyHb+q1s8iOAOeyzoObN77zkxOAsgJt9FcSJLRq70Mw7qSGNJMFDqCgvYR1huYRwQ==",
+ "requires": {
+ "@react-native-community/art": "^1.1.2",
+ "prop-types": "^15.7.2"
+ }
+ },
"react-native-reanimated": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.13.2.tgz",
@@ -10596,6 +10699,11 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -11000,6 +11108,23 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
+ "styled-components": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.2.1.tgz",
+ "integrity": "sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^0.8.8",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ }
+ },
"subscriptions-transport-ws": {
"version": "0.9.18",
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz",
diff --git a/package.json b/package.json
index 5fb3bbb..b12d7b7 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"formik": "^2.2.5",
"graphql": "^15.4.0",
"i18next": "^19.8.4",
+ "lodash": "^4.17.20",
"luxon": "^1.25.0",
"native-base": "^2.13.15",
"react": "16.13.1",
@@ -45,6 +46,7 @@
"react-native-gesture-handler": "~1.8.0",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-indicators": "^0.17.0",
+ "react-native-progress": "^4.1.2",
"react-native-reanimated": "~1.13.0",
"react-native-screens": "~2.15.0",
"react-native-web": "~0.13.12",
@@ -55,7 +57,8 @@
"redux-saga": "^1.1.3",
"reselect": "^4.0.0",
"sentry-expo": "^3.0.4",
- "subscriptions-transport-ws": "^0.9.18"
+ "subscriptions-transport-ws": "^0.9.18",
+ "expo-images-picker": "https://github.com/snaptsoft/expo-images-picker/"
},
"devDependencies": {
"@babel/core": "^7.12.13",
diff --git a/redux/app/app.actions.js b/redux/app/app.actions.js
index b9af5db..1aeb536 100644
--- a/redux/app/app.actions.js
+++ b/redux/app/app.actions.js
@@ -24,3 +24,6 @@ export const documentUploadFailure = (error) => ({
type: AppActionTypes.DOCUMENT_UPLOAD_FAILURE,
payload: error,
});
+export const toggleDeleteAfterUpload = () => ({
+ type: AppActionTypes.TOGGLE_DLETE_AFTER_UPLOAD,
+});
diff --git a/redux/app/app.reducer.js b/redux/app/app.reducer.js
index c22f6e9..c267510 100644
--- a/redux/app/app.reducer.js
+++ b/redux/app/app.reducer.js
@@ -5,6 +5,7 @@ const INITIAL_STATE = {
cameraJob: null,
documentUploadInProgress: null,
documentUploadError: null,
+ deleteAfterUpload: true,
};
const appReducer = (state = INITIAL_STATE, action) => {
@@ -37,6 +38,11 @@ const appReducer = (state = INITIAL_STATE, action) => {
documentUploadError: action.payload,
documentUploadInProgress: null,
};
+ case AppActionTypes.TOGGLE_DLETE_AFTER_UPLOAD:
+ return {
+ ...state,
+ deleteAfterUpload: !state.deleteAfterUpload,
+ };
default:
return state;
}
diff --git a/redux/app/app.selectors.js b/redux/app/app.selectors.js
index 1ea895a..4626679 100644
--- a/redux/app/app.selectors.js
+++ b/redux/app/app.selectors.js
@@ -6,15 +6,23 @@ export const selectCurrentCameraJobId = createSelector(
[selectApp],
(app) => app.cameraJobId
);
+
export const selectCurrentCameraJob = createSelector(
[selectApp],
(app) => app.cameraJob
);
+
export const selectDocumentUploadInProgress = createSelector(
[selectApp],
(app) => app.documentUploadInProgress
);
+
export const selectDocumentUploadError = createSelector(
[selectApp],
(app) => app.documentUploadError
);
+
+export const selectDeleteAfterUpload = createSelector(
+ [selectApp],
+ (app) => app.deleteAfterUpload
+);
diff --git a/redux/app/app.types.js b/redux/app/app.types.js
index d706bb7..029d8e0 100644
--- a/redux/app/app.types.js
+++ b/redux/app/app.types.js
@@ -4,5 +4,6 @@ const AppActionTypes = {
DOCUMENT_UPLOAD_START: "DOCUMENT_UPLOAD_START",
DOCUMENT_UPLOAD_SUCCESS: "DOCUMENT_UPLOAD_SUCCESS",
DOCUMENT_UPLOAD_FAILURE: "DOCUMENT_UPLOAD_FAILURE",
+ TOGGLE_DLETE_AFTER_UPLOAD: "TOGGLE_DLETE_AFTER_UPLOAD",
};
export default AppActionTypes;
diff --git a/translations/en-US/common.json b/translations/en-US/common.json
index c284e86..1b95f6c 100644
--- a/translations/en-US/common.json
+++ b/translations/en-US/common.json
@@ -72,6 +72,15 @@
"jobtab": "Jobs"
}
},
+ "mediabrowser": {
+ "actions": {
+ "upload": "Upload"
+ },
+ "labels": {
+ "deleteafterupload": "Delete After Upload",
+ "selectjob": "Please select a job."
+ }
+ },
"mediacache": {
"actions": {
"deleteall": "Delete All",
diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json
index eac291d..459616c 100644
--- a/translations/es-MX/common.json
+++ b/translations/es-MX/common.json
@@ -72,6 +72,15 @@
"jobtab": ""
}
},
+ "mediabrowser": {
+ "actions": {
+ "upload": ""
+ },
+ "labels": {
+ "deleteafterupload": "",
+ "selectjob": ""
+ }
+ },
"mediacache": {
"actions": {
"deleteall": "",
diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json
index 4519d5a..e67a01e 100644
--- a/translations/fr-CA/common.json
+++ b/translations/fr-CA/common.json
@@ -72,6 +72,15 @@
"jobtab": ""
}
},
+ "mediabrowser": {
+ "actions": {
+ "upload": ""
+ },
+ "labels": {
+ "deleteafterupload": "",
+ "selectjob": ""
+ }
+ },
"mediacache": {
"actions": {
"deleteall": "",
diff --git a/util/document-upload.utility.js b/util/document-upload.utility.js
index f5d27f2..d0bb9fa 100644
--- a/util/document-upload.utility.js
+++ b/util/document-upload.utility.js
@@ -10,7 +10,6 @@ var cleanAxios = axios.create();
cleanAxios.interceptors.request.eject(axiosAuthInterceptorId);
export const handleUpload = async (ev, context) => {
- console.log("ev,context", ev, context);
const { onError, onSuccess, onProgress } = ev;
const { bodyshop, jobId } = context;
@@ -74,7 +73,7 @@ export const uploadToCloudinary = async (
if (signedURLResponse.status !== 200) {
console.log("Error Getting Signed URL", signedURLResponse.statusText);
- if (!!onError) onError(signedURLResponse.statusText);
+ if (onError) onError(signedURLResponse.statusText);
return { success: false, error: signedURLResponse.statusText };
}
@@ -84,7 +83,7 @@ export const uploadToCloudinary = async (
var options = {
headers: { "X-Requested-With": "XMLHttpRequest" },
onUploadProgress: (e) => {
- if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
+ if (onProgress) onProgress({ percent: e.loaded / e.total });
},
};
const formData = new FormData();
@@ -94,7 +93,7 @@ export const uploadToCloudinary = async (
type: fileType,
name: file.data.name,
});
- console.log("Applying lower quality transforms.");
+
formData.append("upload_preset", "incoming_upload");
formData.append("api_key", env.REACT_APP_CLOUDINARY_API_KEY);
@@ -113,7 +112,7 @@ export const uploadToCloudinary = async (
...options,
}
);
- console.log("Cloudinary Upload Response", cloudinaryUploadResponse.data);
+ // console.log("Cloudinary Upload Response", cloudinaryUploadResponse.data);
} catch (error) {
console.log("CLOUDINARY error", error, cloudinaryUploadResponse);
return { success: false, error: error };
@@ -125,7 +124,7 @@ export const uploadToCloudinary = async (
cloudinaryUploadResponse.statusText,
cloudinaryUploadResponse
);
- if (!!onError) onError(cloudinaryUploadResponse.statusText);
+ if (onError) onError(cloudinaryUploadResponse.statusText);
return { success: false, error: cloudinaryUploadResponse.statusText };
}
@@ -145,7 +144,7 @@ export const uploadToCloudinary = async (
},
});
if (!documentInsert.errors) {
- if (!!onSuccess)
+ if (onSuccess)
onSuccess({
uid: documentInsert.data.insert_documents.returning[0].id,
name: documentInsert.data.insert_documents.returning[0].name,
@@ -159,7 +158,7 @@ export const uploadToCloudinary = async (
callback();
}
} else {
- if (!!onError) onError(JSON.stringify(documentInsert.errors));
+ if (onError) onError(JSON.stringify(documentInsert.errors));
// notification["error"]({
// message: i18n.t("documents.errors.insert", {
// message: JSON.stringify(JSON.stringify(documentInsert.errors)),