Refactor to using RNP & UI Updates.

This commit is contained in:
Patrick Fic
2021-03-11 19:10:27 -07:00
parent 59f6605a40
commit a912b4f1d7
26 changed files with 689 additions and 440 deletions

16
App.js
View File

@@ -26,29 +26,17 @@ const theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: "tomato",
accent: "yellow",
primary: "dodgerblue",
accent: "tomato",
},
};
export default class App extends React.Component {
// constructor(props) {
// super(props);
// this.state = {
// isReady: false,
// };
// }
async componentDidMount() {
logImEXEvent("imexmobile_app_start");
//this.setState({ isReady: true });
}
render() {
// if (!this.state.isReady) {
// return <AppLoading />;
// }
return (
<Provider store={store}>
<PersistGate persistor={persistor}>

View File

@@ -162,6 +162,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dates</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>documents</name>
<definition_loaded>false</definition_loaded>
@@ -225,6 +246,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobinfo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines</name>
<definition_loaded>false</definition_loaded>
@@ -246,6 +288,132 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines_desc</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines_lb_hrs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines_lbr_ty</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines_part_type</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines_price</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>lines_qty</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>nojobnotes</name>
<definition_loaded>false</definition_loaded>

View File

@@ -1,8 +1,9 @@
import { useQuery } from "@apollo/client";
import { Ionicons } from "@expo/vector-icons";
import React from "react";
import { useTranslation } from "react-i18next";
import { FlatList, RefreshControl } from "react-native";
import { Button, List, Modal, Portal, Provider } from "react-native-paper";
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";
@@ -47,23 +48,80 @@ export function CameraSelectJob({
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
const containerStyle = { backgroundColor: "white", padding: 20 };
const onRefresh = async () => {
return refetch();
};
const [searchQuery, setSearchQuery] = React.useState("");
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 (
<Provider>
<>
<Portal>
<Modal
visible={visible}
onDismiss={hideModal}
contentContainerStyle={containerStyle}
// eslint-disable-next-line react-native/no-color-literals
contentContainerStyle={{
paddingTop: 20,
paddingBottom: 20,
flex: 1,
backgroundColor: "white",
}}
>
<View
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
margin: 8,
}}
>
<Button onPress={() => hideModal()}>
<Ionicons name="ios-arrow-back" size={32} color="dodgerblue" />
</Button>
<Searchbar
style={{ flex: 1 }}
onChangeText={onChangeSearch}
value={searchQuery}
/>
</View>
<FlatList
refreshControl={
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
}
data={data.jobs}
data={[{ id: "temp", ro_number: "Temporary Storage" }, ...jobs]}
keyExtractor={(item) => item.id}
renderItem={(object) => (
<List.Item
@@ -71,8 +129,25 @@ export function CameraSelectJob({
setCameraJobId(object.item.id);
setCameraJob(object.item);
hideModal();
setSearchQuery("");
}}
description={`${
left={() => {
if (object.item.id !== cameraJobId) return null;
return (
<Ionicons
name="ios-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 || ""
@@ -85,41 +160,19 @@ export function CameraSelectJob({
/>
</Modal>
</Portal>
<Button style={{ marginTop: 30 }} onPress={showModal}>
<Button mode="outlined" style={{ margin: 8 }} onPress={showModal}>
{cameraJobId
? `${cameraJob.ro_number ? `${cameraJob.ro_number} - ` : ``}${
? 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>
</Provider>
</>
);
// return (
// <View
// style={{
// marginHorizontal: 10,
// }}
// >
// <Picker
// selectedValue={cameraJobId}
// onValueChange={(value, idx) => {
// logImEXEvent("imexmobile_setcamerajobid");
// setCameraJobId(value);
// setCameraJob(data.jobs[idx]);
// }}
// >
// <Picker.Item
// label={t("mediabrowser.labels.selectjob")}
// value={null}
// key="null"
// />
// </Picker>
// </View>
// );
}
export default connect(mapStateToProps, mapDispatchToProps)(CameraSelectJob);

View File

@@ -1,36 +1,54 @@
import React, { useState } from "react";
import React, { useState, useMemo } from "react";
import {
FlatList,
Image,
RefreshControl,
StyleSheet,
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";
const REACT_APP_CLOUDINARY_IMAGE_ENDPOINT =
"https://res.cloudinary.com/bodyshop/image/upload";
const REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS = "c_fill,f_auto,h_250,w_250";
export default function JobDocumentsComponent({ job, loading, refetch }) {
const [previewVisible, setPreviewVisible] = useState(false);
const [imgIndex, setImgIndex] = useState(0);
const onRefresh = async () => {
return refetch();
};
const fullphotos = useMemo(
() =>
job.documents.map((doc) => {
return {
source: {
uri: `${env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
doc.type
)}/upload/${doc.key}`,
},
};
}),
[job.documents]
);
return (
<View>
<View style={{ flex: 1 }}>
<FlatList
refreshControl={
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
}
data={job.documents}
contentContainerStyle={styles.listContentContainer}
keyExtractor={(item) => item.id}
numColumns={4}
keyExtractor={(item) => item.id}
renderItem={(object) => (
<View
style={{
flex: 1,
flexDirection: "column",
margin: 5,
}}
>
<TouchableOpacity
onPress={() => {
setImgIndex(object.index);
@@ -38,18 +56,23 @@ export default function JobDocumentsComponent({ job, loading, refetch }) {
}}
>
<Image
style={{ margin: 5 }}
source={{
width: 100,
height: 100,
uri: `${REACT_APP_CLOUDINARY_IMAGE_ENDPOINT}/${REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${object.item.key}`,
uri: `${
env.REACT_APP_CLOUDINARY_ENDPOINT
}/${DetermineFileType(object.item.type)}/upload/${
env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
}/${object.item.key}`,
}}
/>
</TouchableOpacity>
</View>
)}
/>
<Text>{job.documents.length}</Text>
<MediaCacheOverlay
photos={fullphotos}
imgIndex={imgIndex}
setImgIndex={setImgIndex}
previewVisible={previewVisible}
@@ -58,22 +81,3 @@ export default function JobDocumentsComponent({ job, loading, refetch }) {
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
actions: {
display: "flex",
flexDirection: "row",
justifyContent: "space-evenly",
},
listContentContainer: {
flex: 1,
},
thumbnail: {
width: 10,
height: 10,
backgroundColor: "tomato",
},
});

View File

@@ -2,7 +2,7 @@ import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";
import { Card } from "react-native-paper";
import { Card, DataTable } from "react-native-paper";
export default function JobLines({ job, loading, refetch }) {
const { t } = useTranslation();
@@ -18,60 +18,64 @@ export default function JobLines({ job, loading, refetch }) {
return (
<View>
<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.Title style={{ flex: 1 }}>
{t("jobdetail.labels.lines_price")}
</DataTable.Title>
</DataTable.Header>
</DataTable>
<FlatList
data={job.joblines}
refreshControl={
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
}
contentContainerStyle={localStyles.listContentContainer}
keyExtractor={(item) => item.id}
renderItem={(object) => (
<Card>
<Card.Content style={localStyles.flexRow}>
<Text style={localStyles.growWithEllipsis}>{`${
object.item.line_desc
}${
object.item.part_qty > 1 ? ` x ${object.item.part_qty}` : ""
}`}</Text>
{object.item.part_type && (
<Text style={localStyles.sideMargins}>
{t(`jobdetail.part_types.${object.item.part_type}`)}
</Text>
)}
<Text style={localStyles.sideMargins}>
<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.Cell style={{ flex: 1 }}>
{Dinero({
amount: Math.round((object.item.act_price || 0) * 100),
}).toFormat()}
</Text>
</Card.Content>
<Card.Content style={localStyles.flexRow}>
{object.item.mod_lbr_ty && (
<Text>
{t(`jobdetail.lbr_types.${object.item.mod_lbr_ty}`)}
</Text>
)}
</Card.Content>
</Card>
</DataTable.Cell>
</DataTable.Row>
)}
/>
</View>
);
}
const localStyles = StyleSheet.create({
listContentContainer: {
flex: 1,
},
flexRow: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
},
sideMargins: {
marginLeft: 5,
marginRight: 5,
},
growWithEllipsis: {
flex: 1,
},
});
const localStyles = StyleSheet.create({});

View File

@@ -4,8 +4,7 @@ import React, { useRef } from "react";
import { useTranslation } from "react-i18next";
import { Animated } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import Swipeable from "react-native-gesture-handler/Swipeable";
import { List } from "react-native-paper";
import { Button, List, Title } from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
@@ -60,21 +59,35 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) {
};
return (
<Swipeable
ref={_swipeableRow}
renderRightActions={RenderRightAction}
shouldCancelWhenOutside
>
<TouchableOpacity onPress={onPress}>
<List.Item
onPress={onPress}
title={item.ro_number || t("general.labels.na")}
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}
onPress={() => {
logImEXEvent("imexmobile_setcamerajobid_swipe");
setCameraJobId(item.id);
setCameraJob(item);
navigation.navigate("MediaBrowserTab");
_swipeableRow.current.close();
}}
>
<Ionicons
style={style}
name="ios-add"
size={32}
color="dodgerblue"
/>
</Swipeable>
</Button>
)}
/>
</TouchableOpacity>
);
}

View File

@@ -5,7 +5,7 @@ import { Text, View } from "react-native";
import { Card } from "react-native-paper";
export default function NoteListItem({ item }) {
return (
<Card>
<Card style={{ margin: 8 }}>
<Card.Content>
<View style={{ display: "flex", flex: 1 }}>
<Text>{item.text}</Text>

View File

@@ -1,8 +1,8 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { FlatList, RefreshControl, Text } from "react-native";
import JobNotesItem from "../job-notes-item/job-notes-item.component";
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) {
@@ -14,6 +14,7 @@ export default function JobNotes({ job, loading, refetch }) {
const onRefresh = async () => {
return refetch();
};
if (job.notes.length === 0)
return (
<Card>
@@ -31,7 +32,6 @@ export default function JobNotes({ job, loading, refetch }) {
style={{ flex: 1 }}
data={job.notes}
renderItem={(object) => <JobNotesItem item={object.item} />}
//ItemSeparatorComponent={FlatListItemSeparator}
/>
);
}

View File

@@ -1,14 +1,16 @@
import { Card } from "react-native-paper";
import React from "react";
import { useTranslation } from "react-i18next";
import {
RefreshControl,
StyleSheet,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { Card, Headline, Subheading } from "react-native-paper";
import DataLabelComponent from "../data-label/data-label.component";
import StyleRepeater from "../style-repeater/style-repeater";
import styles from "../styles";
export default function JobTombstone({ job, loading, refetch }) {
const { t } = useTranslation();
@@ -23,31 +25,31 @@ export default function JobTombstone({ job, loading, refetch }) {
return (
<ScrollView
padder
style={styles.cardBackground}
refreshControl={
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
}
>
<StyleRepeater childStyle={{ margin: 4 }}>
<Card>
<Card.Content bordered style={localStyles.status}>
<Text>{job.status}</Text>
</Card.Content>
<Card.Title title={t("jobdetail.labels.jobinfo")} />
<Card.Content>
<Headline>{job.status}</Headline>
{job.inproduction && (
<Card.Content bordered style={localStyles.inproduction}>
<Text>{t("objects.jobs.labels.inproduction")}</Text>
</Card.Content>
<Subheading>{t("objects.jobs.labels.inproduction")}</Subheading>
)}
{job.inproduction && job.production_vars && !!job.production_vars.note && (
<Card.Content bordered style={localStyles.inproduction}>
<Text>{job.production_vars.note}</Text>
</Card.Content>
{job.inproduction &&
job.production_vars &&
!!job.production_vars.note && (
<Subheading>{job.production_vars.note}</Subheading>
)}
</Card.Content>
</Card>
<Card>
<Card.Content bordered style={localStyles.status}>
<Text>{t("jobdetail.labels.claiminformation")}</Text>
</Card.Content>
<View>
<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 || ""} ${
@@ -60,6 +62,8 @@ export default function JobTombstone({ job, loading, refetch }) {
job.v_model_desc || ""
}`}
/>
</View>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.ins_co_nm")}
content={job.ins_co_nm}
@@ -69,16 +73,16 @@ export default function JobTombstone({ job, loading, refetch }) {
content={job.clm_no}
/>
</View>
</Card.Content>
</Card>
<Card>
<Card.Content bordered style={localStyles.status}>
<Text>{t("jobdetail.labels.employeeassignments")}</Text>
</Card.Content>
<View>
<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.first_name) ||
""
} ${
(job.employee_body_rel && job.employee_body_rel.last_name) || ""
}`}
@@ -86,7 +90,8 @@ export default function JobTombstone({ job, loading, refetch }) {
<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.first_name) ||
""
} ${
(job.employee_prep_rel && job.employee_prep_rel.last_name) || ""
}`}
@@ -103,9 +108,11 @@ export default function JobTombstone({ job, loading, refetch }) {
""
}`}
/>
</View>
</Card.Content>
</Card>
<Card style={localStyles.twoColumnCard}>
<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")}
@@ -130,7 +137,9 @@ export default function JobTombstone({ job, loading, refetch }) {
dateTime
/>
</View>
</Card.Content>
</Card>
</StyleRepeater>
</ScrollView>
);
}
@@ -144,7 +153,6 @@ const localStyles = StyleSheet.create({
justifyContent: "center",
},
inproduction: {
backgroundColor: "tomato",
textAlign: "center",
flexDirection: "row",
justifyContent: "center",

View File

@@ -1,84 +1,23 @@
import React from "react";
import { Alert, Modal, StyleSheet, Text, Button, View } from "react-native";
import ImageViewer from "react-native-image-zoom-viewer";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { removePhotos } from "../../redux/photos/photos.actions";
import { selectPhotos } from "../../redux/photos/photos.selectors";
import { Modal } from "react-native";
import Gallery from "react-native-image-gallery";
const mapStateToProps = createStructuredSelector({
photos: selectPhotos,
});
const mapDispatchToProps = (dispatch) => ({
removePhotos: (ids) => dispatch(removePhotos(ids)),
});
export function MediaCacheOverlay({
export default function MediaCacheOverlay({
photos,
removePhotos,
previewVisible,
setPreviewVisible,
imgIndex,
setImgIndex,
}) {
console.log("photos :>> ", photos);
return (
<Modal
animationType="slide"
onRequestClose={() => {
Alert.alert("Modal has been closed.");
}}
onDismiss={() => setPreviewVisible(false)}
onRequestClose={() => setPreviewVisible(false)}
visible={previewVisible}
transparent={true}
transparent={false}
>
<ImageViewer
onCancel={() => setPreviewVisible(false)}
index={imgIndex}
onChange={(index) => setImgIndex(index)}
style={{ display: "flex" }}
renderFooter={(index) => (
<View
style={{
marginleft: "auto",
backgroundColor: "tomato",
}}
>
<Text>{index} This is the thing.</Text>
<Button
onPress={() => {
removePhotos([photos[index].id]);
}}
>
<Text>Delete</Text>
</Button>
</View>
)}
enableSwipeDown
enablePreload
imageUrls={photos.map((p) => {
return { url: p.uri };
})}
/>
<Gallery initialPage={imgIndex} style={{ flex: 1 }} images={photos} />
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
actions: {
display: "flex",
flexDirection: "row",
justifyContent: "space-evenly",
},
listContentContainer: {
flex: 1,
},
thumbnail: {
width: 10,
height: 10,
backgroundColor: "tomato",
},
});
export default connect(mapStateToProps, mapDispatchToProps)(MediaCacheOverlay);

View File

@@ -2,7 +2,7 @@ import { useQuery } from "@apollo/client";
import React from "react";
import { useTranslation } from "react-i18next";
import { useWindowDimensions } from "react-native";
import { SceneMap, TabView } from "react-native-tab-view";
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";
@@ -24,6 +24,14 @@ export default function ScreenJobDetail({ route }) {
skip: !jobId,
});
const renderTabBar = (props) => (
<TabBar
{...props}
indicatorStyle={{ backgroundColor: "white" }}
style={{ backgroundColor: "dodgerblue" }}
/>
);
const renderScene = SceneMap({
job: () =>
JobTombstone({
@@ -68,6 +76,7 @@ export default function ScreenJobDetail({ route }) {
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
renderTabBar={renderTabBar}
/>
);
}

View File

@@ -64,7 +64,6 @@ export function ImageBrowserScreen({
}
const onDone = async (data) => {
console.log("Assets :>> ", data);
logImEXEvent("imexmobile_upload_documents", { count: data.length });
const actions = [];
data.forEach(function (p) {

View File

@@ -1,7 +1,8 @@
import { Formik } from "formik";
import React from "react";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, Image, StyleSheet, View, Text } from "react-native";
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";
@@ -11,8 +12,6 @@ import {
selectSigningIn,
} from "../../redux/user/user.selectors";
import SignInErrorAlertComponent from "../sign-in-error-alert/sign-in-error-alert.component";
import styles from "../styles";
import { TextInput, Button, Subheading } from "react-native-paper";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -33,41 +32,46 @@ export function SignIn({ emailSignInStart, signingIn }) {
};
return (
<View style={localStyles.content}>
<View
scrollEnabled={false}
contentContainerStyle={styles.contentContainer__centered}
style={localStyles.content}
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-evenly",
}}
>
<View style={styles.evenlySpacedRow}>
<Image style={localStyles.logo} source={Logo} />
<Text>{t("app.title")}</Text>
<Title>{t("app.title")}</Title>
</View>
<Formik initialValues={{ email: "", password: "" }} onSubmit={formSubmit}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<View>
<Subheading>{t("signin.fields.email")}</Subheading>
<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]}
/>
</View>
<View>
<Subheading>{t("signin.fields.password")}</Subheading>
<TextInput
label={t("signin.fields.password")}
mode="outlined"
secureTextEntry={true}
onChangeText={handleChange("password")}
onBlur={handleBlur("password")}
value={values.password}
style={[localStyles.input]}
/>
</View>
<SignInErrorAlertComponent />
<Button full onPress={handleSubmit}>
<Button mode="outlined" loading={signingIn} onPress={handleSubmit}>
<Text>{t("signin.actions.signin")}</Text>
{signingIn ? <ActivityIndicator size="large" /> : null}
</Button>
</View>
)}
@@ -78,9 +82,13 @@ export function SignIn({ emailSignInStart, signingIn }) {
const localStyles = StyleSheet.create({
content: {
paddingBottom: 200,
display: "flex",
flex: 1,
},
logo: { width: 100, height: 100 },
input: {
margin: 12,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);

View File

@@ -1,29 +1,35 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Image, StyleSheet, View } from "react-native";
import { BarIndicator } from "react-native-indicators";
import { ActivityIndicator, Image, StyleSheet, View } from "react-native";
import { Title } from "react-native-paper";
import Logo from "../../assets/logo192.png";
import styles from "../styles";
export default function ScreenSplash() {
const { t } = useTranslation();
return (
<View
contentContainerStyle={[
styles.contentContainer__centered,
localStyles.middleAlign,
]}
>
<View style={[localStyles.container]}>
<View style={[localStyles.logoContainer]}>
<Image style={localStyles.logo} source={Logo} />
<Title>{t("app.title")}</Title>
<BarIndicator count={5} color="dodgerblue" />
</View>
<ActivityIndicator color="dodgerblue" size="large" />
</View>
);
}
const localStyles = StyleSheet.create({
middleAlign: {
container: {
display: "flex",
flex: 1,
flexDirection: "column",
alignContent: "center",
justifyContent: "center",
},
logoContainer: {
display: "flex",
flexDirection: "column",
alignItems: "center",
},
logo: { width: 100, height: 100, margin: 20 },
logo: { width: 175, height: 175, margin: 20 },
});

View File

@@ -0,0 +1,13 @@
import React from "react";
export default function StyleRepeater({ childStyle, children }) {
return (
<>
{React.Children.map(children, (child) =>
React.cloneElement(child, {
style: [child.props.style, childStyle],
})
)}
</>
);
}

View File

@@ -1,30 +1,12 @@
import { StyleSheet } from "react-native";
const cardBackgroundColor = "gainsboro";
export default StyleSheet.create({
contentContainer__centered: {
justifyContent: "center",
cardBackground: {
padding: 5,
backgroundColor: cardBackgroundColor,
display: "flex",
flex: 1,
},
evenlySpacedRow: {
flexDirection: "row",
justifyContent: "space-evenly",
alignItems: "center",
},
swipe_view: {
flex: 1,
alignItems: "center",
justifyContent: "center",
width: 100,
// marginTop: 5,
//marginBottom: 5,
},
swipe_view_blue: {
backgroundColor: "dodgerblue",
},
swipe_text: {
textAlign: "center",
color: "white",
},
});

View File

@@ -1,5 +1,5 @@
import { Ionicons } from "@expo/vector-icons";
import React from "react";
import React, { useMemo } from "react";
import {
ScrollView,
StyleSheet,
@@ -10,10 +10,15 @@ import {
import * as Progress from "react-native-progress";
import _ from "lodash";
export default function UploadProgress({ uploads, setUploads }) {
const uploadKeys = useMemo(() => {
if (uploads) return Object.keys(uploads);
return [];
}, [uploads]);
return (
<View style={styles.container}>
<ScrollView>
{Object.keys(uploads).map((key) => (
{uploadKeys.map((key) => (
<View key={key} style={styles.progressItem}>
<Text style={styles.progressText}>{key}</Text>
<View style={styles.progressBarContainer}>

1
env.js
View File

@@ -5,6 +5,7 @@ export const prodUrl = "https://someapp.herokuapp.com";
const ENV = {
dev: {
API_URL: "https://api.imex.online",
REACT_APP_CLOUDINARY_ENDPOINT_API:
"https://api.cloudinary.com/v1_1/bodyshop",
REACT_APP_CLOUDINARY_ENDPOINT: "https://res.cloudinary.com/bodyshop",

View File

@@ -274,6 +274,7 @@ export const GET_JOB_BY_PK = gql`
name
key
created_at
type
}
}
}

71
package-lock.json generated
View File

@@ -42,7 +42,7 @@
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
"react-native-easy-grid": "^0.2.2",
"react-native-gesture-handler": "~1.8.0",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-image-gallery": "^2.1.5",
"react-native-indicators": "^0.17.0",
"react-native-pager-view": "^5.1.0",
"react-native-paper": "^4.7.2",
@@ -13001,6 +13001,15 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-mixin": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/react-mixin/-/react-mixin-3.1.1.tgz",
"integrity": "sha512-z9fZ0aCRDjlgxLdMeWkJ9TwhmVLhQ09r8RFpin/cEPA2T6jsb7YHNWcIe0Oii+hhJNyMymdy91CSya5mRkuCkg==",
"dependencies": {
"object-assign": "^4.0.1",
"smart-mixin": "^2.0.0"
}
},
"node_modules/react-native": {
"version": "0.63.2",
"license": "MIT",
@@ -13063,23 +13072,14 @@
"prop-types": "^15.7.2"
}
},
"node_modules/react-native-image-pan-zoom": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz",
"integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==",
"license": "ISC",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-image-zoom-viewer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz",
"integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==",
"license": "MIT",
"node_modules/react-native-image-gallery": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/react-native-image-gallery/-/react-native-image-gallery-2.1.5.tgz",
"integrity": "sha512-xC7nuPu4GUH0da6byofQ10LjtqlKj+VaLc0NHBJmeMHVvdvmRvFEO6UOq0Q0m/ePx3OQiPTNwGbf5BSPJEKa0w==",
"dependencies": {
"react-native-image-pan-zoom": "^2.1.12"
"prop-types": "^15.6.0",
"react-mixin": "^3.0.5",
"react-timer-mixin": "^0.13.3"
},
"peerDependencies": {
"react": "*",
@@ -14223,6 +14223,11 @@
"node": ">=8.0.0"
}
},
"node_modules/smart-mixin": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/smart-mixin/-/smart-mixin-2.0.0.tgz",
"integrity": "sha1-o0oQVeMqdbMNK048oyPcmctT9Dc="
},
"node_modules/snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@@ -25256,6 +25261,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-mixin": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/react-mixin/-/react-mixin-3.1.1.tgz",
"integrity": "sha512-z9fZ0aCRDjlgxLdMeWkJ9TwhmVLhQ09r8RFpin/cEPA2T6jsb7YHNWcIe0Oii+hhJNyMymdy91CSya5mRkuCkg==",
"requires": {
"object-assign": "^4.0.1",
"smart-mixin": "^2.0.0"
}
},
"react-native": {
"version": "0.63.2",
"requires": {
@@ -25317,18 +25331,14 @@
"prop-types": "^15.7.2"
}
},
"react-native-image-pan-zoom": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz",
"integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==",
"requires": {}
},
"react-native-image-zoom-viewer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz",
"integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==",
"react-native-image-gallery": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/react-native-image-gallery/-/react-native-image-gallery-2.1.5.tgz",
"integrity": "sha512-xC7nuPu4GUH0da6byofQ10LjtqlKj+VaLc0NHBJmeMHVvdvmRvFEO6UOq0Q0m/ePx3OQiPTNwGbf5BSPJEKa0w==",
"requires": {
"react-native-image-pan-zoom": "^2.1.12"
"prop-types": "^15.6.0",
"react-mixin": "^3.0.5",
"react-timer-mixin": "^0.13.3"
}
},
"react-native-indicators": {
@@ -26146,6 +26156,11 @@
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz",
"integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg=="
},
"smart-mixin": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/smart-mixin/-/smart-mixin-2.0.0.tgz",
"integrity": "sha1-o0oQVeMqdbMNK048oyPcmctT9Dc="
},
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",

View File

@@ -45,7 +45,7 @@
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
"react-native-easy-grid": "^0.2.2",
"react-native-gesture-handler": "~1.8.0",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-image-gallery": "^2.1.5",
"react-native-indicators": "^0.17.0",
"react-native-pager-view": "^5.1.0",
"react-native-paper": "^4.7.2",

View File

@@ -19,10 +19,18 @@
"jobdetail": {
"labels": {
"claiminformation": "Claim Information",
"documents": "Documents",
"dates": "Dates",
"documents": "Docs",
"employeeassignments": "Employee Assignments",
"job": "Job",
"jobinfo": "Job Information",
"lines": "Lines",
"lines_desc": "Desc.",
"lines_lb_hrs": "Hrs",
"lines_lbr_ty": "Lbr. Ty.",
"lines_part_type": "Part Ty.",
"lines_price": "$",
"lines_qty": "Qty.",
"nojobnotes": "There are no notes.",
"notes": "Notes"
},

View File

@@ -19,10 +19,18 @@
"jobdetail": {
"labels": {
"claiminformation": "",
"dates": "",
"documents": "",
"employeeassignments": "",
"job": "",
"jobinfo": "",
"lines": "",
"lines_desc": "",
"lines_lb_hrs": "",
"lines_lbr_ty": "",
"lines_part_type": "",
"lines_price": "",
"lines_qty": "",
"nojobnotes": "",
"notes": ""
},

View File

@@ -19,10 +19,18 @@
"jobdetail": {
"labels": {
"claiminformation": "",
"dates": "",
"documents": "",
"employeeassignments": "",
"job": "",
"jobinfo": "",
"lines": "",
"lines_desc": "",
"lines_lb_hrs": "",
"lines_lbr_ty": "",
"lines_part_type": "",
"lines_price": "",
"lines_qty": "",
"nojobnotes": "",
"notes": ""
},

View File

@@ -181,7 +181,6 @@ export const uploadToCloudinary = async (
};
export function DetermineFileType(filetype) {
console.log("Checking Type", filetype, filetype.startsWith("video"));
if (!filetype) return "auto";
else if (filetype.startsWith("image")) return "image";
else if (filetype.startsWith("video")) return "video";

View File

@@ -7338,6 +7338,14 @@
"resolved" "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz"
"version" "17.0.1"
"react-mixin@^3.0.5":
"integrity" "sha512-z9fZ0aCRDjlgxLdMeWkJ9TwhmVLhQ09r8RFpin/cEPA2T6jsb7YHNWcIe0Oii+hhJNyMymdy91CSya5mRkuCkg=="
"resolved" "https://registry.npmjs.org/react-mixin/-/react-mixin-3.1.1.tgz"
"version" "3.1.1"
dependencies:
"object-assign" "^4.0.1"
"smart-mixin" "^2.0.0"
"react-native-easy-grid@^0.2.2":
"integrity" "sha512-MlYrNIldnEMKn6TVatQN1P64GoVlwGIuz+8ncdfJ0Wq/xtzUkQwlil8Uksyp7MhKfENE09MQnGNcba6Mx3oSAA=="
"resolved" "https://registry.npmjs.org/react-native-easy-grid/-/react-native-easy-grid-0.2.2.tgz"
@@ -7355,17 +7363,14 @@
"invariant" "^2.2.4"
"prop-types" "^15.7.2"
"react-native-image-pan-zoom@^2.1.12":
"integrity" "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q=="
"resolved" "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz"
"version" "2.1.12"
"react-native-image-zoom-viewer@^3.0.1":
"integrity" "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA=="
"resolved" "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz"
"version" "3.0.1"
"react-native-image-gallery@^2.1.5":
"integrity" "sha512-xC7nuPu4GUH0da6byofQ10LjtqlKj+VaLc0NHBJmeMHVvdvmRvFEO6UOq0Q0m/ePx3OQiPTNwGbf5BSPJEKa0w=="
"resolved" "https://registry.npmjs.org/react-native-image-gallery/-/react-native-image-gallery-2.1.5.tgz"
"version" "2.1.5"
dependencies:
"react-native-image-pan-zoom" "^2.1.12"
"prop-types" "^15.6.0"
"react-mixin" "^3.0.5"
"react-timer-mixin" "^0.13.3"
"react-native-indicators@^0.17.0":
"integrity" "sha512-s23em477GHGxWeGczWrixScAZD6tQU4mx1fttlrwhEGKOxhBgp55Kh3RoD9Wj4yna4e5W35xQNoPqoJAT6QW5A=="
@@ -7499,7 +7504,7 @@
"resolved" "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz"
"version" "0.4.3"
"react-timer-mixin@^0.13.4":
"react-timer-mixin@^0.13.3", "react-timer-mixin@^0.13.4":
"integrity" "sha512-4+ow23tp/Tv7hBM5Az5/Be/eKKF7DIvJ09voz5LyHGQaqqz9WV8YMs31eFvcYQs7d451LSg7kDJV70XYN/Ug/Q=="
"resolved" "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.4.tgz"
"version" "0.13.4"
@@ -8069,6 +8074,11 @@
"resolved" "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz"
"version" "1.4.7"
"smart-mixin@^2.0.0":
"integrity" "sha1-o0oQVeMqdbMNK048oyPcmctT9Dc="
"resolved" "https://registry.npmjs.org/smart-mixin/-/smart-mixin-2.0.0.tgz"
"version" "2.0.0"
"snapdragon-node@^2.0.1":
"integrity" "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw=="
"resolved" "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz"