Delete unused code, remove logs, add dnd package.
This commit is contained in:
@@ -15,7 +15,7 @@ export default ({ config }) => {
|
|||||||
projectId: IS_ROME ? "df105e21-a07f-4425-af10-2200a7704a48" : "ffe01f3a-d507-4698-82cd-da1f1cad450b"
|
projectId: IS_ROME ? "df105e21-a07f-4425-af10-2200a7704a48" : "ffe01f3a-d507-4698-82cd-da1f1cad450b"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: IS_ROME ? "./assets/RomeIcon.png" : "./assets/logo192noa.png",
|
icon: IS_ROME ? "./assets/RomeIcon.png" : "./assets/ImEXlogo192noa.png",
|
||||||
ios: {
|
ios: {
|
||||||
...config.ios,
|
...config.ios,
|
||||||
bundleIdentifier: IS_ROME ? "com.rome.mobile" : "com.imex.imexmobile"
|
bundleIdentifier: IS_ROME ? "com.rome.mobile" : "com.imex.imexmobile"
|
||||||
@@ -55,7 +55,5 @@ export default ({ config }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("New Expo Config:", JSON.stringify(newConfig, null, 2));
|
|
||||||
return newConfig;
|
return newConfig;
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { checkUserSession } from "@/redux/user/user.actions";
|
import { checkUserSession } from "@/redux/user/user.actions";
|
||||||
import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors";
|
||||||
import { ApolloProvider } from "@apollo/client";
|
import { ApolloProvider } from "@apollo/client";
|
||||||
|
import { loadDevMessages, loadErrorMessages } from "@apollo/client/dev";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import {
|
import {
|
||||||
DarkTheme,
|
DarkTheme,
|
||||||
@@ -28,6 +29,9 @@ import { persistor, store } from "../redux/store";
|
|||||||
import "../translations/i18n";
|
import "../translations/i18n";
|
||||||
import { registerForPushNotificationsAsync } from "../util/notificationHandler";
|
import { registerForPushNotificationsAsync } from "../util/notificationHandler";
|
||||||
|
|
||||||
|
loadDevMessages();
|
||||||
|
loadErrorMessages();
|
||||||
|
|
||||||
function AuthenticatedLayout() {
|
function AuthenticatedLayout() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const paperTheme = usePaperTheme();
|
const paperTheme = usePaperTheme();
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
// // src/toolbar.component.js file
|
|
||||||
// import { Ionicons } from "@expo/vector-icons";
|
|
||||||
// import { Camera } from "expo-camera";
|
|
||||||
// import React from "react";
|
|
||||||
// import {
|
|
||||||
// StyleSheet,
|
|
||||||
// TouchableOpacity,
|
|
||||||
// TouchableWithoutFeedback,
|
|
||||||
// View,
|
|
||||||
// } from "react-native";
|
|
||||||
|
|
||||||
// const styles = StyleSheet.create({
|
|
||||||
// alignCenter: {
|
|
||||||
// flex: 1,
|
|
||||||
// alignItems: "center",
|
|
||||||
// justifyContent: "center",
|
|
||||||
// },
|
|
||||||
// bottomToolbar: {
|
|
||||||
// marginTop: "auto",
|
|
||||||
// height: 100,
|
|
||||||
// display: "flex",
|
|
||||||
// justifyContent: "space-evenly",
|
|
||||||
// alignItems: "center",
|
|
||||||
// flexDirection: "row",
|
|
||||||
// },
|
|
||||||
// captureBtn: {
|
|
||||||
// width: 60,
|
|
||||||
// height: 60,
|
|
||||||
// borderWidth: 2,
|
|
||||||
// borderRadius: 60,
|
|
||||||
// borderColor: "#FFFFFF",
|
|
||||||
// },
|
|
||||||
// captureBtnActive: {
|
|
||||||
// width: 80,
|
|
||||||
// height: 80,
|
|
||||||
// },
|
|
||||||
// captureBtnInternal: {
|
|
||||||
// width: 76,
|
|
||||||
// height: 76,
|
|
||||||
// borderWidth: 2,
|
|
||||||
// borderRadius: 76,
|
|
||||||
// backgroundColor: "red",
|
|
||||||
// borderColor: "transparent",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const { FlashMode: CameraFlashModes, Type: CameraTypes } = Camera.Constants;
|
|
||||||
|
|
||||||
// export default function CameraControls({
|
|
||||||
// capturing = false,
|
|
||||||
// cameraType = CameraTypes.back,
|
|
||||||
// flashMode = CameraFlashModes.off,
|
|
||||||
// setFlashMode,
|
|
||||||
// setCameraType,
|
|
||||||
// onCaptureIn,
|
|
||||||
// onCaptureOut,
|
|
||||||
// onLongCapture,
|
|
||||||
// onShortCapture,
|
|
||||||
// }) {
|
|
||||||
// return (
|
|
||||||
// <View style={styles.bottomToolbar}>
|
|
||||||
// <TouchableOpacity
|
|
||||||
// onPress={() =>
|
|
||||||
// setFlashMode(
|
|
||||||
// flashMode === CameraFlashModes.on
|
|
||||||
// ? CameraFlashModes.off
|
|
||||||
// : CameraFlashModes.on
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// >
|
|
||||||
// <Ionicons
|
|
||||||
// name={flashMode == CameraFlashModes.on ? "md-flash" : "md-flash-off"}
|
|
||||||
// color="white"
|
|
||||||
// size={30}
|
|
||||||
// />
|
|
||||||
// </TouchableOpacity>
|
|
||||||
|
|
||||||
// <TouchableWithoutFeedback
|
|
||||||
// onPressIn={onCaptureIn}
|
|
||||||
// onPressOut={onCaptureOut}
|
|
||||||
// onLongPress={onLongCapture}
|
|
||||||
// onPress={onShortCapture}
|
|
||||||
// disabled={capturing}
|
|
||||||
// >
|
|
||||||
// <View style={[styles.captureBtn, capturing && styles.captureBtnActive]}>
|
|
||||||
// {capturing && <View style={styles.captureBtnInternal} />}
|
|
||||||
// </View>
|
|
||||||
// </TouchableWithoutFeedback>
|
|
||||||
|
|
||||||
// <TouchableOpacity
|
|
||||||
// onPress={() =>
|
|
||||||
// setCameraType(
|
|
||||||
// cameraType === CameraTypes.back
|
|
||||||
// ? CameraTypes.front
|
|
||||||
// : CameraTypes.back
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// >
|
|
||||||
// <Ionicons name="md-reverse-camera" color="white" size={30} />
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// </View>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { FlatList, RefreshControl, View } from "react-native";
|
|
||||||
import { Button, List, Modal, Portal, Searchbar } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
|
||||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
|
||||||
import {
|
|
||||||
selectCurrentCameraJob,
|
|
||||||
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";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
cameraJobId: selectCurrentCameraJobId,
|
|
||||||
cameraJob: selectCurrentCameraJob,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
|
||||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function CameraSelectJob({
|
|
||||||
bodyshop,
|
|
||||||
cameraJobId,
|
|
||||||
setCameraJobId,
|
|
||||||
cameraJob,
|
|
||||||
setCameraJob,
|
|
||||||
}) {
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
|
||||||
variables: {
|
|
||||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
|
||||||
},
|
|
||||||
skip: !bodyshop,
|
|
||||||
});
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [visible, setVisible] = React.useState(false);
|
|
||||||
const [searchQuery, setSearchQuery] = React.useState("");
|
|
||||||
if (loading) return <LoadingDisplay />;
|
|
||||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
|
||||||
|
|
||||||
const showModal = () => setVisible(true);
|
|
||||||
const hideModal = () => setVisible(false);
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeSearch = (query) => setSearchQuery(query);
|
|
||||||
|
|
||||||
const jobs = data
|
|
||||||
? searchQuery === ""
|
|
||||||
? data.jobs
|
|
||||||
: data.jobs.filter(
|
|
||||||
(j) =>
|
|
||||||
(j.ro_number || "")
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.ownr_co_nm || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.ownr_fn || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.ownr_ln || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.plate_no || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.v_model_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.v_make_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase())
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Portal>
|
|
||||||
<Modal
|
|
||||||
visible={visible}
|
|
||||||
onDismiss={hideModal}
|
|
||||||
// eslint-disable-next-line react-native/no-color-literals
|
|
||||||
contentContainerStyle={{
|
|
||||||
paddingTop: 20,
|
|
||||||
paddingBottom: 20,
|
|
||||||
margin: 12,
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: "white",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
margin: 8,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button onPress={() => hideModal()}>
|
|
||||||
<Ionicons name="arrow-back" size={32} color="dodgerblue" />
|
|
||||||
</Button>
|
|
||||||
<Searchbar
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
onChangeText={onChangeSearch}
|
|
||||||
value={searchQuery}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<FlatList
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
data={[{ id: "temp", ro_number: "Temporary Storage" }, ...jobs]}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
renderItem={(object) => (
|
|
||||||
<List.Item
|
|
||||||
onPress={() => {
|
|
||||||
setCameraJobId(object.item.id);
|
|
||||||
setCameraJob(object.item);
|
|
||||||
hideModal();
|
|
||||||
setSearchQuery("");
|
|
||||||
}}
|
|
||||||
left={() => {
|
|
||||||
if (object.item.id !== cameraJobId) return null;
|
|
||||||
return (
|
|
||||||
<Ionicons
|
|
||||||
name="checkmark-circle"
|
|
||||||
size={24}
|
|
||||||
color="dodgerblue"
|
|
||||||
style={{ alignSelf: "center" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
titleStyle={{
|
|
||||||
...(object.item.id === cameraJobId
|
|
||||||
? { color: "dodgerblue" }
|
|
||||||
: {}),
|
|
||||||
}}
|
|
||||||
title={`${
|
|
||||||
object.item.ro_number ? `${object.item.ro_number} ` : ``
|
|
||||||
}${object.item.ownr_fn || ""} ${object.item.ownr_ln || ""} ${
|
|
||||||
object.item.ownr_co_nm || ""
|
|
||||||
} ${
|
|
||||||
object.item.v_model_yr ? `- ${object.item.v_model_yr}` : ""
|
|
||||||
} ${
|
|
||||||
object.item.v_make_desc ? `- ${object.item.v_make_desc}` : ""
|
|
||||||
} ${
|
|
||||||
object.item.v_model_desc
|
|
||||||
? `- ${object.item.v_model_desc}`
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
key={object.item.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</Portal>
|
|
||||||
<Button mode="outlined" style={{ margin: 8 }} onPress={showModal}>
|
|
||||||
{cameraJobId
|
|
||||||
? cameraJobId === "temp"
|
|
||||||
? t("mediabrowser.labels.temporarystorage")
|
|
||||||
: `${cameraJob.ro_number ? `${cameraJob.ro_number} - ` : ``}${
|
|
||||||
cameraJob.ownr_fn || ""
|
|
||||||
} ${cameraJob.ownr_ln || ""} ${cameraJob.ownr_co_nm || ""} - ${
|
|
||||||
cameraJob.v_model_yr || ""
|
|
||||||
} ${cameraJob.v_make_desc || ""} ${cameraJob.v_model_desc || ""}`
|
|
||||||
: t("mediabrowser.labels.selectjob")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CameraSelectJob);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { View, Text } from "react-native";
|
|
||||||
|
|
||||||
export default function ErrorDisplay({ errorMessage }) {
|
|
||||||
return (
|
|
||||||
<View style={{ backgroundColor: "red" }}>
|
|
||||||
<Text>{errorMessage}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { View, Text } from "react-native";
|
|
||||||
|
|
||||||
export default function FlatListItemSeparator() {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 1,
|
|
||||||
width: "100%",
|
|
||||||
backgroundColor: "#000",
|
|
||||||
opacity: 0.2,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
FlatList,
|
|
||||||
Image,
|
|
||||||
RefreshControl,
|
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
|
|
||||||
import * as Sentry from "@sentry/react-native";
|
|
||||||
import Toast from "react-native-toast-message";
|
|
||||||
|
|
||||||
import cleanAxios from "../../util/CleanAxios";
|
|
||||||
|
|
||||||
export default function JobDocumentsLocalComponent({ bodyshop, job }) {
|
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
|
||||||
const [images, setImages] = useState([]);
|
|
||||||
const [imgIndex, setImgIndex] = useState(0);
|
|
||||||
useEffect(() => {
|
|
||||||
if (job.id) {
|
|
||||||
getPhotos({ bodyshop, jobid: job.id, setImages });
|
|
||||||
}
|
|
||||||
}, [job.id, bodyshop]);
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return getPhotos({ bodyshop, jobid: job.id, setImages });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<FlatList
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={false} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
data={images}
|
|
||||||
numColumns={4}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
renderItem={(object) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1 / 4, aspectRatio: 1, margin: 4 }}
|
|
||||||
onPress={async () => {
|
|
||||||
setImgIndex(object.index);
|
|
||||||
setPreviewVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
resizeMode="cover"
|
|
||||||
source={{
|
|
||||||
uri: object.item.thumbUrl,
|
|
||||||
aspectRatio: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Text>
|
|
||||||
{images?.filter((d) => d.type?.mime?.startsWith("image")).length}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<MediaCacheOverlay
|
|
||||||
photos={images}
|
|
||||||
imgIndex={imgIndex}
|
|
||||||
setImgIndex={setImgIndex}
|
|
||||||
previewVisible={previewVisible}
|
|
||||||
setPreviewVisible={setPreviewVisible}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPhotos({ bodyshop, jobid, setImages }) {
|
|
||||||
let localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
|
|
||||||
if (localmediaserverhttp.endsWith("/")) {
|
|
||||||
localmediaserverhttp = localmediaserverhttp.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const imagesFetch = await cleanAxios.post(
|
|
||||||
`${localmediaserverhttp}/jobs/list`,
|
|
||||||
{
|
|
||||||
jobid,
|
|
||||||
},
|
|
||||||
{ headers: { ims_token: bodyshop.localmediatoken } }
|
|
||||||
);
|
|
||||||
|
|
||||||
const normalizedImages = imagesFetch.data
|
|
||||||
.filter((d) => d.type?.mime?.startsWith("image"))
|
|
||||||
.map((d, idx) => {
|
|
||||||
return {
|
|
||||||
...d,
|
|
||||||
// src: `${localmediaserverhttp}/${d.src}`,
|
|
||||||
|
|
||||||
uri: `${localmediaserverhttp}${d.src}`,
|
|
||||||
thumbUrl: `${localmediaserverhttp}${d.thumbnail}`,
|
|
||||||
id: idx,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
setImages(normalizedImages);
|
|
||||||
} catch (error) {
|
|
||||||
Sentry.captureException(error);
|
|
||||||
Toast.show({
|
|
||||||
type: "error",
|
|
||||||
text1: `Error fetching photos.`,
|
|
||||||
|
|
||||||
text2: JSON.stringify(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
FlatList,
|
|
||||||
Image,
|
|
||||||
RefreshControl,
|
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import env from "../../env";
|
|
||||||
import { DetermineFileType } from "../../util/document-upload.utility";
|
|
||||||
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
//import { splitClient } from "../screen-main/screen-main.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(JobDocumentsComponent);
|
|
||||||
|
|
||||||
export function JobDocumentsComponent({ bodyshop, job, loading, refetch }) {
|
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
|
||||||
const [fullphotos, setFullPhotos] = useState([]);
|
|
||||||
const [imgIndex, setImgIndex] = useState(0);
|
|
||||||
|
|
||||||
const useImgproxy = splitClient?.getTreatment("Imgproxy");
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function getPhotos() {
|
|
||||||
if (useImgproxy) {
|
|
||||||
const result = await axios.post(
|
|
||||||
`${env.API_URL}/media/imgproxy/thumbnails`,
|
|
||||||
{
|
|
||||||
jobid: job.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
setFullPhotos(
|
|
||||||
result.data.map((doc, idx) => {
|
|
||||||
return {
|
|
||||||
id: idx,
|
|
||||||
videoUrl:
|
|
||||||
DetermineFileType(doc.type) === "video" &&
|
|
||||||
doc.originalUrlViaProxyPath,
|
|
||||||
source:
|
|
||||||
DetermineFileType(doc.type) === "video"
|
|
||||||
? { uri: doc.thumbnailUrl }
|
|
||||||
: { uri: doc.originalUrl },
|
|
||||||
url:
|
|
||||||
DetermineFileType(doc.type) === "video"
|
|
||||||
? doc.thumbnailUrl
|
|
||||||
: doc.originalUrl,
|
|
||||||
uri:
|
|
||||||
DetermineFileType(doc.type) === "video"
|
|
||||||
? doc.originalUrlViaProxyPath
|
|
||||||
: doc.originalUrl,
|
|
||||||
thumbUrl: doc.thumbnailUrl,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setFullPhotos(
|
|
||||||
job.documents.map((doc, idx) => {
|
|
||||||
return {
|
|
||||||
id: idx,
|
|
||||||
videoUrl:
|
|
||||||
DetermineFileType(doc.type) === "video" && GenerateSrcUrl(doc),
|
|
||||||
source:
|
|
||||||
DetermineFileType(doc.type) === "video"
|
|
||||||
? { uri: GenerateThumbUrl(doc) }
|
|
||||||
: { uri: GenerateSrcUrl(doc) },
|
|
||||||
url:
|
|
||||||
DetermineFileType(doc.type) === "video"
|
|
||||||
? GenerateThumbUrl(doc)
|
|
||||||
: GenerateSrcUrl(doc),
|
|
||||||
uri:
|
|
||||||
DetermineFileType(doc.type) === "video"
|
|
||||||
? GenerateThumbUrl(doc)
|
|
||||||
: GenerateSrcUrl(doc),
|
|
||||||
thumbUrl: GenerateThumbUrl(doc),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPhotos();
|
|
||||||
}, [job.documents]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<FlatList
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
data={fullphotos}
|
|
||||||
numColumns={4}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
renderItem={(object) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1 / 4, aspectRatio: 1, margin: 4 }}
|
|
||||||
onPress={async () => {
|
|
||||||
setImgIndex(object.index);
|
|
||||||
setPreviewVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
resizeMode="cover"
|
|
||||||
source={{
|
|
||||||
uri: object.item.thumbUrl,
|
|
||||||
aspectRatio: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={{ textAlign: "center", color: useImgproxy ? "blue" : "black" }}
|
|
||||||
>
|
|
||||||
{fullphotos.length}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<MediaCacheOverlay
|
|
||||||
photos={fullphotos}
|
|
||||||
imgIndex={imgIndex}
|
|
||||||
setImgIndex={setImgIndex}
|
|
||||||
previewVisible={previewVisible}
|
|
||||||
setPreviewVisible={setPreviewVisible}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GenerateSrcUrl = (value) => {
|
|
||||||
let extension = value.extension;
|
|
||||||
if (extension && extension.toLowerCase().includes("heic")) extension = "jpg";
|
|
||||||
|
|
||||||
return `${env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
|
||||||
value.type
|
|
||||||
)}/upload/${value.key}${extension ? `.${extension}` : ""}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GenerateThumbUrl = (value) => {
|
|
||||||
let extension = value.extension;
|
|
||||||
if (extension && extension.includes("heic")) extension = "jpg";
|
|
||||||
else if (
|
|
||||||
DetermineFileType(value.type) !== "image" ||
|
|
||||||
(value.type && value.type.includes("application"))
|
|
||||||
)
|
|
||||||
extension = "jpg";
|
|
||||||
|
|
||||||
return `${env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
|
||||||
value.type
|
|
||||||
)}/upload/${env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}${
|
|
||||||
extension ? `.${extension}` : ""
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";
|
|
||||||
import { Card, DataTable } from "react-native-paper";
|
|
||||||
|
|
||||||
export default function JobLines({ job, loading, refetch }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
if (!job) {
|
|
||||||
<Card>
|
|
||||||
<Text>Job is not defined.</Text>
|
|
||||||
</Card>;
|
|
||||||
}
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<DataTable>
|
|
||||||
<DataTable.Header>
|
|
||||||
<DataTable.Title style={{ flex: 4 }}>
|
|
||||||
{t("jobdetail.labels.lines_desc")}
|
|
||||||
</DataTable.Title>
|
|
||||||
<DataTable.Title style={{ flex: 2 }}>
|
|
||||||
{t("jobdetail.labels.lines_lbr_ty")}
|
|
||||||
</DataTable.Title>
|
|
||||||
<DataTable.Title style={{ flex: 1 }}>
|
|
||||||
{t("jobdetail.labels.lines_lb_hrs")}
|
|
||||||
</DataTable.Title>
|
|
||||||
<DataTable.Title style={{ flex: 2 }}>
|
|
||||||
{t("jobdetail.labels.lines_part_type")}
|
|
||||||
</DataTable.Title>
|
|
||||||
<DataTable.Title style={{ flex: 1 }}>
|
|
||||||
{t("jobdetail.labels.lines_qty")}
|
|
||||||
</DataTable.Title>
|
|
||||||
</DataTable.Header>
|
|
||||||
</DataTable>
|
|
||||||
|
|
||||||
<FlatList
|
|
||||||
data={job.joblines}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
renderItem={(object) => (
|
|
||||||
<DataTable.Row>
|
|
||||||
<DataTable.Cell style={{ flex: 4 }}>
|
|
||||||
{object.item.line_desc}
|
|
||||||
</DataTable.Cell>
|
|
||||||
<DataTable.Cell style={{ flex: 2 }}>
|
|
||||||
{object.item.mod_lbr_ty &&
|
|
||||||
t(`jobdetail.lbr_types.${object.item.mod_lbr_ty}`)}
|
|
||||||
</DataTable.Cell>
|
|
||||||
<DataTable.Cell style={{ flex: 1 }}>
|
|
||||||
{object.item.mod_lb_hrs}
|
|
||||||
</DataTable.Cell>
|
|
||||||
<DataTable.Cell style={{ flex: 2 }}>
|
|
||||||
{object.item.part_type &&
|
|
||||||
t(`jobdetail.part_types.${object.item.part_type}`)}
|
|
||||||
</DataTable.Cell>
|
|
||||||
<DataTable.Cell style={{ flex: 1 }}>
|
|
||||||
{object.item.part_qty}
|
|
||||||
</DataTable.Cell>
|
|
||||||
</DataTable.Row>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const localStyles = StyleSheet.create({});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import { useNavigation } from "@react-navigation/native";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Button, List, Title } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
|
||||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
|
||||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigation = useNavigation();
|
|
||||||
// const _swipeableRow = useRef(null);
|
|
||||||
|
|
||||||
// const RenderRightAction = (progress, dragX) => {
|
|
||||||
// const scale = dragX.interpolate({
|
|
||||||
// inputRange: [-100, 0],
|
|
||||||
// outputRange: [0.7, 0],
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <TouchableOpacity
|
|
||||||
// style={[styles.swipe_view, styles.swipe_view_blue]}
|
|
||||||
// onPress={() => {
|
|
||||||
// logImEXEvent("imexmobile_setcamerajobid_swipe");
|
|
||||||
// setCameraJobId(item.id);
|
|
||||||
// setCameraJob(item);
|
|
||||||
// navigation.navigate("MediaBrowserTab");
|
|
||||||
// _swipeableRow.current.close();
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <Animated.View
|
|
||||||
// style={{
|
|
||||||
// transform: [{ scale }],
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <Ionicons name="ios-camera" size={64} color="white" />
|
|
||||||
// </Animated.View>
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
const onPress = () => {
|
|
||||||
logImEXEvent("imexmobile_view_job_detail");
|
|
||||||
navigation.push("JobDetail", {
|
|
||||||
jobId: item.id,
|
|
||||||
title: item.ro_number || t("general.labels.na"),
|
|
||||||
job: item,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<List.Item
|
|
||||||
onPress={onPress}
|
|
||||||
title={<Title>{item.ro_number || t("general.labels.na")}</Title>}
|
|
||||||
description={`${item.ownr_fn || ""} ${item.ownr_ln || ""} ${
|
|
||||||
item.ownr_co_nm || ""
|
|
||||||
} - ${item.v_model_yr || ""} ${item.v_make_desc || ""} ${
|
|
||||||
item.v_model_desc || ""
|
|
||||||
}`}
|
|
||||||
right={({ style }) => (
|
|
||||||
<Button
|
|
||||||
style={[style, { alignSelf: "center" }]}
|
|
||||||
onPress={() => {
|
|
||||||
logImEXEvent("imexmobile_setcamerajobid_row");
|
|
||||||
setCameraJobId(item.id);
|
|
||||||
setCameraJob(item);
|
|
||||||
navigation.navigate("MediaBrowserTab");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Ionicons
|
|
||||||
style={[style, { alignSelf: "center" }]}
|
|
||||||
name="add"
|
|
||||||
size={32}
|
|
||||||
color="dodgerblue"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobListItem);
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { FlatList, RefreshControl, Text, View } from "react-native";
|
|
||||||
import { Button, Searchbar, Title } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import ErrorDisplay from "../error-display/error-display.component";
|
|
||||||
import JobListItem from "../job-list-item/job-list-item.component";
|
|
||||||
import LoadingDisplay from "../loading-display/loading-display.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function JobListComponent({ bodyshop }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [searchQuery, setSearchQuery] = React.useState("");
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
|
||||||
variables: {
|
|
||||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
|
||||||
},
|
|
||||||
skip: !bodyshop,
|
|
||||||
notifyOnNetworkStatusChange: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return refetch();
|
|
||||||
};
|
|
||||||
const onChangeSearch = (query) => setSearchQuery(query);
|
|
||||||
|
|
||||||
if (loading) return <LoadingDisplay />;
|
|
||||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
|
||||||
if (data && data.jobs && data.jobs.length === 0)
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
||||||
<Title>
|
|
||||||
<Text>{t("joblist.labels.nojobs")}</Text>
|
|
||||||
</Title>
|
|
||||||
<Button onPress={() => refetch()}>
|
|
||||||
{t("joblist.actions.refresh")}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const jobs = data
|
|
||||||
? searchQuery === ""
|
|
||||||
? data.jobs
|
|
||||||
: data.jobs.filter(
|
|
||||||
(j) =>
|
|
||||||
(j.ro_number || "")
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.ownr_co_nm || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.ownr_fn || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.ownr_ln || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.v_model_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
(j.v_make_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase())
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Searchbar
|
|
||||||
onChangeText={onChangeSearch}
|
|
||||||
value={searchQuery}
|
|
||||||
placeholder={t("joblist.labels.search")}
|
|
||||||
/>
|
|
||||||
<FlatList
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
data={jobs}
|
|
||||||
renderItem={(object) => <JobListItem item={object.item} />}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(JobListComponent);
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { AntDesign } from "@expo/vector-icons";
|
|
||||||
import { DateTime } from "luxon";
|
|
||||||
import React from "react";
|
|
||||||
import { Text, View } from "react-native";
|
|
||||||
import { Card } from "react-native-paper";
|
|
||||||
export default function NoteListItem({ item }) {
|
|
||||||
return (
|
|
||||||
<Card style={{ margin: 8 }}>
|
|
||||||
<Card.Content>
|
|
||||||
<View style={{ display: "flex", flex: 1 }}>
|
|
||||||
<Text>{item.text}</Text>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "column",
|
|
||||||
alignSelf: "flex-end",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.private && (
|
|
||||||
<AntDesign
|
|
||||||
name="eyeo"
|
|
||||||
style={{ margin: 4 }}
|
|
||||||
size={24}
|
|
||||||
color="black"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{item.critical && (
|
|
||||||
<AntDesign
|
|
||||||
name="warning"
|
|
||||||
style={{ margin: 4 }}
|
|
||||||
size={24}
|
|
||||||
color="tomato"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Text style={{ fontSize: 12 }}>{item.created_by}</Text>
|
|
||||||
<Text style={{ fontSize: 12 }}>
|
|
||||||
{DateTime.fromISO(item.created_at).toLocaleString(
|
|
||||||
DateTime.DATETIME_SHORT
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { FlatList, RefreshControl, Text } from "react-native";
|
|
||||||
import { Card } from "react-native-paper";
|
|
||||||
import JobNotesItem from "../job-notes-item/job-notes-item.component";
|
|
||||||
export default function JobNotes({ job, loading, refetch }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
if (!job) {
|
|
||||||
<Card>
|
|
||||||
<Text>Job is not defined.</Text>
|
|
||||||
</Card>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (job.notes.length === 0)
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<Card.Content>
|
|
||||||
<Text>{t("jobdetail.labels.nojobnotes")}</Text>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
data={job.notes}
|
|
||||||
renderItem={(object) => <JobNotesItem item={object.item} />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import React from "react";
|
|
||||||
import { ProgressBar } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { GET_DOC_SIZE_TOTALS } from "../../graphql/documents.queries";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { View, Text } from "react-native";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { formatBytes } from "../../util/document-upload.utility";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobSpaceAvailable);
|
|
||||||
|
|
||||||
export function JobSpaceAvailable({ bodyshop, style, jobid }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data } = useQuery(GET_DOC_SIZE_TOTALS, {
|
|
||||||
variables: { jobId: jobid },
|
|
||||||
skip: !jobid || jobid === "temp",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!jobid || !data) return <></>;
|
|
||||||
|
|
||||||
const progress =
|
|
||||||
data.documents_aggregate.aggregate.sum.size /
|
|
||||||
((bodyshop && bodyshop.jobsizelimit) || 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={{ margin: 10 }}>
|
|
||||||
<Text style={{ marginBottom: 5 }}>
|
|
||||||
{t("mediabrowser.labels.storageused", {
|
|
||||||
used: formatBytes(data.documents_aggregate.aggregate.sum.size),
|
|
||||||
total: formatBytes((bodyshop && bodyshop.jobsizelimit) || 1),
|
|
||||||
percent: Math.round(progress * 100),
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
<ProgressBar style={[style]} progress={progress} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
|
||||||
RefreshControl,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import { Card, Headline, Subheading } from "react-native-paper";
|
|
||||||
import DataLabelComponent from "../../components/data-label/data-label";
|
|
||||||
import StyleRepeater from "../style-repeater/style-repeater";
|
|
||||||
import styles from "../styles";
|
|
||||||
|
|
||||||
export default function JobTombstone({ job, loading, refetch }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
if (!job) {
|
|
||||||
<Card>
|
|
||||||
<Text>Job is not defined.</Text>
|
|
||||||
</Card>;
|
|
||||||
}
|
|
||||||
const onRefresh = async () => {
|
|
||||||
return refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
style={styles.cardBackground}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyleRepeater childStyle={{ margin: 4 }}>
|
|
||||||
<Card>
|
|
||||||
<Card.Title title={t("jobdetail.labels.jobinfo")} />
|
|
||||||
<Card.Content>
|
|
||||||
<Headline>{job.status}</Headline>
|
|
||||||
{job.inproduction && (
|
|
||||||
<Subheading>{t("objects.jobs.labels.inproduction")}</Subheading>
|
|
||||||
)}
|
|
||||||
{job.inproduction &&
|
|
||||||
job.production_vars &&
|
|
||||||
!!job.production_vars.note && (
|
|
||||||
<Subheading>{job.production_vars.note}</Subheading>
|
|
||||||
)}
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<Card.Title title={t("jobdetail.labels.claiminformation")} />
|
|
||||||
<Card.Content style={localStyles.twoColumnCard}>
|
|
||||||
<View style={localStyles.twoColumnCardColumn}>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.owner")}
|
|
||||||
content={`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
|
||||||
job.ownr_co_nm || ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.vehicle")}
|
|
||||||
content={
|
|
||||||
<View>
|
|
||||||
<Text>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
|
|
||||||
job.v_model_desc || ""
|
|
||||||
}`}</Text>
|
|
||||||
<Text>{job.v_vin}</Text>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={localStyles.twoColumnCardColumn}>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.ins_co_nm")}
|
|
||||||
content={job.ins_co_nm}
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.clm_no")}
|
|
||||||
content={job.clm_no}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<Card.Title title={t("jobdetail.labels.employeeassignments")} />
|
|
||||||
<Card.Content>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.employee_body")}
|
|
||||||
content={`${
|
|
||||||
(job.employee_body_rel && job.employee_body_rel.first_name) ||
|
|
||||||
""
|
|
||||||
} ${
|
|
||||||
(job.employee_body_rel && job.employee_body_rel.last_name) || ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.employee_prep")}
|
|
||||||
content={`${
|
|
||||||
(job.employee_prep_rel && job.employee_prep_rel.first_name) ||
|
|
||||||
""
|
|
||||||
} ${
|
|
||||||
(job.employee_prep_rel && job.employee_prep_rel.last_name) || ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.employee_refinish")}
|
|
||||||
content={`${
|
|
||||||
(job.employee_refinish_rel &&
|
|
||||||
job.employee_refinish_rel.first_name) ||
|
|
||||||
""
|
|
||||||
} ${
|
|
||||||
(job.employee_refinish_rel &&
|
|
||||||
job.employee_refinish_rel.last_name) ||
|
|
||||||
""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.employee_csr")}
|
|
||||||
content={`${
|
|
||||||
(job.employee_csr_rel && job.employee_csr_rel.first_name) || ""
|
|
||||||
} ${
|
|
||||||
(job.employee_csr_rel && job.employee_csr_rel.last_name) || ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<Card.Title title={t("jobdetail.labels.dates")} />
|
|
||||||
<Card.Content style={localStyles.twoColumnCard}>
|
|
||||||
<View style={localStyles.twoColumnCardColumn}>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.scheduled_in")}
|
|
||||||
content={job.scheduled_in}
|
|
||||||
dateTime
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.actual_in")}
|
|
||||||
content={job.actual_in}
|
|
||||||
dateTime
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={localStyles.twoColumnCardColumn}>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.scheduled_completion")}
|
|
||||||
content={job.scheduled_completion}
|
|
||||||
dateTime
|
|
||||||
/>
|
|
||||||
<DataLabelComponent
|
|
||||||
label={t("objects.jobs.fields.scheduled_delivery")}
|
|
||||||
content={job.scheduled_delivery}
|
|
||||||
dateTime
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
</StyleRepeater>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const localStyles = StyleSheet.create({
|
|
||||||
twoColumnCard: { display: "flex", flexDirection: "row" },
|
|
||||||
twoColumnCardColumn: { flex: 1 },
|
|
||||||
status: {
|
|
||||||
textAlign: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
inproduction: {
|
|
||||||
textAlign: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { BarIndicator } from "react-native-indicators";
|
|
||||||
|
|
||||||
export default function LoadingDisplay({ count = 5 }) {
|
|
||||||
//TODO: This is throwing an error per expo, but it appears to be happening inside the component itself.
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1, alignContent: "center", justifyContent: "center" }}>
|
|
||||||
<BarIndicator count={count} color="dodgerblue" />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { SafeAreaView } from "react-native";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import ImageView from "react-native-image-viewing";
|
|
||||||
|
|
||||||
export default function MediaCacheOverlay({
|
|
||||||
photos,
|
|
||||||
previewVisible,
|
|
||||||
setPreviewVisible,
|
|
||||||
imgIndex,
|
|
||||||
setImgIndex,
|
|
||||||
}) {
|
|
||||||
//const videoRef = React.useRef(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView>
|
|
||||||
<ImageView
|
|
||||||
onRequestClose={() => setPreviewVisible(false)}
|
|
||||||
visible={previewVisible}
|
|
||||||
images={photos}
|
|
||||||
imageIndex={imgIndex}
|
|
||||||
// onImageIndexChange={(...props) => {
|
|
||||||
// // console.log(props);
|
|
||||||
// }}
|
|
||||||
/>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
// 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 { Text, View } from "react-native";
|
|
||||||
// import { connect } from "react-redux";
|
|
||||||
// import { createStructuredSelector } from "reselect";
|
|
||||||
// import {
|
|
||||||
// selectCurrentCameraJob,
|
|
||||||
// selectCurrentCameraJobId,
|
|
||||||
// } from "../../redux/app/app.selectors";
|
|
||||||
// import { addPhoto } from "../../redux/photos/photos.actions";
|
|
||||||
// import CameraControls from "../camera-controls/camera-controls.component";
|
|
||||||
// import CameraSelectJob from "../camera-select-job/camera-select-job.component";
|
|
||||||
|
|
||||||
// const mapStateToProps = createStructuredSelector({
|
|
||||||
// cameraJobId: selectCurrentCameraJobId,
|
|
||||||
// cameraJob: selectCurrentCameraJob,
|
|
||||||
// });
|
|
||||||
// const mapDispatchToProps = (dispatch) => ({
|
|
||||||
// addPhoto: (photo) => dispatch(addPhoto(photo)),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export function ScreenCamera({ cameraJobId, addPhoto }) {
|
|
||||||
// const [hasPermission, setHasPermission] = useState(null);
|
|
||||||
// const [state, setState] = useState({
|
|
||||||
// flashMode: Camera.Constants.FlashMode.off,
|
|
||||||
// capturing: null,
|
|
||||||
// cameraType: Camera.Constants.Type.back,
|
|
||||||
// tabHasFocus: null,
|
|
||||||
// });
|
|
||||||
// const cameraRef = useRef(null);
|
|
||||||
|
|
||||||
// useFocusEffect(
|
|
||||||
// React.useCallback(() => {
|
|
||||||
// // Do something when the screen is focused
|
|
||||||
// setState({ ...state, tabHasFocus: true });
|
|
||||||
// return () => {
|
|
||||||
// // Do something when the screen is unfocused
|
|
||||||
// // Useful for cleanup functions
|
|
||||||
// setState({ ...state, tabHasFocus: false });
|
|
||||||
// };
|
|
||||||
// }, [])
|
|
||||||
// );
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// (async () => {
|
|
||||||
// const { status } = await Camera.requestPermissionsAsync();
|
|
||||||
// setHasPermission(status === "granted");
|
|
||||||
// })();
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
// const setFlashMode = (flashMode) => setState({ ...state, flashMode });
|
|
||||||
// const setCameraType = (cameraType) => setState({ ...state, cameraType });
|
|
||||||
// const handleCaptureIn = () => setState({ ...state, capturing: true });
|
|
||||||
|
|
||||||
// const handleCaptureOut = () => {
|
|
||||||
// if (state.capturing) cameraRef.current.stopRecording();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleShortCapture = async () => {
|
|
||||||
// if (cameraRef.current) {
|
|
||||||
// const options = {
|
|
||||||
// quality: 0.8,
|
|
||||||
// //base64: true,
|
|
||||||
// skipProcessing: true,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let photo = await cameraRef.current.takePictureAsync(options);
|
|
||||||
// const filename = photo.uri.substring(photo.uri.lastIndexOf("/") + 1);
|
|
||||||
// const newUri = FileSystem.documentDirectory + "photos/" + filename;
|
|
||||||
|
|
||||||
// await FileSystem.moveAsync({
|
|
||||||
// from: photo.uri,
|
|
||||||
// to: newUri,
|
|
||||||
// });
|
|
||||||
// setState({ ...state, capturing: false });
|
|
||||||
// addPhoto({
|
|
||||||
// ...photo,
|
|
||||||
// id: filename,
|
|
||||||
// uri: newUri,
|
|
||||||
// jobId: cameraJobId,
|
|
||||||
// video: false,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleLongCapture = async () => {
|
|
||||||
// console.log("Taking a video!");
|
|
||||||
// if (cameraRef.current) {
|
|
||||||
// let video = await cameraRef.current.recordAsync();
|
|
||||||
|
|
||||||
// const filename = video.uri.substring(video.uri.lastIndexOf("/") + 1);
|
|
||||||
// const newUri = FileSystem.documentDirectory + "photos/" + filename;
|
|
||||||
|
|
||||||
// await FileSystem.moveAsync({
|
|
||||||
// from: video.uri,
|
|
||||||
// to: newUri,
|
|
||||||
// });
|
|
||||||
// setState({ ...state, capturing: false });
|
|
||||||
// console.log("Adding Photo", {
|
|
||||||
// ...video,
|
|
||||||
// id: filename,
|
|
||||||
// uri: newUri,
|
|
||||||
// jobId: cameraJobId,
|
|
||||||
// video: true,
|
|
||||||
// });
|
|
||||||
// addPhoto({
|
|
||||||
// ...video,
|
|
||||||
// id: filename,
|
|
||||||
// uri: newUri,
|
|
||||||
// jobId: cameraJobId,
|
|
||||||
// video: true,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// if (hasPermission === null || !state.tabHasFocus) {
|
|
||||||
// return <View />;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (hasPermission === false) {
|
|
||||||
// return <Text>No access to camera. Please ensure that you allow it.</Text>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const { flashMode, cameraType, capturing } = state;
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Camera
|
|
||||||
// style={{ flex: 1, display: "flex" }}
|
|
||||||
// type={state.cameraType}
|
|
||||||
// ref={cameraRef}
|
|
||||||
// ratio={"16:9"}
|
|
||||||
// >
|
|
||||||
// <View
|
|
||||||
// style={{
|
|
||||||
// flex: 1,
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <CameraSelectJob />
|
|
||||||
|
|
||||||
// <CameraControls
|
|
||||||
// capturing={capturing}
|
|
||||||
// flashMode={flashMode}
|
|
||||||
// cameraType={cameraType}
|
|
||||||
// setFlashMode={setFlashMode}
|
|
||||||
// setCameraType={setCameraType}
|
|
||||||
// onCaptureIn={handleCaptureIn}
|
|
||||||
// onCaptureOut={handleCaptureOut}
|
|
||||||
// onLongCapture={handleLongCapture}
|
|
||||||
// onShortCapture={handleShortCapture}
|
|
||||||
// />
|
|
||||||
// </View>
|
|
||||||
// </Camera>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// export default connect(mapStateToProps, mapDispatchToProps)(ScreenCamera);
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useWindowDimensions } from "react-native";
|
|
||||||
import { SceneMap, TabView, TabBar } from "react-native-tab-view";
|
|
||||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
|
||||||
import ErrorDisplay from "../error-display/error-display.component";
|
|
||||||
import JobDocuments from "../job-documents/job-documents.component";
|
|
||||||
import JobLines from "../job-lines/job-lines.component";
|
|
||||||
import JobNotes from "../job-notes/job-notes.component";
|
|
||||||
import JobTombstone from "../job-tombstone/job-tombstone.component";
|
|
||||||
import LoadingDisplay from "../loading-display/loading-display.component";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import JobDocumentsLocalComponent from "../job-documents/job-documents-local.component";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ScreenJobDetail);
|
|
||||||
|
|
||||||
export function ScreenJobDetail({ bodyshop, route }) {
|
|
||||||
const {
|
|
||||||
params: { jobId },
|
|
||||||
} = route;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const layout = useWindowDimensions();
|
|
||||||
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
|
||||||
variables: {
|
|
||||||
id: jobId,
|
|
||||||
},
|
|
||||||
skip: !jobId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderTabBar = (props) => (
|
|
||||||
<TabBar
|
|
||||||
{...props}
|
|
||||||
indicatorStyle={{ backgroundColor: "#ffffff" }}
|
|
||||||
// style={{ backgroundColor: "dodgerblue" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderScene = SceneMap({
|
|
||||||
job: () =>
|
|
||||||
JobTombstone({
|
|
||||||
job: data.jobs_by_pk,
|
|
||||||
loading: loading,
|
|
||||||
refetch: refetch,
|
|
||||||
}),
|
|
||||||
lines: () =>
|
|
||||||
JobLines({
|
|
||||||
job: data.jobs_by_pk,
|
|
||||||
loading: loading,
|
|
||||||
refetch: refetch,
|
|
||||||
}),
|
|
||||||
|
|
||||||
documents: () => {
|
|
||||||
return bodyshop.uselocalmediaserver ? (
|
|
||||||
<JobDocumentsLocalComponent job={data.jobs_by_pk} bodyshop={bodyshop} />
|
|
||||||
) : (
|
|
||||||
<JobDocuments
|
|
||||||
job={data.jobs_by_pk}
|
|
||||||
loading={loading}
|
|
||||||
refetch={refetch}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
notes: () =>
|
|
||||||
JobNotes({
|
|
||||||
job: data.jobs_by_pk,
|
|
||||||
loading: loading,
|
|
||||||
refetch: refetch,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const [index, setIndex] = React.useState(0);
|
|
||||||
const [routes] = React.useState([
|
|
||||||
{ key: "job", title: t("jobdetail.labels.job") },
|
|
||||||
{ key: "lines", title: t("jobdetail.labels.lines") },
|
|
||||||
{ key: "documents", title: t("jobdetail.labels.documents") },
|
|
||||||
{ key: "notes", title: t("jobdetail.labels.notes") },
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (loading) return <LoadingDisplay />;
|
|
||||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TabView
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
navigationState={{ index, routes }}
|
|
||||||
renderScene={renderScene}
|
|
||||||
onIndexChange={setIndex}
|
|
||||||
initialLayout={{ width: layout.width }}
|
|
||||||
renderTabBar={renderTabBar}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import JobListComponent from "../job-list/job-list.component.jsx";
|
|
||||||
|
|
||||||
export default function ScreenJobList({ navigation }) {
|
|
||||||
return <JobListComponent />;
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
|
||||||
import { NavigationContainer } from "@react-navigation/native";
|
|
||||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|
||||||
import i18n from "i18next";
|
|
||||||
import moment from "moment";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Button } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
|
||||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
|
||||||
import {
|
|
||||||
checkUserSession,
|
|
||||||
emailSignInStart,
|
|
||||||
signOutStart,
|
|
||||||
} from "../../redux/user/user.actions";
|
|
||||||
import {
|
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import ScreenJobDetail from "../screen-job-detail/screen-job-detail.component";
|
|
||||||
import ScreenJobList from "../screen-job-list/screen-job-list.component";
|
|
||||||
import ScreenMediaBrowser from "../screen-media-browser/screen-media-browser.component";
|
|
||||||
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";
|
|
||||||
|
|
||||||
const ActiveJobStack = createNativeStackNavigator();
|
|
||||||
const MoreStack = createNativeStackNavigator();
|
|
||||||
const BottomTabs = createBottomTabNavigator();
|
|
||||||
const MediaBrowserStack = createNativeStackNavigator();
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
|
||||||
emailSignInStart: (email, password) =>
|
|
||||||
dispatch(emailSignInStart({ email, password })),
|
|
||||||
signOutStart: () => dispatch(signOutStart()),
|
|
||||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
|
||||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const JobsTabNavigator = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(({ setCameraJobId, setCameraJob }) => (
|
|
||||||
<ActiveJobStack.Navigator initialRouteName="JobList">
|
|
||||||
<ActiveJobStack.Screen
|
|
||||||
name="JobList"
|
|
||||||
options={() => ({
|
|
||||||
title: i18n.t("joblist.labels.activejobs"),
|
|
||||||
})}
|
|
||||||
component={ScreenJobList}
|
|
||||||
/>
|
|
||||||
<ActiveJobStack.Screen
|
|
||||||
name="JobDetail"
|
|
||||||
component={ScreenJobDetail}
|
|
||||||
options={({ navigation, route }) => ({
|
|
||||||
title:
|
|
||||||
(route.params && route.params.title) ||
|
|
||||||
i18n.t("joblist.labels.detail"),
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
headerRight: () => (
|
|
||||||
<Button
|
|
||||||
onPress={() => {
|
|
||||||
logImEXEvent("imexmobile_setcamerajobid_jobheader");
|
|
||||||
setCameraJobId(route.params.jobId);
|
|
||||||
setCameraJob(route.params.job);
|
|
||||||
navigation.navigate("MediaBrowserTab");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Ionicons name="add" size={32} color="dodgerblue" />
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</ActiveJobStack.Navigator>
|
|
||||||
));
|
|
||||||
|
|
||||||
const MediaBrowserStackNavigator = () => (
|
|
||||||
<MediaBrowserStack.Navigator initialRouteName="MediaBrowser">
|
|
||||||
<MediaBrowserStack.Screen
|
|
||||||
name="MediaBrowser"
|
|
||||||
options={{ title: i18n.t("mediabrowser.titles.mediabrowsertab") }}
|
|
||||||
component={ScreenMediaBrowser}
|
|
||||||
/>
|
|
||||||
</MediaBrowserStack.Navigator>
|
|
||||||
);
|
|
||||||
|
|
||||||
const MoreStackNavigator = () => (
|
|
||||||
<MoreStack.Navigator>
|
|
||||||
<MoreStack.Screen
|
|
||||||
name="Settings"
|
|
||||||
options={{
|
|
||||||
title: i18n.t("settings.titles.settings"),
|
|
||||||
}}
|
|
||||||
component={ScreenSettingsComponent}
|
|
||||||
/>
|
|
||||||
</MoreStack.Navigator>
|
|
||||||
);
|
|
||||||
|
|
||||||
const BottomTabsNavigator = () => (
|
|
||||||
<BottomTabs.Navigator
|
|
||||||
screenOptions={({ route }) => ({
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
tabBarIcon: ({ color, size }) => {
|
|
||||||
let iconName;
|
|
||||||
if (route.name === "JobTab") {
|
|
||||||
iconName = "list";
|
|
||||||
} else if (route.name === "MoreTab") {
|
|
||||||
iconName = "settings";
|
|
||||||
} else if (route.name === "MediaBrowserTab") {
|
|
||||||
iconName = "camera";
|
|
||||||
} else {
|
|
||||||
//iconName = "customerservice";
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Ionicons name={iconName} size={size} color={color} />;
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<BottomTabs.Screen
|
|
||||||
name="JobTab"
|
|
||||||
options={{
|
|
||||||
title: i18n.t("joblist.titles.jobtab"),
|
|
||||||
headerShown: false,
|
|
||||||
}}
|
|
||||||
component={JobsTabNavigator}
|
|
||||||
/>
|
|
||||||
<BottomTabs.Screen
|
|
||||||
name="MediaBrowserTab"
|
|
||||||
options={{
|
|
||||||
title: i18n.t("mediabrowser.titles.mediabrowsertab"),
|
|
||||||
headerShown: false,
|
|
||||||
}}
|
|
||||||
component={MediaBrowserStackNavigator}
|
|
||||||
/>
|
|
||||||
<BottomTabs.Screen
|
|
||||||
name="MoreTab"
|
|
||||||
options={{ title: i18n.t("more.titles.moretab"), headerShown: false }}
|
|
||||||
component={MoreStackNavigator}
|
|
||||||
/>
|
|
||||||
</BottomTabs.Navigator>
|
|
||||||
);
|
|
||||||
|
|
||||||
export function ScreenMainComponent({
|
|
||||||
checkUserSession,
|
|
||||||
currentUser,
|
|
||||||
bodyshop,
|
|
||||||
}) {
|
|
||||||
useEffect(() => {
|
|
||||||
checkUserSession();
|
|
||||||
}, [checkUserSession]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// // LogRocket.init("idt6oy/imex-mobile", {
|
|
||||||
// // updateId: Updates.isEmbeddedLaunch ? null : Updates.updateId,
|
|
||||||
// // expoChannel: Updates.channel,
|
|
||||||
// // });
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavigationContainer>
|
|
||||||
{currentUser.authorized === null ? (
|
|
||||||
<ScreenSplash />
|
|
||||||
) : currentUser.authorized ? (
|
|
||||||
bodyshop ? (
|
|
||||||
HasAccess(bodyshop) ? (
|
|
||||||
<BottomTabsNavigator />
|
|
||||||
) : (
|
|
||||||
<ScreenSplash noAccess />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<ScreenSplash />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<ScreenSignIn />
|
|
||||||
)}
|
|
||||||
</NavigationContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ScreenMainComponent);
|
|
||||||
|
|
||||||
function HasAccess({ features }) {
|
|
||||||
if (features.mobile === undefined || features.mobile === true) return true;
|
|
||||||
if (features.mobile === false) return false;
|
|
||||||
const d = moment(moment(features.mobile));
|
|
||||||
if (d.isValid()) return d.isAfter(moment());
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
import Constants from "expo-constants";
|
|
||||||
import * as ImagePicker from "expo-image-picker";
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
|
||||||
import { Button } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { toggleDeleteAfterUpload } from "../../redux/app/app.actions";
|
|
||||||
import {
|
|
||||||
selectCurrentCameraJobId,
|
|
||||||
selectDeleteAfterUpload,
|
|
||||||
} from "../../redux/app/app.selectors";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import CameraSelectJob from "../camera-select-job/camera-select-job.component";
|
|
||||||
import JobSpaceAvailable from "../job-space-available/job-space-available.component";
|
|
||||||
import UploadDeleteSwitch from "../upload-delete-switch/upload-delete-switch.component";
|
|
||||||
import UploadProgressLocal from "../upload-progress-local/upload-progress-local.component";
|
|
||||||
import UploadProgress from "../upload-progress/upload-progress.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
selectedCameraJobId: selectCurrentCameraJobId,
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
deleteAfterUpload: selectDeleteAfterUpload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ImageBrowserScreen({
|
|
||||||
bodyshop,
|
|
||||||
selectedCameraJobId,
|
|
||||||
//toggleDeleteAfterUpload,
|
|
||||||
// deleteAfterUpload,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [uploads, setUploads] = useState(null);
|
|
||||||
const [density, setDensity] = useState(3);
|
|
||||||
const [tick, setTick] = useState(0);
|
|
||||||
|
|
||||||
const forceRerender = useCallback(() => {
|
|
||||||
setTick((tick) => tick + 1);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
if (Constants.platform.ios) {
|
|
||||||
const cameraRollStatus =
|
|
||||||
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
||||||
const cameraStatus = await ImagePicker.requestCameraPermissionsAsync();
|
|
||||||
if (
|
|
||||||
cameraRollStatus.status !== "granted" ||
|
|
||||||
cameraStatus.status !== "granted"
|
|
||||||
) {
|
|
||||||
alert(
|
|
||||||
"Photo and Camera permissions have not been granted. Please open the settings app and allow these permissions to upload photos."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const pickImage = async () => {
|
|
||||||
let result = await ImagePicker.launchImageLibraryAsync({
|
|
||||||
mediaTypes: ["images", "videos"],
|
|
||||||
aspect: [4, 3],
|
|
||||||
quality: 1,
|
|
||||||
allowsMultipleSelection: true,
|
|
||||||
});
|
|
||||||
setUploads(result.assets);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.flex, styles.container]}>
|
|
||||||
<CameraSelectJob />
|
|
||||||
{bodyshop.uselocalmediaserver ? (
|
|
||||||
<Text style={{ margin: 10 }}>
|
|
||||||
{t("mediabrowser.labels.localserver", {
|
|
||||||
url: bodyshop.localmediaserverhttp,
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<JobSpaceAvailable jobid={selectedCameraJobId} key={`${tick}-space`} />
|
|
||||||
)}
|
|
||||||
<UploadDeleteSwitch />
|
|
||||||
|
|
||||||
{!selectedCameraJobId && (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text>{t("mediabrowser.labels.selectjobassetselector")}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Button onPress={pickImage}>Media Select</Button>
|
|
||||||
{bodyshop.uselocalmediaserver ? (
|
|
||||||
<UploadProgressLocal
|
|
||||||
uploads={uploads}
|
|
||||||
setUploads={setUploads}
|
|
||||||
forceRerender={forceRerender}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<UploadProgress
|
|
||||||
uploads={uploads}
|
|
||||||
setUploads={setUploads}
|
|
||||||
forceRerender={forceRerender}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
flex: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
display: "flex",
|
|
||||||
// position: "relative",
|
|
||||||
},
|
|
||||||
buttonStyle: {
|
|
||||||
//backgroundColor: "tomato",
|
|
||||||
},
|
|
||||||
textStyle: {
|
|
||||||
color: "dodgerblue",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ImageBrowserScreen);
|
|
||||||
|
|
||||||
// // Utility to get asset ID from URI if missing
|
|
||||||
// async function getAssetIdFromUri(uri, filename = null, maxPages = 10) {
|
|
||||||
// let after = null;
|
|
||||||
// let found = null;
|
|
||||||
// let pageCount = 0;
|
|
||||||
|
|
||||||
// while (!found && pageCount < maxPages) {
|
|
||||||
// const page = await MediaLibrary.getAssetsAsync({
|
|
||||||
// first: 100,
|
|
||||||
// mediaType: [MediaLibrary.MediaType.photo, MediaLibrary.MediaType.video],
|
|
||||||
// after,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Try to match by URI
|
|
||||||
// found = page.assets.find((asset) => asset.uri === uri);
|
|
||||||
|
|
||||||
// // Fallback: try to match by filename if not found and filename is available
|
|
||||||
// if (!found && filename) {
|
|
||||||
// found = page.assets.find((asset) => asset.filename === filename);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// after = page.endCursor;
|
|
||||||
// pageCount++;
|
|
||||||
// if (!after) break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return found ? found.id : null;
|
|
||||||
// }
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import _ from "lodash";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
FlatList,
|
|
||||||
Image,
|
|
||||||
SafeAreaView,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import { Button } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import {
|
|
||||||
removeAllPhotos,
|
|
||||||
uploadAllPhotos,
|
|
||||||
} from "../../redux/photos/photos.actions";
|
|
||||||
import {
|
|
||||||
selectPhotos,
|
|
||||||
selectUploadInProgress,
|
|
||||||
} from "../../redux/photos/photos.selectors";
|
|
||||||
import MediaCacheOverlay from "../media-cache-overlay/media-cache-overlay.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
photos: selectPhotos,
|
|
||||||
uploadInProgress: selectUploadInProgress,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
removeAllPhotos: () => dispatch(removeAllPhotos()),
|
|
||||||
uploadAllphotos: () => dispatch(uploadAllPhotos()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ScreenMediaCache({
|
|
||||||
photos,
|
|
||||||
removeAllPhotos,
|
|
||||||
uploadAllphotos,
|
|
||||||
uploadInProgress,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
|
||||||
const [imgIndex, setImgIndex] = useState(0);
|
|
||||||
|
|
||||||
const groupedPhotos = _.groupBy(photos, "jobId");
|
|
||||||
|
|
||||||
const RenderJobPictures = ({ jobId, jobPhotos }) => (
|
|
||||||
<View>
|
|
||||||
<Text>{jobId}</Text>
|
|
||||||
<FlatList
|
|
||||||
data={jobPhotos}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
contentContainerStyle={styles.listContentContainer}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
numColumns={4}
|
|
||||||
renderItem={(object) =>
|
|
||||||
object.item.video ? (
|
|
||||||
<Text>Video</Text>
|
|
||||||
) : (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
flex: 1 / 4, //here you can use flex:1 also
|
|
||||||
aspectRatio: 1,
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
setImgIndex(object.index);
|
|
||||||
setPreviewVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
source={{ uri: object.item.uri }}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
resizeMode="cover"
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={styles.container}>
|
|
||||||
<View style={styles.actions}>
|
|
||||||
<Button onPress={() => removeAllPhotos()}>
|
|
||||||
<Text>{t("mediacache.actions.deleteall")}</Text>
|
|
||||||
</Button>
|
|
||||||
<Button onPress={() => uploadAllphotos()}>
|
|
||||||
<Text>{t("mediacache.actions.uploadall")}</Text>
|
|
||||||
{uploadInProgress && <ActivityIndicator />}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
<FlatList
|
|
||||||
data={groupedPhotos ? Object.keys(groupedPhotos) : []}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
contentContainerStyle={styles.listContentContainer}
|
|
||||||
keyExtractor={(item) => item}
|
|
||||||
renderItem={(object) => (
|
|
||||||
<RenderJobPictures
|
|
||||||
jobPhotos={groupedPhotos[object.item]}
|
|
||||||
jobId={object.item}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<MediaCacheOverlay
|
|
||||||
imgIndex={imgIndex}
|
|
||||||
setImgIndex={setImgIndex}
|
|
||||||
previewVisible={previewVisible}
|
|
||||||
setPreviewVisible={setPreviewVisible}
|
|
||||||
/>
|
|
||||||
<Text>{`${photos.length} Photos`}</Text>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
},
|
|
||||||
listContentContainer: {
|
|
||||||
//flex: 1,
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
//flexDirection: "row",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ScreenMediaCache);
|
|
||||||
|
|
||||||
// <FlatList
|
|
||||||
// data={imagesInDir}
|
|
||||||
// style={{}}
|
|
||||||
// keyExtractor={(item) => item.id}
|
|
||||||
// renderItem={(object) => <Text>{object.item}</Text>}
|
|
||||||
// />;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { View, Text } from "react-native";
|
|
||||||
|
|
||||||
export default function ScreenMessagingConversation({ navigation }) {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text></Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { View, Text } from "react-native";
|
|
||||||
|
|
||||||
export default function ScreenMessagingList() {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text>A list of conversations.</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";
|
|
||||||
import DraggableFlatList, {
|
|
||||||
ScaleDecorator,
|
|
||||||
NestableScrollContainer,
|
|
||||||
NestableDraggableFlatList,
|
|
||||||
} from "react-native-draggable-flatlist";
|
|
||||||
|
|
||||||
const NUM_ITEMS = 10;
|
|
||||||
function getColor(i) {
|
|
||||||
const multiplier = 255 / (NUM_ITEMS - 1);
|
|
||||||
const colorVal = i * multiplier;
|
|
||||||
return `rgb(${colorVal}, ${Math.abs(128 - colorVal)}, ${255 - colorVal})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialData = [...Array(NUM_ITEMS)].map((d, index) => {
|
|
||||||
const backgroundColor = getColor(index);
|
|
||||||
return {
|
|
||||||
key: `item-${index}`,
|
|
||||||
label: String(index) + "",
|
|
||||||
height: 100,
|
|
||||||
width: 60 + Math.random() * 40,
|
|
||||||
backgroundColor,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialData2 = [...Array(NUM_ITEMS)].map((d, index) => {
|
|
||||||
const backgroundColor = getColor(index);
|
|
||||||
return {
|
|
||||||
key: `item-${index}`,
|
|
||||||
label: String(index) + "",
|
|
||||||
height: 100,
|
|
||||||
width: 60 + Math.random() * 40,
|
|
||||||
backgroundColor,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const [data, setData] = useState(initialData);
|
|
||||||
const [data2, setData2] = useState(initialData2);
|
|
||||||
|
|
||||||
const renderItem = ({ item, drag, isActive }) => {
|
|
||||||
return (
|
|
||||||
<ScaleDecorator>
|
|
||||||
<TouchableOpacity
|
|
||||||
onLongPress={drag}
|
|
||||||
disabled={isActive}
|
|
||||||
style={[
|
|
||||||
styles.rowItem,
|
|
||||||
{ backgroundColor: isActive ? "red" : item.backgroundColor },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={styles.text}>{item.label}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</ScaleDecorator>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NestableScrollContainer>
|
|
||||||
<NestableDraggableFlatList
|
|
||||||
data={data}
|
|
||||||
onDragEnd={({ data }) => setData(data)}
|
|
||||||
keyExtractor={(item) => item.key}
|
|
||||||
renderItem={renderItem}
|
|
||||||
/>
|
|
||||||
<NestableDraggableFlatList
|
|
||||||
data={data2}
|
|
||||||
onDragEnd={({ data }) => setData2(data)}
|
|
||||||
keyExtractor={(item) => item.key}
|
|
||||||
renderItem={renderItem}
|
|
||||||
/>
|
|
||||||
</NestableScrollContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
rowItem: {
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
color: "white",
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: "bold",
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import Constants from "expo-constants";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { View, Text } from "react-native";
|
|
||||||
import { Title, Button } from "react-native-paper";
|
|
||||||
import { purgeStoredState } from "redux-persist";
|
|
||||||
import SignOutButton from "../sign-out-button/sign-out-button.component";
|
|
||||||
import * as Updates from "expo-updates";
|
|
||||||
import * as Application from "expo-application";
|
|
||||||
|
|
||||||
export default function ScreenSettingsComponent() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Title>
|
|
||||||
{t("settings.labels.version", {
|
|
||||||
number: `${Constants.expoConfig.version}(${Application.nativeBuildVersion} - ${Constants.expoConfig.extra.expover})`,
|
|
||||||
})}
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Text>Release Channel {Updates.channel}</Text>
|
|
||||||
<SignOutButton />
|
|
||||||
{/* <Button title="Purge State" onPress={() => purgeStoredState()} /> */}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import { Formik } from "formik";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Image, StyleSheet, Text, View } from "react-native";
|
|
||||||
import { Button, TextInput, Title } from "react-native-paper";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import Logo from "../../assets/logo192.png";
|
|
||||||
import { emailSignInStart } from "../../redux/user/user.actions";
|
|
||||||
import {
|
|
||||||
selectCurrentUser,
|
|
||||||
selectSigningIn,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import SignInErrorAlertComponent from "../sign-in-error-alert/sign-in-error-alert.component";
|
|
||||||
import Constants from "expo-constants";
|
|
||||||
import * as Updates from "expo-updates";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
signingIn: selectSigningIn,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
emailSignInStart: (email, password) =>
|
|
||||||
dispatch(emailSignInStart({ email, password })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function SignIn({ emailSignInStart, signingIn }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const formSubmit = (values) => {
|
|
||||||
const { email, password } = values;
|
|
||||||
emailSignInStart(email, password);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={localStyles.content}>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
marginTop: 80,
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image style={localStyles.logo} source={Logo} />
|
|
||||||
<Title>{t("app.title")}</Title>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Formik
|
|
||||||
initialValues={{ email: "", password: "" }}
|
|
||||||
onSubmit={formSubmit}
|
|
||||||
>
|
|
||||||
{({ handleChange, handleBlur, handleSubmit, values }) => (
|
|
||||||
<View>
|
|
||||||
<TextInput
|
|
||||||
label={t("signin.fields.email")}
|
|
||||||
mode="outlined"
|
|
||||||
autoCapitalize="none"
|
|
||||||
keyboardType="email-address"
|
|
||||||
onChangeText={handleChange("email")}
|
|
||||||
onBlur={handleBlur("email")}
|
|
||||||
value={values.email}
|
|
||||||
style={[localStyles.input]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
label={t("signin.fields.password")}
|
|
||||||
mode="outlined"
|
|
||||||
secureTextEntry={true}
|
|
||||||
autoCorrect={false}
|
|
||||||
autoCapitalize="none"
|
|
||||||
onChangeText={handleChange("password")}
|
|
||||||
onBlur={handleBlur("password")}
|
|
||||||
value={values.password}
|
|
||||||
style={[localStyles.input]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SignInErrorAlertComponent />
|
|
||||||
<Button
|
|
||||||
mode="outlined"
|
|
||||||
loading={signingIn}
|
|
||||||
onPress={handleSubmit}
|
|
||||||
>
|
|
||||||
<Text>{t("signin.actions.signin")}</Text>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</View>
|
|
||||||
<Text style={{ padding: 10, alignSelf: "center" }}>
|
|
||||||
{t("settings.labels.version", {
|
|
||||||
number: Constants.expoConfig.version,
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const localStyles = StyleSheet.create({
|
|
||||||
content: {
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
logo: { width: 100, height: 100 },
|
|
||||||
input: {
|
|
||||||
margin: 12,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ActivityIndicator, Image, StyleSheet, View } from "react-native";
|
|
||||||
import { Title, Subheading, Divider } from "react-native-paper";
|
|
||||||
import Logo from "../../assets/logo192.png";
|
|
||||||
import SignOutButton from "../sign-out-button/sign-out-button.component";
|
|
||||||
|
|
||||||
export default function ScreenSplash({ noAccess }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<View style={[localStyles.container]}>
|
|
||||||
<View style={[localStyles.logoContainer]}>
|
|
||||||
<Image style={localStyles.logo} source={Logo} />
|
|
||||||
<Title>{t("app.title")}</Title>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{noAccess ? (
|
|
||||||
<View style={[localStyles.logoContainer]}>
|
|
||||||
<Subheading style={{ textAlign: "center" }}>
|
|
||||||
{t("app.nomobileaccess")}
|
|
||||||
</Subheading>
|
|
||||||
<Divider />
|
|
||||||
<SignOutButton />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<ActivityIndicator color="dodgerblue" size="large" />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const localStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: "column",
|
|
||||||
alignContent: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
logoContainer: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
margin: 10,
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
logo: { width: 175, height: 175, margin: 20 },
|
|
||||||
});
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Title } from "react-native-paper";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function SignInErrorAlertComponent({ signInError }) {
|
|
||||||
const [errorText, setErrorText] = useState("");
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let text;
|
|
||||||
if (signInError && signInError.code)
|
|
||||||
switch (signInError.code) {
|
|
||||||
case "auth/user-not-found":
|
|
||||||
text = t("signin.errors.usernotfound");
|
|
||||||
break;
|
|
||||||
case "auth/invalid-email":
|
|
||||||
text = t("signin.errors.emailformat");
|
|
||||||
break;
|
|
||||||
case "auth/wrong-password":
|
|
||||||
text = t("signin.errors.wrongpassword");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text = signInError.code + " " + signInError.message;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
setErrorText(text);
|
|
||||||
}, [signInError, setErrorText]);
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
{errorText ? <Title style={localStyles.alert}>{errorText}</Title> : null}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(SignInErrorAlertComponent);
|
|
||||||
|
|
||||||
const localStyles = StyleSheet.create({
|
|
||||||
alert: {
|
|
||||||
color: "red",
|
|
||||||
textAlign: "center",
|
|
||||||
margin: 15,
|
|
||||||
padding: 15,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function StyleRepeater({ childStyle, children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{React.Children.map(children, (child) =>
|
|
||||||
React.cloneElement(child, {
|
|
||||||
style: [child.props.style, childStyle],
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { StyleSheet } from "react-native";
|
|
||||||
|
|
||||||
const cardBackgroundColor = "gainsboro";
|
|
||||||
|
|
||||||
export default StyleSheet.create({
|
|
||||||
cardBackground: {
|
|
||||||
padding: 5,
|
|
||||||
backgroundColor: cardBackgroundColor,
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
|
||||||
import { Switch } from "react-native-paper";
|
|
||||||
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({
|
|
||||||
deleteAfterUpload: selectDeleteAfterUpload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function UploadDeleteSwitch({
|
|
||||||
deleteAfterUpload,
|
|
||||||
toggleDeleteAfterUpload,
|
|
||||||
}) {
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text style={styles.text}>
|
|
||||||
{t("mediabrowser.labels.deleteafterupload")}
|
|
||||||
</Text>
|
|
||||||
<Switch
|
|
||||||
// trackColor={{ false: '#767577', true: '#81b0ff' }}
|
|
||||||
// thumbColor={deleteAfterUpload ? 'tomato' : '#f4f3f4'}
|
|
||||||
//ios_backgroundColor="#3e3e3e"
|
|
||||||
onValueChange={() => {
|
|
||||||
toggleDeleteAfterUpload();
|
|
||||||
}}
|
|
||||||
value={deleteAfterUpload}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
margin: 10,
|
|
||||||
},
|
|
||||||
|
|
||||||
text: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UploadDeleteSwitch);
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
import * as MediaLibrary from "expo-media-library";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
Alert,
|
|
||||||
Modal,
|
|
||||||
Platform,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import { ProgressBar } from "react-native-paper";
|
|
||||||
import Toast from "react-native-toast-message";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
|
||||||
import {
|
|
||||||
selectCurrentCameraJobId,
|
|
||||||
selectDeleteAfterUpload,
|
|
||||||
} from "../../redux/app/app.selectors";
|
|
||||||
import * as Sentry from "@sentry/react-native";
|
|
||||||
|
|
||||||
import { formatBytes } from "../../util/document-upload.utility";
|
|
||||||
import { handleLocalUpload } from "../../util/local-document-upload.utility";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
selectedCameraJobId: selectCurrentCameraJobId,
|
|
||||||
deleteAfterUpload: selectDeleteAfterUpload,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(UploadProgress);
|
|
||||||
|
|
||||||
export function UploadProgress({
|
|
||||||
selectedCameraJobId,
|
|
||||||
deleteAfterUpload,
|
|
||||||
uploads,
|
|
||||||
setUploads,
|
|
||||||
forceRerender,
|
|
||||||
}) {
|
|
||||||
const [progress, setProgress] = useState({
|
|
||||||
loading: false,
|
|
||||||
uploadInProgress: false,
|
|
||||||
speed: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//Set the state of uploads to do.
|
|
||||||
if (uploads) {
|
|
||||||
beginUploads(uploads);
|
|
||||||
setUploads(null);
|
|
||||||
}
|
|
||||||
}, [uploads]);
|
|
||||||
|
|
||||||
async function handleOnSuccess({ duration, data }) {
|
|
||||||
//If it's not in production, show a toast with the time.
|
|
||||||
Toast.show({
|
|
||||||
type: "success",
|
|
||||||
text1: ` Upload completed in ${duration}.`,
|
|
||||||
//
|
|
||||||
// text2: duration,
|
|
||||||
});
|
|
||||||
if (deleteAfterUpload) {
|
|
||||||
try {
|
|
||||||
if (Platform.OS === "android") {
|
|
||||||
//Create a new asset with the first file to delete.
|
|
||||||
// console.log('Trying new delete.');
|
|
||||||
await MediaLibrary.getPermissionsAsync(false);
|
|
||||||
|
|
||||||
const album = await MediaLibrary.createAlbumAsync(
|
|
||||||
"ImEX Mobile Deleted",
|
|
||||||
data.pop(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
//Move the rest.
|
|
||||||
if (data.length > 0) {
|
|
||||||
const moveResult = await MediaLibrary.addAssetsToAlbumAsync(
|
|
||||||
data,
|
|
||||||
album,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const deleteResult = await MediaLibrary.deleteAlbumsAsync(album);
|
|
||||||
|
|
||||||
//Delete the album.
|
|
||||||
|
|
||||||
//This defaults to delete all assets in the album.
|
|
||||||
} else {
|
|
||||||
await MediaLibrary.deleteAssetsAsync(data.map((f) => f.id));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Unable to delete picture.", error);
|
|
||||||
Sentry.captureException(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logImEXEvent("imexmobile_successful_upload");
|
|
||||||
forceRerender();
|
|
||||||
setProgress({ ...progress, speed: 0, percent: 1, uploadInProgress: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnProgress({ percent, loaded }) {
|
|
||||||
setProgress((progress) => ({
|
|
||||||
...progress,
|
|
||||||
speed: loaded - progress.loaded,
|
|
||||||
loaded: loaded,
|
|
||||||
percent,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnError({ assetid, error }) {
|
|
||||||
logImEXEvent("imexmobile_upload_documents_error");
|
|
||||||
Toast.show({
|
|
||||||
type: "error",
|
|
||||||
text1: "Unable to upload documents.",
|
|
||||||
text2: error,
|
|
||||||
autoHide: false,
|
|
||||||
});
|
|
||||||
setProgress({
|
|
||||||
speed: 0,
|
|
||||||
percent: 1,
|
|
||||||
uploadInProgress: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const beginUploads = async (data) => {
|
|
||||||
//Validate to make sure the totals for the file sizes do not exceed the total on the job.
|
|
||||||
setProgress({
|
|
||||||
percent: 0,
|
|
||||||
loaded: 0,
|
|
||||||
uploadInProgress: true,
|
|
||||||
start: new Date(),
|
|
||||||
average: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
await handleLocalUpload({
|
|
||||||
files: data,
|
|
||||||
onError: ({ assetid, error }) => handleOnError({ assetid, error }),
|
|
||||||
onProgress: ({ percent, loaded }) =>
|
|
||||||
handleOnProgress({ percent, loaded }),
|
|
||||||
onSuccess: ({ duration }) => handleOnSuccess({ duration, data }),
|
|
||||||
context: {
|
|
||||||
jobid:
|
|
||||||
selectedCameraJobId !== "temp" ? selectedCameraJobId : "temporary",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
visible={progress.uploadInProgress}
|
|
||||||
animationType="slide"
|
|
||||||
transparent={true}
|
|
||||||
onRequestClose={() => {
|
|
||||||
Alert.alert("Cancel?", "Do you want to abort the upload?", [
|
|
||||||
{
|
|
||||||
text: "Yes",
|
|
||||||
onPress: () => {
|
|
||||||
setUploads(null);
|
|
||||||
setProgress(null);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ text: "No" },
|
|
||||||
]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View style={styles.modalContainer}>
|
|
||||||
<View style={styles.modal}>
|
|
||||||
<ActivityIndicator style={{ alignSelf: "center", marginTop: 16 }} />
|
|
||||||
<ProgressBar
|
|
||||||
progress={progress.percent}
|
|
||||||
style={{ alignSelf: "center", marginTop: 16 }}
|
|
||||||
color={progress.percent === 1 ? "green" : "blue"}
|
|
||||||
/>
|
|
||||||
<Text style={{ alignSelf: "center", marginTop: 16 }}>{`${formatBytes(
|
|
||||||
progress.speed
|
|
||||||
)}/sec`}</Text>
|
|
||||||
<Text
|
|
||||||
style={{ alignSelf: "center", marginTop: 16 }}
|
|
||||||
>{`Avg. ${formatBytes(
|
|
||||||
progress.loaded / ((new Date() - progress.start) / 1000)
|
|
||||||
)}/sec`}</Text>
|
|
||||||
<Text
|
|
||||||
style={{ alignSelf: "center", marginTop: 16 }}
|
|
||||||
>{`Total Uploaded ${formatBytes(progress.loaded)}`}</Text>
|
|
||||||
<Text style={{ alignSelf: "center", marginTop: 16 }}>{`Duration ${(
|
|
||||||
(new Date() - progress.start) /
|
|
||||||
1000
|
|
||||||
).toFixed(1)} sec`}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
modalContainer: {
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
// flex: 1,
|
|
||||||
display: "flex",
|
|
||||||
marginLeft: 20,
|
|
||||||
marginRight: 20,
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderRadius: 20,
|
|
||||||
padding: 18,
|
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: {
|
|
||||||
width: 0,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
shadowOpacity: 0.25,
|
|
||||||
shadowRadius: 4,
|
|
||||||
elevation: 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,426 +0,0 @@
|
|||||||
import { useApolloClient } from "@apollo/client";
|
|
||||||
import { File } from "expo-file-system";
|
|
||||||
import * as MediaLibrary from "expo-media-library";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
|
||||||
ActivityIndicator,
|
|
||||||
Alert,
|
|
||||||
Modal,
|
|
||||||
Platform,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import { Divider, ProgressBar } from "react-native-paper";
|
|
||||||
import Toast from "react-native-toast-message";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import * as Sentry from "@sentry/react-native";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
|
||||||
import { GET_DOC_SIZE_TOTALS } from "../../graphql/documents.queries";
|
|
||||||
import {
|
|
||||||
selectCurrentCameraJobId,
|
|
||||||
selectDeleteAfterUpload,
|
|
||||||
} from "../../redux/app/app.selectors";
|
|
||||||
import {
|
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import { formatBytes, handleUpload } from "../../util/document-upload.utility";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
selectedCameraJobId: selectCurrentCameraJobId,
|
|
||||||
deleteAfterUpload: selectDeleteAfterUpload,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(UploadProgress);
|
|
||||||
|
|
||||||
export function UploadProgress({
|
|
||||||
currentUser,
|
|
||||||
bodyshop,
|
|
||||||
selectedCameraJobId,
|
|
||||||
deleteAfterUpload,
|
|
||||||
uploads,
|
|
||||||
setUploads,
|
|
||||||
forceRerender,
|
|
||||||
}) {
|
|
||||||
const [progress, setProgress] = useState({
|
|
||||||
uploadInProgress: false,
|
|
||||||
totalToUpload: 0,
|
|
||||||
totalUploaded: 0,
|
|
||||||
startTime: null,
|
|
||||||
totalFiles: 0,
|
|
||||||
totalFilesCompleted: 0,
|
|
||||||
currentFile: null,
|
|
||||||
files: {}, //uri is the key, value is progress
|
|
||||||
});
|
|
||||||
|
|
||||||
let filesToDelete = [];
|
|
||||||
const client = useApolloClient();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (uploads) {
|
|
||||||
onDone(uploads);
|
|
||||||
setUploads(null);
|
|
||||||
}
|
|
||||||
}, [uploads]);
|
|
||||||
|
|
||||||
function handleOnSuccess(asset) {
|
|
||||||
//NEEDS REDO.
|
|
||||||
filesToDelete.push(asset);
|
|
||||||
setProgress((progress) => ({
|
|
||||||
...progress,
|
|
||||||
// totalUploaded: progress.totalToUpload + asset.size,
|
|
||||||
totalFilesCompleted: progress.totalFilesCompleted + 1,
|
|
||||||
files: {
|
|
||||||
...progress.files,
|
|
||||||
[asset.uri]: {
|
|
||||||
...progress.files[asset.uri],
|
|
||||||
uploadEnd: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnProgress({ uri, filename }, percent, loaded) {
|
|
||||||
//NEED REDO
|
|
||||||
setProgress((progress) => {
|
|
||||||
return {
|
|
||||||
...progress,
|
|
||||||
totalUploaded:
|
|
||||||
progress.totalUploaded +
|
|
||||||
(loaded - (progress.files[uri]?.loaded || 0)),
|
|
||||||
files: {
|
|
||||||
...progress.files,
|
|
||||||
[uri]: {
|
|
||||||
...progress.files[uri],
|
|
||||||
percent,
|
|
||||||
filename,
|
|
||||||
speed: loaded - (progress.files[uri]?.loaded || 0),
|
|
||||||
loaded: loaded,
|
|
||||||
uploadStart: progress.files[uri]?.uploadStart || new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handleOnError(error) {
|
|
||||||
logImEXEvent("imexmobile_upload_documents_error", { error });
|
|
||||||
Toast.show({
|
|
||||||
type: "error",
|
|
||||||
text1: "Unable to upload document.",
|
|
||||||
text2: error,
|
|
||||||
autoHide: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDone = async (selectedFiles) => {
|
|
||||||
setProgress((progress) => {
|
|
||||||
return {
|
|
||||||
...progress,
|
|
||||||
uploadInProgress: true,
|
|
||||||
statusText: "Preparing upload...",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
//Validate to make sure the totals for the file sizes do not exceed the total on the job.
|
|
||||||
const data = [];
|
|
||||||
const totalOfUploads = await selectedFiles.reduce(async (acc, val) => {
|
|
||||||
//Get the size of the file based on URI.
|
|
||||||
if (acc.fileSize) {
|
|
||||||
return acc + acc.fileSize;
|
|
||||||
} else {
|
|
||||||
const info = new File(val.uri).size;
|
|
||||||
data.push({ ...info, ...val }); //Add in the size.
|
|
||||||
val.albumId && MediaLibrary.migrateAlbumIfNeededAsync(val.albumId);
|
|
||||||
return (await acc) + info.size;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if (selectedCameraJobId !== "temp") {
|
|
||||||
const queryData = await client.query({
|
|
||||||
query: GET_DOC_SIZE_TOTALS,
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
variables: {
|
|
||||||
jobId: selectedCameraJobId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
bodyshop.jobsizelimit -
|
|
||||||
queryData.data.documents_aggregate.aggregate.sum.size <=
|
|
||||||
totalOfUploads
|
|
||||||
) {
|
|
||||||
//No more room... abandon ship.
|
|
||||||
setProgress((progress) => ({
|
|
||||||
...progress,
|
|
||||||
speed: 0,
|
|
||||||
action: null,
|
|
||||||
statusText: null,
|
|
||||||
uploadInProgress: false,
|
|
||||||
}));
|
|
||||||
Alert.alert(
|
|
||||||
t("mediabrowser.labels.storageexceeded_title"),
|
|
||||||
t("mediabrowser.labels.storageexceeded")
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//We made it this far. We have enough space, so let's start uploading.
|
|
||||||
setProgress((progress) => ({
|
|
||||||
...progress,
|
|
||||||
totalToUpload: totalOfUploads,
|
|
||||||
totalUploaded: 0,
|
|
||||||
totalFilesCompleted: 0,
|
|
||||||
startTime: new Date(),
|
|
||||||
totalFiles: data.length,
|
|
||||||
currentFile: null,
|
|
||||||
statusText: null,
|
|
||||||
files: {}, //uri is the key, value is progress
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length + 4; i = i + 4) {
|
|
||||||
//Reset the files.
|
|
||||||
setProgress((progress) => ({ ...progress, files: {} }));
|
|
||||||
let proms = [];
|
|
||||||
if (data[i]) {
|
|
||||||
proms.push(CreateUploadProm(data[i]));
|
|
||||||
}
|
|
||||||
if (data[i + 1]) {
|
|
||||||
proms.push(CreateUploadProm(data[i + 1]));
|
|
||||||
}
|
|
||||||
if (data[i + 2]) {
|
|
||||||
proms.push(CreateUploadProm(data[i + 2]));
|
|
||||||
}
|
|
||||||
if (data[i + 3]) {
|
|
||||||
proms.push(CreateUploadProm(data[i + 3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(proms);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error during upload.", error, error.stack);
|
|
||||||
Sentry.captureException(error);
|
|
||||||
setProgress((progress) => ({
|
|
||||||
...progress,
|
|
||||||
speed: 0,
|
|
||||||
action: null,
|
|
||||||
statusText: null,
|
|
||||||
uploadInProgress: false,
|
|
||||||
}));
|
|
||||||
Alert.alert(
|
|
||||||
t("mediabrowser.labels.uploaderror_title"),
|
|
||||||
t("mediabrowser.labels.uploaderror")
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Everything is uploaded, delete the succesful ones.
|
|
||||||
if (deleteAfterUpload) {
|
|
||||||
try {
|
|
||||||
if (Platform.OS === "android") {
|
|
||||||
//Create a new asset with the first file to delete.
|
|
||||||
// console.log('Trying new delete.');
|
|
||||||
await MediaLibrary.getPermissionsAsync(false);
|
|
||||||
|
|
||||||
const album = await MediaLibrary.createAlbumAsync(
|
|
||||||
"ImEX Mobile Deleted",
|
|
||||||
filesToDelete.pop(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
//Move the rest.
|
|
||||||
if (filesToDelete.length > 0) {
|
|
||||||
const moveResult = await MediaLibrary.addAssetsToAlbumAsync(
|
|
||||||
filesToDelete,
|
|
||||||
album,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const deleteResult = await MediaLibrary.deleteAlbumsAsync(album);
|
|
||||||
|
|
||||||
//Delete the album.
|
|
||||||
|
|
||||||
//This defaults to delete all assets in the album.
|
|
||||||
} else {
|
|
||||||
await MediaLibrary.deleteAssetsAsync(filesToDelete.map((f) => f.id));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Unable to delete picture.", error);
|
|
||||||
Sentry.captureException(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filesToDelete = [];
|
|
||||||
Toast.show({
|
|
||||||
type: "success",
|
|
||||||
text1: ` Upload completed.`,
|
|
||||||
//
|
|
||||||
// text2: duration,
|
|
||||||
});
|
|
||||||
//Reset state.
|
|
||||||
|
|
||||||
setProgress({
|
|
||||||
uploadInProgress: false,
|
|
||||||
totalToUpload: 0,
|
|
||||||
totalUploaded: 0,
|
|
||||||
totalFilesCompleted: 0,
|
|
||||||
startTime: null,
|
|
||||||
totalFiles: 0,
|
|
||||||
currentFile: null,
|
|
||||||
files: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
forceRerender();
|
|
||||||
};
|
|
||||||
|
|
||||||
const CreateUploadProm = async (p) => {
|
|
||||||
return handleUpload(
|
|
||||||
{
|
|
||||||
mediaId: p.id,
|
|
||||||
onError: handleOnError,
|
|
||||||
onProgress: ({ percent, loaded }) =>
|
|
||||||
handleOnProgress(p, percent, loaded),
|
|
||||||
onSuccess: () => handleOnSuccess(p),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bodyshop: bodyshop,
|
|
||||||
jobId: selectedCameraJobId !== "temp" ? selectedCameraJobId : null,
|
|
||||||
uploaded_by: currentUser.email,
|
|
||||||
photo: p,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
visible={progress.uploadInProgress}
|
|
||||||
animationType="slide"
|
|
||||||
transparent={true}
|
|
||||||
onRequestClose={() => {
|
|
||||||
Alert.alert("Cancel?", "Do you want to abort the upload?", [
|
|
||||||
{
|
|
||||||
text: "Yes",
|
|
||||||
onPress: () => {
|
|
||||||
setUploads(null);
|
|
||||||
setProgress({
|
|
||||||
uploadInProgress: false,
|
|
||||||
totalToUpload: 0,
|
|
||||||
totalUploaded: 0,
|
|
||||||
totalFilesCompleted: 0,
|
|
||||||
startTime: null,
|
|
||||||
totalFiles: 0,
|
|
||||||
currentFile: null,
|
|
||||||
files: {},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ text: "No" },
|
|
||||||
]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View style={styles.modalContainer}>
|
|
||||||
<View style={styles.modal}>
|
|
||||||
{Object.keys(progress.files).map((key) => (
|
|
||||||
<View key={key} style={styles.progressItem}>
|
|
||||||
<Text style={styles.progressText}>
|
|
||||||
{progress.files[key].filename}
|
|
||||||
</Text>
|
|
||||||
<View style={styles.progressBarContainer}>
|
|
||||||
<ProgressBar
|
|
||||||
progress={progress.files[key].percent}
|
|
||||||
style={styles.progress}
|
|
||||||
color={progress.files[key].percent === 1 ? "green" : "blue"}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text>{`${formatBytes(
|
|
||||||
progress.files[key].loaded /
|
|
||||||
(((progress.files[key].uploadEnd || new Date()) -
|
|
||||||
progress.files[key].uploadStart) /
|
|
||||||
1000)
|
|
||||||
)}/sec`}</Text>
|
|
||||||
{progress.files[key].percent === 1 && (
|
|
||||||
<>
|
|
||||||
<ActivityIndicator style={{ marginLeft: 12 }} />
|
|
||||||
<Text style={{ marginLeft: 4 }}>Processing...</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
<View style={styles.centeredView}>
|
|
||||||
{progress.statusText ? (
|
|
||||||
<>
|
|
||||||
<ActivityIndicator style={{ marginLeft: 12 }} />
|
|
||||||
<Text style={{ marginLeft: 4 }}>{progress.statusText}</Text>
|
|
||||||
<Divider />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Text>{`${progress.totalFilesCompleted} of ${progress.totalFiles} uploaded.`}</Text>
|
|
||||||
<Text>{`${formatBytes(progress.totalUploaded)} of ${formatBytes(
|
|
||||||
progress.totalToUpload
|
|
||||||
)} uploaded.`}</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
modalContainer: {
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
//flex: 1,
|
|
||||||
display: "flex",
|
|
||||||
marginLeft: 20,
|
|
||||||
marginRight: 20,
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderRadius: 20,
|
|
||||||
padding: 18,
|
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: {
|
|
||||||
width: 0,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
shadowOpacity: 0.25,
|
|
||||||
shadowRadius: 4,
|
|
||||||
elevation: 5,
|
|
||||||
},
|
|
||||||
centeredView: {
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
marginTop: 22,
|
|
||||||
},
|
|
||||||
progressItem: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
marginBottom: 12,
|
|
||||||
marginLeft: 12,
|
|
||||||
marginRight: 12,
|
|
||||||
},
|
|
||||||
progressText: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
progressBarContainer: {
|
|
||||||
flex: 3,
|
|
||||||
marginLeft: 12,
|
|
||||||
marginRight: 12,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -47,8 +47,6 @@ export default function GlobalSearch() {
|
|||||||
if (!q) return;
|
if (!q) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
// TODO: Integrate real search endpoint
|
|
||||||
console.log(`[GlobalSearch] (debounced placeholder) searching for: "${q}"`);
|
|
||||||
try {
|
try {
|
||||||
const searchData = await axios.post(`${env.API_URL}/search`, {
|
const searchData = await axios.post(`${env.API_URL}/search`, {
|
||||||
search: q,
|
search: q,
|
||||||
|
|||||||
@@ -105,11 +105,6 @@ export function JobDocumentsComponent({ bodyshop }) {
|
|||||||
setFullPhotos(normalizedImages);
|
setFullPhotos(normalizedImages);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
|
||||||
"Error fetching photos:",
|
|
||||||
error.message,
|
|
||||||
JSON.stringify(error, null, 2)
|
|
||||||
);
|
|
||||||
setError(error.message || "Unknown error fetching photos.");
|
setError(error.message || "Unknown error fetching photos.");
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -144,7 +144,11 @@ function Tab({ bodyshop, currentUser, signOutStart }) {
|
|||||||
"Error",
|
"Error",
|
||||||
`Unable to register for notifications: ${error.message}`
|
`Unable to register for notifications: ${error.message}`
|
||||||
);
|
);
|
||||||
console.log("Notification registration error:", error);
|
console.log(
|
||||||
|
"Notification registration error:",
|
||||||
|
error,
|
||||||
|
error.stack
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -161,7 +165,9 @@ function Tab({ bodyshop, currentUser, signOutStart }) {
|
|||||||
}`}
|
}`}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.paragraph}>
|
<Text style={styles.paragraph}>
|
||||||
{`${t("settings.labels.signedinuser")} ${currentUser?.email || "Unknown"}`}
|
{`${t("settings.labels.signedinuser")} ${
|
||||||
|
currentUser?.email || "Unknown"
|
||||||
|
}`}
|
||||||
</Text>
|
</Text>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
<Card.Actions>
|
<Card.Actions>
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
|
|
||||||
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Platform } from 'react-native';
|
|
||||||
|
|
||||||
const tintColorLight = '#0a7ea4';
|
|
||||||
const tintColorDark = '#fff';
|
|
||||||
|
|
||||||
export const Colors = {
|
|
||||||
light: {
|
|
||||||
text: '#11181C',
|
|
||||||
background: '#fff',
|
|
||||||
tint: tintColorLight,
|
|
||||||
icon: '#687076',
|
|
||||||
tabIconDefault: '#687076',
|
|
||||||
tabIconSelected: tintColorLight,
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
text: '#ECEDEE',
|
|
||||||
background: '#151718',
|
|
||||||
tint: tintColorDark,
|
|
||||||
icon: '#9BA1A6',
|
|
||||||
tabIconDefault: '#9BA1A6',
|
|
||||||
tabIconSelected: tintColorDark,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Fonts = Platform.select({
|
|
||||||
ios: {
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignDefault` */
|
|
||||||
sans: 'system-ui',
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignSerif` */
|
|
||||||
serif: 'ui-serif',
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignRounded` */
|
|
||||||
rounded: 'ui-rounded',
|
|
||||||
/** iOS `UIFontDescriptorSystemDesignMonospaced` */
|
|
||||||
mono: 'ui-monospace',
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
sans: 'normal',
|
|
||||||
serif: 'serif',
|
|
||||||
rounded: 'normal',
|
|
||||||
mono: 'monospace',
|
|
||||||
},
|
|
||||||
web: {
|
|
||||||
sans: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
||||||
serif: "Georgia, 'Times New Roman', serif",
|
|
||||||
rounded: "'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif",
|
|
||||||
mono: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
2
env.js
2
env.js
@@ -64,11 +64,9 @@ const ENV = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const IS_ROME = Constants?.expoConfig?.extra?.appVariant === 'ROME';
|
const IS_ROME = Constants?.expoConfig?.extra?.appVariant === 'ROME';
|
||||||
//console.log("*** ~ Constants?.expoConfig?.extra:", Constants?.expoConfig);
|
|
||||||
|
|
||||||
function getEnvVars() {
|
function getEnvVars() {
|
||||||
if (Updates.channel !== "production") return IS_ROME ? ENV.rometest : ENV.test;
|
if (Updates.channel !== "production") return IS_ROME ? ENV.rometest : ENV.test;
|
||||||
else return IS_ROME ? ENV.romeprod : ENV.prod;
|
else return IS_ROME ? ENV.romeprod : ENV.prod;
|
||||||
}
|
}
|
||||||
console.log('APP_VARIANT:', Constants?.expoConfig?.extra?.appVariant, 'IS_ROME:', IS_ROME, "ENV: ", getEnvVars());
|
|
||||||
export default getEnvVars();
|
export default getEnvVars();
|
||||||
|
|||||||
@@ -25,34 +25,14 @@ const errorLink = onError(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const subscriptionMiddleware = {
|
// const subscriptionMiddleware = {
|
||||||
applyMiddleware: async (options, next) => {
|
// applyMiddleware: async (options, next) => {
|
||||||
options.authToken =
|
// options.authToken =
|
||||||
auth.currentUser && (await auth.currentUser.getIdToken(true));
|
// auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||||
next();
|
// next();
|
||||||
},
|
|
||||||
};
|
|
||||||
//wsLink.subscriptionClient.use([subscriptionMiddleware]);
|
|
||||||
|
|
||||||
// const link = split(
|
|
||||||
// // split based on operation type
|
|
||||||
// ({ query }) => {
|
|
||||||
// const definition = getMainDefinition(query);
|
|
||||||
// // console.log(
|
|
||||||
// // "##Intercepted GQL Transaction : " +
|
|
||||||
// // definition.operation +
|
|
||||||
// // "|" +
|
|
||||||
// // // definition.name.value +
|
|
||||||
// // "##"
|
|
||||||
// // );
|
|
||||||
// return (
|
|
||||||
// definition.kind === "OperationDefinition" &&
|
|
||||||
// definition.operation === "subscription"
|
|
||||||
// );
|
|
||||||
// },
|
// },
|
||||||
// wsLink,
|
// };
|
||||||
// httpLink
|
|
||||||
// );
|
|
||||||
|
|
||||||
const authLink = setContext((_, { headers }) => {
|
const authLink = setContext((_, { headers }) => {
|
||||||
return (
|
return (
|
||||||
@@ -84,7 +64,7 @@ const retryLink = new RetryLink({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const cache = new InMemoryCache({});
|
const cache = new InMemoryCache();
|
||||||
|
|
||||||
export const client = new ApolloClient({
|
export const client = new ApolloClient({
|
||||||
//link: ApolloLink.from(middlewares),
|
//link: ApolloLink.from(middlewares),
|
||||||
|
|||||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "imexmobile",
|
"name": "imexmobile",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.12.11",
|
"@apollo/client": "^3.14.0",
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
"react-native-image-viewing": "^0.2.2",
|
"react-native-image-viewing": "^0.2.2",
|
||||||
"react-native-paper": "^5.14.5",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-reanimated": "~4.1.3",
|
"react-native-reanimated": "~4.1.3",
|
||||||
|
"react-native-reanimated-dnd": "^1.1.0",
|
||||||
"react-native-safe-area-context": "~5.6.1",
|
"react-native-safe-area-context": "~5.6.1",
|
||||||
"react-native-screens": "~4.17.1",
|
"react-native-screens": "~4.17.1",
|
||||||
"react-native-tab-view": "4.1.3",
|
"react-native-tab-view": "4.1.3",
|
||||||
@@ -2792,9 +2793,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@expo/vector-icons": {
|
"node_modules/@expo/vector-icons": {
|
||||||
"version": "15.0.2",
|
"version": "15.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz",
|
||||||
"integrity": "sha512-IiBjg7ZikueuHNf40wSGCf0zS73a3guJLdZzKnDUxsauB8VWPLMeWnRIupc+7cFhLUkqyvyo0jLNlcxG5xPOuQ==",
|
"integrity": "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"expo-font": ">=14.0.4",
|
"expo-font": ">=14.0.4",
|
||||||
@@ -13285,6 +13286,18 @@
|
|||||||
"react-native-worklets": ">=0.5.0"
|
"react-native-worklets": ">=0.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-reanimated-dnd": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-reanimated-dnd/-/react-native-reanimated-dnd-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-9ZgdAFsw2rjB/0VE3wsR9+PnBOWIoO+1s6lEiyV51ptBcoz0NaUnUb8aqGocEztnXSbySSznpOh5vy6Ijo9A3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-native": ">=0.60.0",
|
||||||
|
"react-native-gesture-handler": ">=2.0.0",
|
||||||
|
"react-native-reanimated": ">=3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-reanimated/node_modules/semver": {
|
"node_modules/react-native-reanimated/node_modules/semver": {
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"lint": "expo lint"
|
"lint": "expo lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.12.11",
|
"@apollo/client": "^3.14.0",
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
"react-native-image-viewing": "^0.2.2",
|
"react-native-image-viewing": "^0.2.2",
|
||||||
"react-native-paper": "^5.14.5",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-reanimated": "~4.1.3",
|
"react-native-reanimated": "~4.1.3",
|
||||||
|
"react-native-reanimated-dnd": "^1.1.0",
|
||||||
"react-native-safe-area-context": "~5.6.1",
|
"react-native-safe-area-context": "~5.6.1",
|
||||||
"react-native-screens": "~4.17.1",
|
"react-native-screens": "~4.17.1",
|
||||||
"react-native-tab-view": "4.1.3",
|
"react-native-tab-view": "4.1.3",
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function* openImagePickerAction({ payload: jobid }) {
|
|||||||
yield put(mediaUploadStart({ photos: result.assets, jobid }));
|
yield put(mediaUploadStart({ photos: result.assets, jobid }));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Saga Error: open Picker", error);
|
// console.log("Saga Error: open Picker", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,8 +93,6 @@ export function* onMediaUploadStart() {
|
|||||||
|
|
||||||
export function* mediaUploadStartAction({ payload: { photos, jobid } }) {
|
export function* mediaUploadStartAction({ payload: { photos, jobid } }) {
|
||||||
try {
|
try {
|
||||||
console.log("Starting upload for", photos.length, "photos");
|
|
||||||
|
|
||||||
const bodyshop = yield select(selectBodyshop);
|
const bodyshop = yield select(selectBodyshop);
|
||||||
|
|
||||||
if (bodyshop.uselocalmediaserver) {
|
if (bodyshop.uselocalmediaserver) {
|
||||||
@@ -130,7 +128,7 @@ export function* mediaUploadStartAction({ payload: { photos, jobid } }) {
|
|||||||
yield delay(100);
|
yield delay(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("All uploads completed. This shouldn't fire before the uploads are done.");
|
|
||||||
yield put(mediaUploadCompleted(photos));
|
yield put(mediaUploadCompleted(photos));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user