Merge remote-tracking branch 'origin/feature/IO-2236-timetickets' into rome/1.4.1
This commit is contained in:
24
components/Buttons/create-time-ticket-button.component.jsx
Normal file
24
components/Buttons/create-time-ticket-button.component.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import { Text } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
export function AddTimeTicketButton() {
|
||||
|
||||
const navigation = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Button
|
||||
mode="text"
|
||||
compact={true}
|
||||
onPress={() => {navigation.navigate("CreateTimeTicket");}}
|
||||
icon="plus"
|
||||
>
|
||||
<Text style={{fontSize: 12}}>{t("timeticketbrowser.actions.ticket")}</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
export default connect(null, null)(AddTimeTicketButton);
|
||||
26
components/Buttons/employee-sign-out-button.component.jsx
Normal file
26
components/Buttons/employee-sign-out-button.component.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { Text } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { employeeSignOut } from "../../redux/employee/employee.actions";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
signOut: () => dispatch(employeeSignOut()),
|
||||
});
|
||||
|
||||
export function SignOutButton({ signOut,style }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Button
|
||||
mode="text"
|
||||
compact={true}
|
||||
onPress={() => signOut()}
|
||||
icon="logout"
|
||||
style={style}
|
||||
>
|
||||
<Text style={{fontSize: 12}}>{t("general.actions.logout")}</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(SignOutButton);
|
||||
220
components/Modals/JobSearchAndSelectModal.jsx
Normal file
220
components/Modals/JobSearchAndSelectModal.jsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, RefreshControl, Text, View } from "react-native";
|
||||
import { Button, List, Modal, Portal, Searchbar } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import _ from "lodash";
|
||||
import { useCallback,useRef, useEffect } from "react";
|
||||
|
||||
const useIsMounted = () => {
|
||||
const isMounted = useRef(false);
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isMounted.current;
|
||||
};
|
||||
|
||||
export function JobSearchAndSelectModal(props) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const jobSrchNotExported =
|
||||
props?.notExported !== undefined ? props.notExported : true;
|
||||
const jobSrchNotInvoiced =
|
||||
props?.notInvoiced !== undefined ? props.notInvoiced : false;
|
||||
const jobSrchConvertedOnly =
|
||||
props?.convertedOnly !== undefined ? props.convertedOnly : false;
|
||||
|
||||
const jobSrchCurrentValue =
|
||||
props?.currentValue !== undefined
|
||||
? props.currentValue
|
||||
: { id:"temp", ro_number:t("selectjobid.labels.noselection") };
|
||||
const jobSrchOnSetCurrentValue =
|
||||
props?.onSetCurrentValue !== undefined
|
||||
? props.onSetCurrentValue
|
||||
: (e) => {
|
||||
console.info("onSetCurrentValue was called", e);
|
||||
};
|
||||
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [searchText, setSearchText] = React.useState("");
|
||||
|
||||
const showModal = () => setVisible(true);
|
||||
const hideModal = () => setVisible(false);
|
||||
|
||||
const [searchQuery, { loading, error, data, refetch }] = useLazyQuery(
|
||||
SEARCH_JOBS_FOR_AUTOCOMPLETE,{fetchPolicy: "cache-and-network",}
|
||||
);
|
||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||
const onRefresh = async () => {
|
||||
refetch({
|
||||
notInvoiced: jobSrchNotInvoiced,
|
||||
notExported: jobSrchNotExported,
|
||||
isConverted: jobSrchConvertedOnly,
|
||||
search: searchText,
|
||||
});
|
||||
};
|
||||
const search = (v) => {if (v && v !== "") searchQuery(v);};
|
||||
const searchDebouncer = useCallback(_.debounce(search, 1000),[]);
|
||||
const onChangeSearch = (query) => {
|
||||
setSearchText(query);
|
||||
searchDebouncer({
|
||||
variables: {
|
||||
search: query,
|
||||
...(jobSrchConvertedOnly || jobSrchNotExported
|
||||
? {
|
||||
...(jobSrchConvertedOnly ? { isConverted: true } : {}),
|
||||
...(jobSrchNotExported ? { notExported: true } : {}),
|
||||
...(jobSrchNotInvoiced ? { notInvoiced: true } : {}),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
const inputSearch = useRef(null);
|
||||
const isMounted = useIsMounted();
|
||||
const setFocus = useCallback(() => {
|
||||
if (inputSearch.current) {
|
||||
inputSearch.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (visible) {
|
||||
setFocus();
|
||||
}
|
||||
}, 300);
|
||||
}, [visible, isMounted]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onDismiss={hideModal}
|
||||
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="ios-arrow-back" size={32} color="dodgerblue" />
|
||||
</Button>
|
||||
<Searchbar
|
||||
style={{ flex: 1 }}
|
||||
onChangeText={onChangeSearch}
|
||||
value={searchText}
|
||||
ref={inputSearch}
|
||||
placeholder={t("selectjobid.labels.searchplaceholder")}
|
||||
/>
|
||||
</View>
|
||||
<FlatList
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
data={data?.search_jobs}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={(object) => (
|
||||
<List.Item
|
||||
onPress={() => {
|
||||
jobSrchOnSetCurrentValue(object.item);
|
||||
hideModal();
|
||||
setSearchText("");
|
||||
}}
|
||||
left={() => {
|
||||
if (object.item.id !== jobSrchCurrentValue?.id) return null;
|
||||
return (
|
||||
<Ionicons
|
||||
name="ios-checkmark-circle"
|
||||
size={24}
|
||||
color="dodgerblue"
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
titleStyle={{
|
||||
...(object.item.id === jobSrchCurrentValue?.id
|
||||
? { color: "dodgerblue" }
|
||||
: {}),
|
||||
}}
|
||||
title={`${
|
||||
object.item.ro_number ? `${object.item.ro_number} ` : ``
|
||||
}${object.item.ownr_fn || ""} ${object.item.ownr_ln || ""}${
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text>{t("selectjobid.labels.nodata")}</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<Button
|
||||
mode="outlined"
|
||||
style={{
|
||||
margin: 8,
|
||||
height: 50,
|
||||
borderColor: "gray",
|
||||
borderWidth: 0.5,
|
||||
borderRadius: 4,
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onPress={showModal}
|
||||
>
|
||||
{jobSrchCurrentValue?.id
|
||||
? jobSrchCurrentValue?.id === "temp"
|
||||
? t("selectjobid.labels.noselection")
|
||||
: `${
|
||||
jobSrchCurrentValue.ro_number
|
||||
? `${jobSrchCurrentValue.ro_number} - `
|
||||
: ``
|
||||
}${jobSrchCurrentValue.ownr_fn || ""} ${
|
||||
jobSrchCurrentValue.ownr_ln || ""
|
||||
} - ${jobSrchCurrentValue.v_model_yr || ""} ${
|
||||
jobSrchCurrentValue.v_make_desc || ""
|
||||
} ${jobSrchCurrentValue.v_model_desc || ""}`
|
||||
: t("selectjobid.labels.placeholder")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, null)(JobSearchAndSelectModal);
|
||||
108
components/Selects/select-cost-center.jsx
Normal file
108
components/Selects/select-cost-center.jsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import { Dropdown } from "react-native-element-dropdown";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useEffect } from "react";
|
||||
import { t } from "i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
export function CostCenterSelect(props) {
|
||||
const currentRatesNCostCenters = props.currentRatesNCostCenters;
|
||||
const bodyshop = props.bodyshop;
|
||||
|
||||
// const [value, setValue] = useState(null);
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
const [costCenters, setCostCenters] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof currentRatesNCostCenters !== "undefined") {
|
||||
var count = Object.keys(currentRatesNCostCenters).length;
|
||||
let selectDataArray = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
selectDataArray.push({
|
||||
value: currentRatesNCostCenters[i].cost_center,
|
||||
label: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? t(
|
||||
`joblines.fields.lbr_types.${currentRatesNCostCenters[i].cost_center.toUpperCase()}`
|
||||
)
|
||||
: currentRatesNCostCenters[i].cost_center,
|
||||
rate: currentRatesNCostCenters[i].rate,
|
||||
});
|
||||
}
|
||||
setCostCenters(selectDataArray);
|
||||
}
|
||||
}, [currentRatesNCostCenters]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Dropdown
|
||||
style={[styles.dropdown, isFocus && { borderColor: "blue" }]}
|
||||
placeholderStyle={styles.placeholderStyle}
|
||||
selectedTextStyle={styles.selectedTextStyle}
|
||||
inputSearchStyle={styles.inputSearchStyle}
|
||||
iconStyle={styles.iconStyle}
|
||||
maxHeight={200}
|
||||
labelField="label"
|
||||
valueField="value"
|
||||
placeholder={!isFocus ? t("selectcostcenter.labels.placeholder") : t("selectcostcenter.labels.selectedplaceholder")}
|
||||
searchPlaceholder={t("selectcostcenter.labels.searchplaceholder")}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
data={costCenters}
|
||||
value={props.currentValue?.value}
|
||||
onChange={(item) => {
|
||||
props.onValueSelected(item);
|
||||
setIsFocus(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(CostCenterSelect);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
},
|
||||
dropdown: {
|
||||
height: 50,
|
||||
borderColor: "gray",
|
||||
borderWidth: 0.5,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
icon: {
|
||||
marginRight: 5,
|
||||
},
|
||||
label: {
|
||||
position: "absolute",
|
||||
backgroundColor: "white",
|
||||
left: 22,
|
||||
top: 8,
|
||||
zIndex: 999,
|
||||
paddingHorizontal: 8,
|
||||
fontSize: 14,
|
||||
},
|
||||
placeholderStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
selectedTextStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
iconStyle: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
inputSearchStyle: {
|
||||
height: 40,
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
227
components/Selects/select-job-id.jsx
Normal file
227
components/Selects/select-job-id.jsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import _ from "lodash";
|
||||
|
||||
import {
|
||||
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE,
|
||||
SEARCH_JOBS_FOR_AUTOCOMPLETE,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { Dropdown } from "react-native-element-dropdown";
|
||||
import { connect } from "react-redux";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
export function JobIdSearchSelect(
|
||||
props
|
||||
// //currentValue,
|
||||
// ...restProps
|
||||
) {
|
||||
// console.log("JobIdSearchSelectprops:", props);
|
||||
const notExported =
|
||||
props.notExported !== undefined ? props.notExported : true;
|
||||
const notInvoiced =
|
||||
props.notInvoiced !== undefined ? props.notInvoiced : false;
|
||||
const convertedOnly =
|
||||
props.convertedOnly !== undefined ? props.convertedOnly : false;
|
||||
const clm_no = props.clm_no !== undefined ? props.clm_no : false;
|
||||
|
||||
// console.log("notExported:", notExported);
|
||||
// console.log("notInvoiced:", notInvoiced);
|
||||
// console.log("convertedOnly:", convertedOnly);
|
||||
// console.log("clm_no:", clm_no);
|
||||
const { t } = useTranslation();
|
||||
const [theOptions, setTheOptions] = useState([]);
|
||||
|
||||
const [selectorData, setSelectorData] = useState([]);
|
||||
const [selectedvalue, setSelectedValue] = useState(null);
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
|
||||
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
|
||||
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
||||
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(
|
||||
SEARCH_JOBS_FOR_AUTOCOMPLETE,
|
||||
{}
|
||||
);
|
||||
const executeSearch = (v) => {
|
||||
// console.log("executeSearchWithV:", v);
|
||||
if (v && v !== "") callSearch(v);
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
// console.log("handleSearchWithValue:", value);
|
||||
debouncedExecuteSearch({
|
||||
variables: {
|
||||
search: value,
|
||||
...(convertedOnly || notExported
|
||||
? {
|
||||
...(convertedOnly ? { isConverted: true } : {}),
|
||||
...(notExported ? { notExported: true } : {}),
|
||||
...(notInvoiced ? { notInvoiced: true } : {}),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("useEfectDependentOn: restProps.value, callIdSearch", restProps);
|
||||
// if (restProps.value) {
|
||||
// console.log("restPropsValue:", restProps.value);
|
||||
// callIdSearch({ variables: { id: restProps.value } });
|
||||
// }
|
||||
// }, [restProps.value, callIdSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("useEfectDependentOn: [data, idData]");
|
||||
|
||||
// console.log( "idDataValue:", idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []);
|
||||
// console.log("dataValue:", data && data.search_jobs ? data.search_jobs : []);
|
||||
|
||||
if (data) {
|
||||
setTheOptions(
|
||||
_.uniqBy(
|
||||
[
|
||||
...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []),
|
||||
...(data && data.search_jobs ? data.search_jobs : []),
|
||||
],
|
||||
"id"
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [data, idData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof theOptions !== "undefined") {
|
||||
// console.log("useEfectDependentOn: [theOptions]");
|
||||
var count = Object.keys(theOptions).length;
|
||||
// console.log("useEfectDependentOn: [theOptions] count:", count);
|
||||
let selectDataArray = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
selectDataArray.push({
|
||||
value: theOptions[i].id,
|
||||
label: `${
|
||||
clm_no && theOptions[i].clm_no ? `${theOptions[i].clm_no} | ` : ""
|
||||
}${
|
||||
theOptions[i].ro_number || t("general.labels.na")
|
||||
} | ${OwnerNameDisplayFunction(theOptions[i])} | ${
|
||||
theOptions[i].v_model_yr || ""
|
||||
} ${theOptions[i].v_make_desc || ""} ${
|
||||
theOptions[i].v_model_desc || ""
|
||||
}`,
|
||||
});
|
||||
}
|
||||
setSelectorData(selectDataArray);
|
||||
}
|
||||
}, [theOptions]);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("useEffectonselectedvaluechange:", selectedvalue.value);
|
||||
// if (typeof onJobSelected !== "undefined") {
|
||||
// console.log("onJobSelected:", selectedvalue.value);
|
||||
// onJobSelected(selectedvalue.value);
|
||||
// }
|
||||
// }, [selectedvalue]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Dropdown
|
||||
style={[styles.dropdown, isFocus && { borderColor: "blue" }]}
|
||||
placeholderStyle={styles.placeholderStyle}
|
||||
selectedTextStyle={styles.selectedTextStyle}
|
||||
inputSearchStyle={styles.inputSearchStyle}
|
||||
iconStyle={styles.iconStyle}
|
||||
search
|
||||
maxHeight={200}
|
||||
labelField="label"
|
||||
valueField="value"
|
||||
placeholder={!isFocus ? t("selectjobid.labels.placeholder") : t("selectjobid.labels.selectedplaceholder")}
|
||||
searchPlaceholder={t("selectjobid.labels.searchplaceholder")}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
data={selectorData}
|
||||
value={props.currentValue?.value} //{selectedvalue}
|
||||
onChange={(item) => {
|
||||
// console.log("onChange Fired!!!!");
|
||||
props.onJobSelected(item);
|
||||
setIsFocus(false);
|
||||
}}
|
||||
|
||||
|
||||
//TODO: add setIsFocus(false); to this
|
||||
// {
|
||||
// console.log("onValueSelected!!!!");
|
||||
// // (item) => {onJobSelected(item.value)};
|
||||
// console.log("onChangefired!!!!");
|
||||
// console.log("itemSelected", item);
|
||||
// onJobSelected(item.value);
|
||||
// setSelectedValue(item.value);
|
||||
|
||||
// console.log("onValueSelected",onValueSelected);
|
||||
// if (onValueSelected){
|
||||
// console.log("onValueSelected!!!!");
|
||||
// }
|
||||
|
||||
//console.log(item);
|
||||
// setSelectedValue(item.value);
|
||||
// if (onValueSelected) onValueSelected(item.value);
|
||||
|
||||
// setIsFocus(false);
|
||||
// }}
|
||||
onChangeText={(search) => {
|
||||
if (search && search !== "") {
|
||||
// console.log("onChangeTextFired!!!!");
|
||||
handleSearch(search);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/* {theOptions ? console.log(theOptions): null} */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, null)(JobIdSearchSelect);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
},
|
||||
dropdown: {
|
||||
height: 50,
|
||||
borderColor: "gray",
|
||||
borderWidth: 0.5,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
icon: {
|
||||
marginRight: 5,
|
||||
},
|
||||
label: {
|
||||
position: "absolute",
|
||||
backgroundColor: "white",
|
||||
left: 22,
|
||||
top: 8,
|
||||
zIndex: 999,
|
||||
paddingHorizontal: 8,
|
||||
fontSize: 14,
|
||||
},
|
||||
placeholderStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
selectedTextStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
iconStyle: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
inputSearchStyle: {
|
||||
height: 40,
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
@@ -1,10 +1,20 @@
|
||||
import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { StyleSheet,View, Text } from "react-native";
|
||||
import { Title } from "react-native-paper";
|
||||
|
||||
export default function ErrorDisplay({ errorMessage }) {
|
||||
return (
|
||||
<View style={{ backgroundColor: "red" }}>
|
||||
<Text>{errorMessage}</Text>
|
||||
<View >
|
||||
<Title style={localStyles.alert}>{errorMessage}</Title>
|
||||
{/* <Text>{errorMessage}</Text> */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
const localStyles = StyleSheet.create({
|
||||
alert: {
|
||||
color: "red",
|
||||
textAlign: "center",
|
||||
margin: 8,
|
||||
padding: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ const mapStateToProps = createStructuredSelector({});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||
// setTmTicketJobId:(id) => dispatch(setTmTicketJobId(id)),
|
||||
});
|
||||
|
||||
export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
||||
@@ -71,6 +72,7 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
||||
onPress={() => {
|
||||
logImEXEvent("imexmobile_setcamerajobid_row");
|
||||
setCameraJobId(item.id);
|
||||
// setTmTicketJobId(item.id);
|
||||
setCameraJob(item);
|
||||
navigation.navigate("MediaBrowserTab");
|
||||
}}
|
||||
|
||||
27
components/keyboards/KeyboardAvoidingComponent.js
Normal file
27
components/keyboards/KeyboardAvoidingComponent.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { View, Text, Platform } from "react-native";
|
||||
import React from "react";
|
||||
import { KeyboardAvoidingView } from "react-native";
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
const KeyboardAvoidingComponent = ({ children }) => {
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
enabled
|
||||
behavior={Platform.OS === "ios" ? "padding":"height" }
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.inner}>{children}</View>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
inner: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default KeyboardAvoidingComponent;
|
||||
@@ -0,0 +1,190 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, StyleSheet, Text, View } from "react-native";
|
||||
import _ from "lodash";
|
||||
import { ActivityIndicator, Card, DataTable, Divider } from "react-native-paper";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs.queries";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { selectCurrentEmployee } from "../../redux/employee/employee.selectors";
|
||||
import { CalculateAllocationsTotals } from "../../util/labor-allocations-table.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectCurrentEmployee,
|
||||
});
|
||||
|
||||
export function LaborAllocationsTable({ jobId, bodyshop, technician,costCenterDiff,selectedCostCenter, style, shouldRefresh}) {
|
||||
const { t } = useTranslation();
|
||||
const onRefresh = async () => { return refetch(); };
|
||||
const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, {
|
||||
variables: { id: jobId },
|
||||
skip: !!!jobId,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||
|
||||
const [totals, setTotals] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!data?.joblines && !!data?.timetickets && !!bodyshop){
|
||||
let temptotals = CalculateAllocationsTotals(
|
||||
bodyshop,
|
||||
data?.joblines,
|
||||
data?.timetickets,
|
||||
data?.adjustments
|
||||
);
|
||||
if(!!selectedCostCenter){
|
||||
let tempCostCenterDiff = Math.round(
|
||||
temptotals.find(
|
||||
(total) =>
|
||||
total["cost_center"] === selectedCostCenter.label
|
||||
)?.difference * 10
|
||||
) / 10;
|
||||
costCenterDiff.current= tempCostCenterDiff;
|
||||
}
|
||||
setTotals(
|
||||
temptotals
|
||||
);
|
||||
}
|
||||
if (!jobId) setTotals([]);
|
||||
}, [data?.joblines, data?.timetickets, bodyshop, data?.adjustments, jobId, selectedCostCenter]);
|
||||
|
||||
useEffect(() => {
|
||||
!!shouldRefresh && shouldRefresh ? onRefresh() : null
|
||||
},[shouldRefresh]);
|
||||
|
||||
const summary =
|
||||
totals &&
|
||||
totals.reduce(
|
||||
(acc, val) => {
|
||||
acc.hrs_total += val.total;
|
||||
acc.hrs_claimed += val.claimed;
|
||||
acc.adjustments += val.adjustments;
|
||||
acc.difference += val.difference;
|
||||
return acc;
|
||||
},
|
||||
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
|
||||
);
|
||||
|
||||
if (loading) return <ActivityIndicator color="dodgerblue" size="large" />;
|
||||
return (
|
||||
<View style={style}>
|
||||
{typeof data !== "undefined" ? (
|
||||
<Card style={{ flex: 1 }}>
|
||||
<Card.Title title={t("laborallocations.labels.laborallocations")} />
|
||||
<Card.Content>
|
||||
<View style={localStyles.headerArea}>
|
||||
<Text style={localStyles.headertext}>{t("laborallocations.labels.costcenter")}</Text>
|
||||
<Text style={localStyles.headertext}>{t("laborallocations.labels.hourstotal")}</Text>
|
||||
<Text style={localStyles.headertext}>{t("laborallocations.labels.hoursclaimed")}</Text>
|
||||
{/* <Text numberOfLines={2} style={{ flex: 1, flexWrap:'wrap' }}>Hours Claimed</Text> */}
|
||||
<Text style={localStyles.headertextAdjusts}>{t("laborallocations.labels.adjustments")}</Text>
|
||||
<Text style={localStyles.headertext}>{t("laborallocations.labels.difference")}</Text>
|
||||
</View>
|
||||
<Divider orientation="vertical" />
|
||||
<DataTable>
|
||||
<FlatList
|
||||
data={totals}
|
||||
keyExtractor={(item) => item.cost_center}
|
||||
ItemSeparatorComponent={<Divider orientation="vertical" />}
|
||||
renderItem={(object) => (
|
||||
<DataTable.Row style={{ flex: 1, justifyContent:"space-evenly" }}>
|
||||
<View style={{ flex: 1, alignItems: "flex-start" }}>
|
||||
<Text>
|
||||
{object.item.cost_center} {" ("}
|
||||
{object.item.mod_lbr_ty}
|
||||
{")"}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, alignItems: "center" }}>
|
||||
<Text>
|
||||
{object.item.total && object.item.total.toFixed(1)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, alignItems: "center" }}>
|
||||
<Text>
|
||||
{object.item.claimed && object.item.claimed.toFixed(1)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, alignItems: "center" }}>
|
||||
<Text>
|
||||
{object.item.adjustments &&
|
||||
object.item.adjustments.toFixed(1)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, alignItems: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
color: object.item.difference >= 0 ? "green" : "red",
|
||||
}}
|
||||
>
|
||||
{_.round(object.item.difference, 1)}
|
||||
</Text>
|
||||
</View>
|
||||
</DataTable.Row>
|
||||
)}
|
||||
/>
|
||||
{summary && (
|
||||
<View style={localStyles.headerArea}>
|
||||
<Text style={localStyles.footertext}>{t("laborallocations.labels.totals")}</Text>
|
||||
<Text style={localStyles.footertext}>
|
||||
{summary.hrs_total.toFixed(1)}
|
||||
</Text>
|
||||
<Text style={localStyles.footertext}>
|
||||
{summary.hrs_claimed.toFixed(1)}
|
||||
</Text>
|
||||
<Text style={localStyles.footertext}>
|
||||
{summary.adjustments.toFixed(1)}
|
||||
</Text>
|
||||
<Text style={localStyles.footertext}>
|
||||
{summary.difference.toFixed(1)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</DataTable>
|
||||
</Card.Content>
|
||||
|
||||
</Card>
|
||||
) : null}
|
||||
{/* use "totals" for the rows in the table */}
|
||||
{/* use "summary" for the totals at the bottom */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
headerArea: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
margin: 1,
|
||||
},
|
||||
footertext: {
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
textAlignVertical: "center",
|
||||
margin: 1,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
headertext: {
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
textAlignVertical: "center",
|
||||
margin: 1,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
headertextAdjusts: {
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
textAlignVertical: "center",
|
||||
margin: 1,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(LaborAllocationsTable);
|
||||
@@ -0,0 +1,47 @@
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { store } from "../../redux/store";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
|
||||
|
||||
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
||||
const emptyTest =
|
||||
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
|
||||
return "N/A";
|
||||
|
||||
if (bodyshop.last_name_first)
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
}
|
||||
|
||||
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||
const emptyTest =
|
||||
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
|
||||
return "N/A";
|
||||
|
||||
const rdxStore = store.getState();
|
||||
|
||||
if (rdxStore.user.bodyshop.last_name_first && !forceFirstLast)
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Formik } from "formik";
|
||||
import React from "react";
|
||||
import { View, StyleSheet, Text } from "react-native";
|
||||
import { Button, TextInput } from "react-native-paper";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { employeeSignInStart } from "../../redux/employee/employee.actions";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectCurrentEmployee,
|
||||
selectSigningIn,
|
||||
selectSignInError,
|
||||
} from "../../redux/employee/employee.selectors";
|
||||
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEmployee: selectCurrentEmployee,
|
||||
signingIn: selectSigningIn,
|
||||
signingError: selectSignInError,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
employeeSignInStart: (employeeId, pin) =>
|
||||
dispatch(employeeSignInStart({ employeeId, pin })),
|
||||
});
|
||||
|
||||
export function EmployeeSignIn({
|
||||
signingError,
|
||||
signingIn,
|
||||
employeeSignInStart,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSubmit = (values) => {
|
||||
const { employeeId, pin } = values;
|
||||
employeeSignInStart(employeeId, pin);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={localStyles.content}>
|
||||
<View style={localStyles.signInContainer}>
|
||||
<Formik
|
||||
initialValues={{ employeeId: "", pin: "" }}
|
||||
onSubmit={formSubmit}
|
||||
>
|
||||
{({ handleChange, handleBlur, handleSubmit, values }) => {
|
||||
const signingErrorMsg = signingError ? (<ErrorDisplay errorMessage={signingError} />) : null;
|
||||
return(
|
||||
<View>
|
||||
<TextInput
|
||||
label={t("employeesignin.fields.employeeid")}
|
||||
mode="outlined"
|
||||
autoCapitalize="none"
|
||||
keyboardType="default"
|
||||
onChangeText={handleChange("employeeId")}
|
||||
onBlur={handleBlur("employeeId")}
|
||||
value={values.employeeId}
|
||||
style={[localStyles.input]}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("employeesignin.fields.pin")}
|
||||
mode="outlined"
|
||||
secureTextEntry={true}
|
||||
onChangeText={handleChange("pin")}
|
||||
onBlur={handleBlur("pin")}
|
||||
value={values.pin}
|
||||
style={[localStyles.input]}
|
||||
textContentType={"none"}
|
||||
/>
|
||||
{signingErrorMsg}
|
||||
<Button
|
||||
mode="outlined"
|
||||
loading={signingIn}
|
||||
onPress={handleSubmit}
|
||||
>
|
||||
<Text>{t("employeesignin.actions.employeesignin")}</Text>
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
</Formik>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
content: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
},
|
||||
signInContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
input: {
|
||||
margin: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EmployeeSignIn);
|
||||
@@ -1,14 +1,17 @@
|
||||
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 React, { useEffect, useRef } from "react";
|
||||
import i18n from "i18next";
|
||||
import moment from "moment";
|
||||
import React, { useEffect } from "react";
|
||||
import { AppState } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
|
||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
||||
|
||||
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,
|
||||
@@ -19,6 +22,9 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { employeeSignOut } from "../../redux/employee/employee.actions";
|
||||
import { selectCurrentEmployee } from "../../redux/employee/employee.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";
|
||||
@@ -26,16 +32,23 @@ import ScreenSettingsComponent from "../screen-settings/screen-settings.componen
|
||||
import ScreenSignIn from "../screen-sign-in/screen-sign-in.component";
|
||||
import ScreenSplash from "../screen-splash/screen-splash.component";
|
||||
|
||||
import EmployeeSignIn from "../screen-employee-sign-in/screen-employee-sign-in.component";
|
||||
import ScreenTimeTicketBrowser from "../screen-time-ticket-browser/screen-time-ticket-browser.component";
|
||||
import SignOutButton from "../Buttons/employee-sign-out-button.component";
|
||||
import AddTimeTicketButton from "../Buttons/create-time-ticket-button.component";
|
||||
import ScreenTimeTicketCreate from "../time-ticket/screen-time-ticket-create.component";
|
||||
import ScreenTimeTicketClockoffComponent from "../time-ticket/screen-time-ticket-clockoff.component";
|
||||
|
||||
const ActiveJobStack = createNativeStackNavigator();
|
||||
const MoreStack = createNativeStackNavigator();
|
||||
const BottomTabs = createBottomTabNavigator();
|
||||
const MediaBrowserStack = createNativeStackNavigator();
|
||||
const TimeTicketBrowserStack = createNativeStackNavigator();
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
emailSignInStart: (email, password) =>
|
||||
@@ -44,6 +57,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||
});
|
||||
const mapStateToProps2 = createStructuredSelector({
|
||||
currentEmployee: selectCurrentEmployee,
|
||||
});
|
||||
const mapDispatchToProps2 = (dispatch) => ({
|
||||
signOut: () => dispatch(employeeSignOut()),
|
||||
});
|
||||
|
||||
const JobsTabNavigator = connect(
|
||||
mapStateToProps,
|
||||
@@ -104,6 +123,74 @@ const MoreStackNavigator = () => (
|
||||
</MoreStack.Navigator>
|
||||
);
|
||||
|
||||
const TimeTicketBrowserStackNavigator = connect(
|
||||
mapStateToProps2,
|
||||
mapDispatchToProps2
|
||||
)(({ currentEmployee, signOut }) => {
|
||||
const appState = useRef(AppState.currentState);
|
||||
useEffect(() => {
|
||||
const subscription = AppState.addEventListener("change", (nextAppState) => {
|
||||
if (
|
||||
appState.current.match(/active|inactive/) &&
|
||||
nextAppState === "background"
|
||||
) {
|
||||
//App is about to be background
|
||||
signOut();
|
||||
}
|
||||
// if ( appState.current.match(/inactive/)) { console.log("App has come to the inactive"); }
|
||||
// if (appState.current.match(/background/)) { console.log("App has come to the background");}
|
||||
appState.current = nextAppState;
|
||||
});
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TimeTicketBrowserStack.Navigator>
|
||||
{currentEmployee === null ? (
|
||||
<TimeTicketBrowserStack.Screen
|
||||
name="EmployeeSignIn"
|
||||
options={() => ({ title: i18n.t("employeesignin.titles.signin") })}
|
||||
component={EmployeeSignIn}
|
||||
/>
|
||||
) : currentEmployee.authorized ? (
|
||||
<>
|
||||
<TimeTicketBrowserStack.Screen
|
||||
name="TimeTicketBrowser"
|
||||
options={{
|
||||
title: i18n.t("timeticketbrowser.titles.timeticketbrowsertab"),
|
||||
}}
|
||||
component={ScreenTimeTicketBrowser}
|
||||
/>
|
||||
<TimeTicketBrowserStack.Screen
|
||||
name="CreateTimeTicket"
|
||||
options={() => ({
|
||||
title: i18n.t("createtimeticket.titles.createtimeticket"),
|
||||
})}
|
||||
component={ScreenTimeTicketCreate}
|
||||
/>
|
||||
<TimeTicketBrowserStack.Screen
|
||||
name="TimeTicketClockOff"
|
||||
options={() => ({
|
||||
title: i18n.t("timeticketclockoff.titles.clockoff"),
|
||||
})}
|
||||
component={ScreenTimeTicketClockoffComponent}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<TimeTicketBrowserStack.Screen
|
||||
name="EmployeeSignIn"
|
||||
options={() => ({
|
||||
title: i18n.t("employeesignin.titles.signin"),
|
||||
})}
|
||||
component={EmployeeSignIn}
|
||||
/>
|
||||
)}
|
||||
</TimeTicketBrowserStack.Navigator>
|
||||
);
|
||||
});
|
||||
|
||||
const BottomTabsNavigator = () => (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={({ route }) => ({
|
||||
@@ -116,6 +203,8 @@ const BottomTabsNavigator = () => (
|
||||
iconName = "ios-settings";
|
||||
} else if (route.name === "MediaBrowserTab") {
|
||||
iconName = "ios-camera";
|
||||
} else if (route.name === "TimeTicketBrowserTab") {
|
||||
iconName = "ios-stopwatch-outline";
|
||||
} else {
|
||||
//iconName = "customerservice";
|
||||
}
|
||||
@@ -140,6 +229,14 @@ const BottomTabsNavigator = () => (
|
||||
}}
|
||||
component={MediaBrowserStackNavigator}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="TimeTicketBrowserTab"
|
||||
options={{
|
||||
title: i18n.t("timeticketbrowser.titles.timeticketbrowsertab"),
|
||||
headerShown: false,
|
||||
}}
|
||||
component={TimeTicketBrowserStackNavigator}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="MoreTab"
|
||||
options={{ title: i18n.t("more.titles.moretab"), headerShown: false }}
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
import React, { useCallback, useState, useRef } from "react";
|
||||
import moment from "moment";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
RefreshControl,
|
||||
FlatList,
|
||||
} from "react-native";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Card,
|
||||
Headline,
|
||||
Subheading,
|
||||
} from "react-native-paper";
|
||||
import styles from "../styles";
|
||||
|
||||
import axios from "axios";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
import { employeeGetRatesStart } from "../../redux/employee/employee.actions";
|
||||
import {
|
||||
selectCurrentEmployee,
|
||||
selectRates,
|
||||
selectGettingRates,
|
||||
selectSignInError,
|
||||
selectEmployeeFullName,
|
||||
} from "../../redux/employee/employee.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
import CostCenterSelect from "../Selects/select-cost-center";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import {
|
||||
selectCurrentTimeTicketJob,
|
||||
selectCurrentTimeTicketJobId,
|
||||
} from "../../redux/timetickets/timetickets.selectors";
|
||||
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
|
||||
|
||||
// import EmployeeClockedInList from "../time-ticket-lists/employee-clockedin-list.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ClockedinListItem from "../time-ticket-items/clockedin-list-item.component";
|
||||
import SignOutButton from "../Buttons/employee-sign-out-button.component";
|
||||
import AddTimeTicketButton from "../Buttons/create-time-ticket-button.component";
|
||||
|
||||
import KeyboardAvoidingComponent from "../keyboards/KeyboardAvoidingComponent";
|
||||
import JobSearchAndSelectModal from "../Modals/JobSearchAndSelectModal";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEmployee: selectCurrentEmployee,
|
||||
loaderGettingRates: selectGettingRates,
|
||||
signingError: selectSignInError,
|
||||
currentBodyshop: selectBodyshop,
|
||||
currentRatesNCostCenters: selectRates,
|
||||
currentSelectedTimeTicketJobId: selectCurrentTimeTicketJobId,
|
||||
currentSelectedTimeTicketJob: selectCurrentTimeTicketJob,
|
||||
currentEmployeeFullName: selectEmployeeFullName,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
employeeGetRatesStart: (employeeId) =>
|
||||
dispatch(employeeGetRatesStart(employeeId)),
|
||||
});
|
||||
|
||||
export function ScreenTimeTicketBrowser({
|
||||
loaderGettingRates,
|
||||
currentEmployee,
|
||||
employeeGetRatesStart,
|
||||
signingError,
|
||||
currentBodyshop,
|
||||
currentRatesNCostCenters,
|
||||
currentSelectedTimeTicketJob,
|
||||
currentSelectedTimeTicketJobId,
|
||||
currentEmployeeFullName,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [currentSCC, setCurrentSCC] = useState(null);
|
||||
const [loadingClockIn, setLoadingClockIn] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
|
||||
refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"],
|
||||
});
|
||||
|
||||
const navigation = useNavigation();
|
||||
const [curSelClockIntoJob, setCurSelClockIntoJob] = useState(null);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoadingClockIn(true);
|
||||
setError(null);
|
||||
|
||||
const theTime = (await axios.post("/utils/time")).data;
|
||||
|
||||
if (!!currentSCC?.value && !!curSelClockIntoJob?.id) {
|
||||
setError(null);
|
||||
// console.log("have all values");
|
||||
} else {
|
||||
// console.log("missing values!");
|
||||
setLoadingClockIn(false);
|
||||
setError({ message: t("timeticketbrowser.errors.missingvalues") });
|
||||
return;
|
||||
}
|
||||
|
||||
const tempVariablesObj = {
|
||||
variables: {
|
||||
timeTicketInput: [
|
||||
{
|
||||
bodyshopid: currentBodyshop.id,
|
||||
employeeid: currentEmployee?.technician?.id,
|
||||
date: moment(theTime).format("YYYY-MM-DD"),
|
||||
clockon: moment(theTime),
|
||||
jobid: curSelClockIntoJob?.id,
|
||||
cost_center: currentSCC?.value,
|
||||
ciecacode:
|
||||
currentBodyshop?.cdk_dealerid || currentBodyshop?.pbs_serialnumber
|
||||
? currentSCC?.value
|
||||
: Object.keys(
|
||||
currentBodyshop.md_responsibility_centers.defaults.costs
|
||||
).find((key) => {
|
||||
return (
|
||||
currentBodyshop.md_responsibility_centers.defaults.costs[
|
||||
key
|
||||
] === currentSCC?.value
|
||||
);
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const result = await insertTimeTicket(tempVariablesObj);
|
||||
setLoadingClockIn(false);
|
||||
if (!!result.errors) {
|
||||
setError(JSON.stringify(result.errors));
|
||||
} else {
|
||||
setCurSelClockIntoJob(null);
|
||||
setCurrentSCC(null);
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
refetch();
|
||||
setTimeout(() => {
|
||||
setRefreshing(false);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
const [itemState, setItemState] = useState({
|
||||
title: t("employeeclockedinlist.labels.alreadyclockedon"),
|
||||
data: null,
|
||||
content: null,
|
||||
});
|
||||
|
||||
const { loadingATT, errorATT, dataATT, refetch } = useQuery(
|
||||
QUERY_ACTIVE_TIME_TICKETS,
|
||||
{
|
||||
variables: {
|
||||
employeeId: currentEmployee?.technician?.id,
|
||||
},
|
||||
skip: !currentEmployee?.technician,
|
||||
onCompleted: (dataATT) => {
|
||||
if (!!dataATT && dataATT?.timetickets) {
|
||||
setItemState((itemState) => ({
|
||||
...itemState,
|
||||
data: dataATT?.timetickets,
|
||||
}));
|
||||
}
|
||||
},
|
||||
onRefresh: { onRefresh },
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingATT) {
|
||||
setItemState((itemState) => ({
|
||||
...itemState,
|
||||
content: <ActivityIndicator color="dodgerblue" size="large" />,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (errorATT) {
|
||||
setItemState((itemState) => ({
|
||||
...itemState,
|
||||
content: <ErrorDisplay errorMessage={errorATT.message} />,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.cardBackground}>
|
||||
<KeyboardAvoidingComponent>
|
||||
<FlatList
|
||||
ListHeaderComponent={
|
||||
<Card style={localStyles.localCardStyle}>
|
||||
<Card.Title
|
||||
title={t("timeticketbrowser.labels.loggedinemployee")}
|
||||
right={(props) => <SignOutButton style={{margin:8}}/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
{currentEmployeeFullName && (
|
||||
<Subheading>{currentEmployeeFullName}</Subheading>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
}
|
||||
data={!!itemState ? [itemState] : null}
|
||||
renderItem={({ item }) => (
|
||||
<MyItem style={localStyles.localCardStyle} itemState={item} />
|
||||
)}
|
||||
ListFooterComponent={
|
||||
<Card style={localStyles.localCardStyle}>
|
||||
<Card.Title
|
||||
title={t("timeticketbrowser.labels.clockintojob")}
|
||||
right={(props) => (
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Button
|
||||
mode="text"
|
||||
compact={true}
|
||||
onPress={() => {
|
||||
navigation.navigate("CreateTimeTicket");
|
||||
}}
|
||||
icon="plus"
|
||||
style={{ margin: 8 }}
|
||||
>
|
||||
<Text >
|
||||
{t("timeticketbrowser.actions.ticket")}
|
||||
</Text>
|
||||
</Button>
|
||||
{/* <Button
|
||||
mode="outlined"
|
||||
compact={true}
|
||||
loading={loadingClockIn}
|
||||
onPress={handleFinish}
|
||||
style={{ margin: 4 }}
|
||||
>
|
||||
<Text>Clock In</Text>
|
||||
</Button> */}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
<JobSearchAndSelectModal
|
||||
currentValue={curSelClockIntoJob}
|
||||
onSetCurrentValue={setCurSelClockIntoJob}
|
||||
notExported={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||
notInvoiced={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||
convertedOnly={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
<CostCenterSelect
|
||||
currentValue={currentSCC}
|
||||
currentRatesNCostCenters={currentRatesNCostCenters}
|
||||
onValueSelected={setCurrentSCC}
|
||||
/>
|
||||
{error && error?.message ? (
|
||||
<ErrorDisplay errorMessage={error.message} />
|
||||
) : null}
|
||||
</Card.Content>
|
||||
<Card.Actions
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
mode="outlined"
|
||||
style={styles.buttonBasicOutlined}
|
||||
loading={loadingClockIn}
|
||||
onPress={handleFinish}
|
||||
>
|
||||
<Text>{t("timeticketbrowser.actions.clockin")}</Text>
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.containerNoData}>
|
||||
<Text>{t("timeticketbrowser.labels.nodata")}</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</KeyboardAvoidingComponent>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const MyItem = ({ itemState, style }) => {
|
||||
const { t } = useTranslation();
|
||||
const items = itemState?.data;
|
||||
return (
|
||||
<Card key={itemState.title} style={style}>
|
||||
<Card.Title title={itemState.title} />
|
||||
<Card.Content>
|
||||
{!!items ? (
|
||||
items.map((item) => <ClockedinListItem key={item.id} ticket={item} />)
|
||||
) : !!itemState?.content ? (
|
||||
itemState.content
|
||||
) : (
|
||||
<Text>{t("timeticketbrowser.labels.nodata")}</Text>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
content: {
|
||||
display: "flex",
|
||||
flex: 2,
|
||||
},
|
||||
localCardStyle: {
|
||||
margin: 4,
|
||||
},
|
||||
containerNoData: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScreenTimeTicketBrowser);
|
||||
@@ -4,20 +4,20 @@ import { Button } from "react-native";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { employeeSignOut } from "../../redux/employee/employee.actions";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
signOut: () => dispatch(employeeSignOut()),
|
||||
});
|
||||
|
||||
export function SignOutButton({ signOutStart }) {
|
||||
export function SignOutButton({ signOutStart, signOut }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Button
|
||||
style={{ margin: 8 }}
|
||||
onPress={() => signOutStart()}
|
||||
onPress={() => (signOut(),signOutStart())}
|
||||
title={t("general.actions.signout")}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -9,4 +9,23 @@ export default StyleSheet.create({
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
},
|
||||
buttonBasicOutlined: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
borderColor: "gray",
|
||||
borderWidth: 0.8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
buttonFatOutlined: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
height: 48,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
borderColor: "gray",
|
||||
borderWidth: 0.8,
|
||||
borderRadius: 4,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Card, Text } from "react-native-paper";
|
||||
import { connect } from "react-redux";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import { DateTimeFormatter } from "../../util/DateFormater";
|
||||
import { setTmTicketJobId } from "../../redux/app/app.actions";
|
||||
import styles from "../styles";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setTmTicketJobIdRedux: (jobId) => dispatch(setTmTicketJobId(jobId)),
|
||||
});
|
||||
|
||||
export function ClockedinListItem({ setTmTicketJobIdRedux, ticket }) {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const makeNavToTimeTicketClockOff = () => (
|
||||
setTmTicketJobIdRedux(ticket.job.id),
|
||||
navigation.navigate("TimeTicketClockOff", {
|
||||
timeTicketId:ticket.id,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<Card style={{ margin: 8 }}>
|
||||
<Card.Title
|
||||
title={`${
|
||||
ticket.job.ro_number || t("general.labels.na")
|
||||
} ${OwnerNameDisplayFunction(ticket.job)}`}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Text>
|
||||
{t("clockedinlistitem.labels.vehicle")}
|
||||
{`${ticket.job.v_model_yr || ""} ${ticket.job.v_make_desc || ""} ${
|
||||
ticket.job.v_model_desc || ""
|
||||
}`}
|
||||
</Text>
|
||||
<Text>
|
||||
{t("clockedinlistitem.labels.clockedin")}<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||
</Text>
|
||||
<Text>
|
||||
{t("clockedinlistitem.labels.costcenter")}
|
||||
{ticket.cost_center === "timetickets.labels.shift"
|
||||
? t(ticket.cost_center)
|
||||
: ticket.cost_center}
|
||||
</Text>
|
||||
</Card.Content>
|
||||
<Card.Actions style={{
|
||||
justifyContent: 'center'}}>
|
||||
<Button mode="outlined" style={styles.buttonBasicOutlined} onPress={makeNavToTimeTicketClockOff} >
|
||||
{t("clockedinlistitem.actions.clockout")}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ClockedinListItem);
|
||||
@@ -0,0 +1,151 @@
|
||||
import { connect } from "react-redux";
|
||||
import { selectCurrentEmployee } from "../../redux/employee/employee.selectors";
|
||||
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
|
||||
import { ActivityIndicator } from "react-native-paper";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import { View, Text, FlatList, RefreshControl } from "react-native";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ClockedinListItem from "../time-ticket-items/clockedin-list-item.component";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// import { setTmTicketJobId } from "../../redux/app/app.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//technician: selectTechnician,
|
||||
currentEmployee: selectCurrentEmployee,
|
||||
// updateJobs: selectUpdateJobs
|
||||
});
|
||||
// const mapDispatchToProps = (dispatch) => ({
|
||||
// setTmTicketJobId: (jobId) => dispatch(setTmTicketJobId(jobId)),
|
||||
// });
|
||||
|
||||
export function EmployeeClockedInList({ currentEmployee, isRefresh }) {
|
||||
const [refreshKey, setRefreshKey] = useState(isRefresh);
|
||||
const [jobData, setJobData] = useState(data);
|
||||
// console.info("EmployeeClockedInList, QUERY_ACTIVE_TIME_TICKETS called.",currentEmployee);
|
||||
|
||||
// console.info(
|
||||
// "EmployeeClockedInList, isRefresh :",
|
||||
// isRefresh
|
||||
// );
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
// async function fetchData(){
|
||||
// // const result = await getUsers();
|
||||
// // setData(result);
|
||||
// console.log("teste: ", result);
|
||||
// }
|
||||
// fetchData();
|
||||
if(isRefresh){
|
||||
refetch();
|
||||
}
|
||||
// console.log("useEffect: ", isRefresh);
|
||||
// setRefreshKey(isRefresh);
|
||||
|
||||
}, [isRefresh]);
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_ACTIVE_TIME_TICKETS,
|
||||
{
|
||||
variables: {
|
||||
employeeId: currentEmployee?.technician?.id,
|
||||
},
|
||||
skip: !currentEmployee?.technician,
|
||||
onCompleted:setJobData,
|
||||
onRefresh:{onRefresh}
|
||||
}
|
||||
);
|
||||
if (loading) return <ActivityIndicator color="dodgerblue" size="large" />;
|
||||
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||
|
||||
const onRefresh = async () => {
|
||||
// console.info("EmployeeClockedInList, onRefresh.");
|
||||
return refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexGrow: 1 }}>
|
||||
{jobData ? (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ paddingLeft: 12, paddingTop: 14 }}>
|
||||
{t("employeeclockedinlist.labels.alreadyclockedon")}
|
||||
</Text>
|
||||
<FlatList
|
||||
data={jobData?.timetickets}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
renderItem={(object) => (
|
||||
<ClockedinListItem
|
||||
ticket={object.item}
|
||||
// handleRefresh={onRefresh}
|
||||
/>
|
||||
)}
|
||||
// setTmTicketJobId={setTmTicketJobId}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
// <div>
|
||||
// {data.timetickets.length > 0 ? (
|
||||
// <div>
|
||||
// <Typography.Title level={2}>
|
||||
// {t("timetickets.labels.alreadyclockedon")}
|
||||
// </Typography.Title>
|
||||
// <List
|
||||
// grid={{
|
||||
// gutter: 32,
|
||||
// xs: 1,
|
||||
// sm: 2,
|
||||
// md: 3,
|
||||
// lg: 4,
|
||||
// xl: 5,
|
||||
// xxl: 6,
|
||||
// }}
|
||||
// dataSource={data.timetickets || []}
|
||||
// renderItem={(ticket) => (
|
||||
// <List.Item>
|
||||
// <Card
|
||||
// title={
|
||||
// <Link to={`/tech/joblookup?selected=${ticket.job.id}`}>
|
||||
// {`${
|
||||
// ticket.job.ro_number || t("general.labels.na")
|
||||
// } ${OwnerNameDisplayFunction(ticket.job)}`}
|
||||
// </Link>
|
||||
// }
|
||||
// actions={[
|
||||
// <TechClockOffButton
|
||||
// jobId={ticket.jobid}
|
||||
// timeTicketId={ticket.id}
|
||||
// completedCallback={refetch}
|
||||
// />,
|
||||
// ]}
|
||||
// >
|
||||
// <div>
|
||||
// {`
|
||||
// ${ticket.job.v_model_yr || ""} ${
|
||||
// ticket.job.v_make_desc || ""
|
||||
// } ${ticket.job.v_model_desc || ""}`}
|
||||
// </div>
|
||||
// <DataLabel label={t("timetickets.fields.clockon")}>
|
||||
// <DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||
// </DataLabel>
|
||||
// <DataLabel label={t("timetickets.fields.cost_center")}>
|
||||
// {ticket.cost_center === "timetickets.labels.shift"
|
||||
// ? t(ticket.cost_center)
|
||||
// : ticket.cost_center}
|
||||
// </DataLabel>
|
||||
// </Card>
|
||||
// </List.Item>
|
||||
// )}
|
||||
// ></List>
|
||||
// </div>
|
||||
// ) : null}
|
||||
// </div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(EmployeeClockedInList);
|
||||
266
components/time-ticket/screen-time-ticket-clockoff.component.jsx
Normal file
266
components/time-ticket/screen-time-ticket-clockoff.component.jsx
Normal file
@@ -0,0 +1,266 @@
|
||||
import { Formik } from "formik";
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { StyleSheet, Text, View, ScrollView, FlatList } from "react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Card,
|
||||
} from "react-native-paper";
|
||||
import CostCenterSelect from "../Selects/select-cost-center";
|
||||
import {
|
||||
selectCurrentEmployee,
|
||||
selectRates,
|
||||
} from "../../redux/employee/employee.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectRestrictClaimableHoursFlag,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { selectCurrentTmTicketJobId } from "../../redux/app/app.selectors";
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import { timeTicketClockOutStart } from "../../redux/timetickets/timetickets.actions";
|
||||
import axios from "axios";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import styles from "../styles";
|
||||
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEmployee: selectCurrentEmployee,
|
||||
currentRatesNCostCenters: selectRates,
|
||||
currentBodyshop: selectBodyshop,
|
||||
currentTmTicketJobId: selectCurrentTmTicketJobId,
|
||||
currentRestrictClaimableHoursFlag: selectRestrictClaimableHoursFlag,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
timeTicketClockOutStart,
|
||||
});
|
||||
|
||||
export function TimeTicketClockOff({
|
||||
currentEmployee,
|
||||
currentRatesNCostCenters,
|
||||
currentBodyshop,
|
||||
currentTmTicketJobId,
|
||||
currentRestrictClaimableHoursFlag,
|
||||
route,
|
||||
}) {
|
||||
const costCenterDiff = useRef(0);
|
||||
const setCostCenterDiff = (value) => {
|
||||
countRef.current = val;
|
||||
};
|
||||
const navigation = useNavigation();
|
||||
const { timeTicketId } = route.params;
|
||||
const { t } = useTranslation();
|
||||
const [loadingClockOut, setLoadingClockOut] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [currentSCC, setCurrentSCC] = useState(null);
|
||||
|
||||
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET, {
|
||||
refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"],
|
||||
});
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
if (
|
||||
!!values.actualhours &&
|
||||
!!values.productivehours &&
|
||||
!!currentSCC?.value
|
||||
) {
|
||||
if (isNaN(values.actualhours) | isNaN(values.productivehours)) {
|
||||
setLoadingClockOut(false);
|
||||
setError({ message: t("timeticketclockoff.errors.nan") });
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
} else {
|
||||
setLoadingClockOut(false);
|
||||
setError({ message: t("timeticketclockoff.errors.missingvalues") });
|
||||
return;
|
||||
}
|
||||
if (!!currentRestrictClaimableHoursFlag) {
|
||||
if (values.productivehours > costCenterDiff.current) {
|
||||
setLoadingClockOut(false);
|
||||
setError({
|
||||
message: t("timeticketclockoff.errors.hoursenteredmorethanavailable"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const tempcallobj = {
|
||||
variables: {
|
||||
timeticketId: timeTicketId,
|
||||
timeticket: {
|
||||
actualhrs: values?.actualhours,
|
||||
ciecacode:
|
||||
currentBodyshop?.cdk_dealerid || currentBodyshop?.pbs_serialnumber
|
||||
? currentSCC?.value
|
||||
: Object.keys(
|
||||
currentBodyshop.md_responsibility_centers.defaults.costs
|
||||
).find((key) => {
|
||||
return (
|
||||
currentBodyshop.md_responsibility_centers.defaults.costs[
|
||||
key
|
||||
] === currentSCC?.value
|
||||
);
|
||||
}),
|
||||
clockoff: (await axios.post("/utils/time")).data,
|
||||
cost_center: currentSCC?.value,
|
||||
flat_rate:
|
||||
currentEmployee &&
|
||||
currentEmployee.technician &&
|
||||
currentEmployee.technician?.flat_rate,
|
||||
productivehrs: values?.productivehours,
|
||||
rate:
|
||||
currentRatesNCostCenters &&
|
||||
currentSCC?.value &&
|
||||
currentRatesNCostCenters.filter(
|
||||
(r) => r.cost_center === currentSCC?.value
|
||||
)[0]?.rate,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setLoadingClockOut(true);
|
||||
const result = await updateTimeticket(tempcallobj);
|
||||
setLoadingClockOut(false);
|
||||
if (!!result.errors) {
|
||||
setError(JSON.stringify(result.errors));
|
||||
} else {
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setLoading(true);
|
||||
// refetch();
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.cardBackground}>
|
||||
<FlatList
|
||||
ListHeaderComponent={
|
||||
<Card style={localStyles.localCardStyle}>
|
||||
<Card.Content>
|
||||
<Formik
|
||||
initialValues={{
|
||||
costcenter: { currentSCC },
|
||||
productivehours: "",
|
||||
actualhours: "",
|
||||
}}
|
||||
onSubmit={handleFinish}
|
||||
>
|
||||
{({ handleChange, handleBlur, handleSubmit, values }) => (
|
||||
<View style={localStyles.topTimeTicketContainer}>
|
||||
<TextInput
|
||||
style={localStyles.inputStyle}
|
||||
mode="outlined"
|
||||
onChangeText={handleChange("actualhours")}
|
||||
onBlur={handleBlur("actualhours")}
|
||||
value={values.actualhours}
|
||||
label={t(
|
||||
"timeticketclockoff.labels.actualhoursplaceholder"
|
||||
)}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<TextInput
|
||||
style={localStyles.inputStyle}
|
||||
mode="outlined"
|
||||
onChangeText={handleChange("productivehours")}
|
||||
onBlur={handleBlur("productivehours")}
|
||||
value={values.productivehours}
|
||||
label={t(
|
||||
"timeticketclockoff.labels.productivehoursplaceholder"
|
||||
)}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<CostCenterSelect
|
||||
currentRatesNCostCenters={currentRatesNCostCenters}
|
||||
currentValue={currentSCC}
|
||||
onValueSelected={setCurrentSCC}
|
||||
/>
|
||||
{error ? (
|
||||
<ErrorDisplay errorMessage={error.message} />
|
||||
) : null}
|
||||
<View style={{ flexDirection: "row",justifyContent: "center", paddingTop:8 }}>
|
||||
<Button
|
||||
|
||||
mode="outlined"
|
||||
style={styles.buttonBasicOutlined}
|
||||
onPress={handleSubmit}
|
||||
title={t("timeticketclockoff.actions.clockoff")}
|
||||
loading={loadingClockOut}
|
||||
>
|
||||
<Text style={{ fontSize: 12 }}>
|
||||
{t("timeticketclockoff.actions.clockoff")}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</Formik>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
}
|
||||
data={null}
|
||||
renderItem={null}
|
||||
ListFooterComponent={
|
||||
<LaborAllocationsTable
|
||||
jobId={currentTmTicketJobId}
|
||||
costCenterDiff={costCenterDiff}
|
||||
selectedCostCenter={currentSCC}
|
||||
style={localStyles.localCardStyle}
|
||||
shouldRefresh={loading}
|
||||
/>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(TimeTicketClockOff);
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
content: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
},
|
||||
topContainer: {},
|
||||
bottomContainer: {},
|
||||
input: {},
|
||||
dateButton: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
height: 48,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
borderColor: "blue",
|
||||
borderWidth: 0.8,
|
||||
flex: 1,
|
||||
},
|
||||
textForButton: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
},
|
||||
inputStyle: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
height: 48,
|
||||
fontSize: 16,
|
||||
},
|
||||
localCardStyle: {
|
||||
margin: 4,
|
||||
},
|
||||
});
|
||||
327
components/time-ticket/screen-time-ticket-create.component.jsx
Normal file
327
components/time-ticket/screen-time-ticket-create.component.jsx
Normal file
@@ -0,0 +1,327 @@
|
||||
import { Formik } from "formik";
|
||||
import React, { useRef, useState,useCallback } from "react";
|
||||
import { StyleSheet, Text, View, ScrollView, RefreshControl,FlatList } from "react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Button, TextInput, Card } from "react-native-paper";
|
||||
import CostCenterSelect from "../Selects/select-cost-center";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
selectCurrentEmployee,
|
||||
selectRates,
|
||||
selectEmployeeFullName,
|
||||
} from "../../redux/employee/employee.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectRestrictClaimableHoursFlag,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||
|
||||
import ErrorDisplay from "../error-display/error-display.component";
|
||||
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import moment from "moment";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import styles from "../styles";
|
||||
import JobSearchAndSelectModal from "../Modals/JobSearchAndSelectModal";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEmployee: selectCurrentEmployee,
|
||||
currentRatesNCostCenters: selectRates,
|
||||
currentBodyshop: selectBodyshop,
|
||||
currentEmployeeFullName: selectEmployeeFullName,
|
||||
currentRestrictClaimableHoursFlag: selectRestrictClaimableHoursFlag,
|
||||
});
|
||||
|
||||
export function TimeTicketCreate({
|
||||
currentEmployee,
|
||||
currentRatesNCostCenters,
|
||||
currentBodyshop,
|
||||
currentEmployeeFullName,
|
||||
currentRestrictClaimableHoursFlag,
|
||||
}) {
|
||||
const costCenterDiff = useRef(0);
|
||||
const navigation = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingCreate, setLoadingCreate] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [date2, setDate2] = useState(new Date());
|
||||
|
||||
const [currentSCC, setCurrentSCC] = useState(null);
|
||||
const [curSelClockIntoJob,setCurSelClockIntoJob] = useState(null);
|
||||
const showDatePicker = () => {
|
||||
setDatePickerVisibility(true);
|
||||
};
|
||||
const hideDatePicker = () => {
|
||||
setDatePickerVisibility(false);
|
||||
};
|
||||
const handleConfirm = (date) => {
|
||||
setDate2(date);
|
||||
hideDatePicker();
|
||||
};
|
||||
|
||||
const [insertTicket] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setError(null);
|
||||
setLoadingCreate(true);
|
||||
if (
|
||||
!!currentSCC?.value &&
|
||||
!!curSelClockIntoJob?.id &&
|
||||
!!values.productivehours &&
|
||||
!!currentBodyshop.id &&
|
||||
!!currentEmployee?.technician?.id &&
|
||||
!!date2 &&
|
||||
!!values.actualhours
|
||||
) {
|
||||
if (isNaN(values.actualhours) | isNaN(values.productivehours)) {
|
||||
// console.log("actual hours is NAN!");
|
||||
setLoadingCreate(false);
|
||||
setError({ message: t("createtimeticket.errors.nan") });
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
// console.log("have all values");
|
||||
} else {
|
||||
// console.log("missing values!");
|
||||
setLoadingCreate(false);
|
||||
setError({ message: t("createtimeticket.errors.missingvalues") });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!currentRestrictClaimableHoursFlag) {
|
||||
if (values.productivehours > costCenterDiff.current) {
|
||||
setLoadingCreate(false);
|
||||
setError({
|
||||
message: t("timeticketclockoff.errors.hoursenteredmorethanavailable"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const tempVariablesObj = {
|
||||
variables: {
|
||||
timeTicketInput: [
|
||||
{
|
||||
actualhrs: values?.actualhours ? values?.actualhours : null,
|
||||
bodyshopid: currentBodyshop.id,
|
||||
ciecacode:
|
||||
currentBodyshop?.cdk_dealerid || currentBodyshop?.pbs_serialnumber
|
||||
? currentSCC?.value
|
||||
: Object.keys(
|
||||
currentBodyshop.md_responsibility_centers.defaults.costs
|
||||
).find((key) => {
|
||||
return (
|
||||
currentBodyshop.md_responsibility_centers.defaults.costs[
|
||||
key
|
||||
] === currentSCC?.value
|
||||
);
|
||||
}),
|
||||
cost_center: currentSCC?.value,
|
||||
date: moment(date2).format("YYYY-MM-DD"),
|
||||
employeeid: currentEmployee?.technician?.id,
|
||||
flat_rate:
|
||||
currentEmployee &&
|
||||
currentEmployee.technician &&
|
||||
currentEmployee.technician?.flat_rate,
|
||||
jobid: curSelClockIntoJob?.id,
|
||||
productivehrs: values?.productivehours,
|
||||
rate:
|
||||
currentRatesNCostCenters && currentSCC?.value
|
||||
? currentRatesNCostCenters.filter(
|
||||
(r) => r.cost_center === currentSCC?.value
|
||||
)[0].rate
|
||||
: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await insertTicket(tempVariablesObj);
|
||||
setLoadingCreate(false);
|
||||
if (!!result.errors) {
|
||||
// console.log("insertTicket, result.error :", result.errors);
|
||||
setError(JSON.stringify(result.errors));
|
||||
} else {
|
||||
// console.log("insertTicket, result. :", result.data);
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setLoading(true);
|
||||
// refetch();
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.cardBackground}>
|
||||
<FlatList
|
||||
ListHeaderComponent={
|
||||
<Card style={localStyles.localCardStyle}>
|
||||
<Card.Content>
|
||||
<Formik
|
||||
initialValues={{
|
||||
ticketdate: date2.toLocaleDateString(),
|
||||
employee: { currentEmployeeFullName },
|
||||
costcenter: { currentSCC },
|
||||
productivehours: "",
|
||||
actualhours: "",
|
||||
}}
|
||||
onSubmit={handleFinish}
|
||||
>
|
||||
{({ handleChange, handleBlur, handleSubmit, values }) => (
|
||||
<View style={localStyles.topTimeTicketContainer}>
|
||||
<JobSearchAndSelectModal
|
||||
currentValue={curSelClockIntoJob}
|
||||
onSetCurrentValue={setCurSelClockIntoJob}
|
||||
notExported={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||
notInvoiced={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||
convertedOnly={true}
|
||||
/>
|
||||
<Button
|
||||
mode="outlined"
|
||||
title="TicketDatePickerButton"
|
||||
onPress={showDatePicker}
|
||||
style={localStyles.dateButton}
|
||||
>
|
||||
<Text style={localStyles.textForButton}>
|
||||
{t("createtimeticket.actions.ticketdate")}
|
||||
{date2.toLocaleDateString("en-US", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
})}
|
||||
</Text>
|
||||
</Button>
|
||||
<DateTimePickerModal
|
||||
isVisible={isDatePickerVisible}
|
||||
mode="date"
|
||||
date={date2}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={hideDatePicker}
|
||||
/>
|
||||
<TextInput
|
||||
style={localStyles.inputStyle}
|
||||
mode="outlined"
|
||||
disabled={true}
|
||||
value={currentEmployeeFullName}
|
||||
label={t("createtimeticket.labels.employeeplaceholder")}
|
||||
/>
|
||||
<CostCenterSelect
|
||||
currentRatesNCostCenters={currentRatesNCostCenters}
|
||||
currentValue={currentSCC}
|
||||
onValueSelected={setCurrentSCC}
|
||||
/>
|
||||
<TextInput
|
||||
style={localStyles.inputStyle}
|
||||
mode="outlined"
|
||||
onChangeText={handleChange("productivehours")}
|
||||
onBlur={handleBlur("productivehours")}
|
||||
value={values.productivehours}
|
||||
label={t(
|
||||
"createtimeticket.labels.productivehoursplaceholder"
|
||||
)}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<TextInput
|
||||
style={localStyles.inputStyle}
|
||||
mode="outlined"
|
||||
onChangeText={handleChange("actualhours")}
|
||||
onBlur={handleBlur("actualhours")}
|
||||
value={values.actualhours}
|
||||
label={t(
|
||||
"createtimeticket.labels.actualhoursplaceholder"
|
||||
)}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
{error ? (
|
||||
<ErrorDisplay errorMessage={error.message} />
|
||||
) : null}
|
||||
<View style={{ flexDirection: "row",justifyContent: "center", paddingTop:8 }}>
|
||||
|
||||
<Button
|
||||
mode="outlined"
|
||||
style={styles.buttonBasicOutlined}
|
||||
onPress={handleSubmit}
|
||||
loading={loadingCreate}
|
||||
title="Submit"
|
||||
>
|
||||
<Text style={{ fontSize: 12 }}>
|
||||
{t("createtimeticket.actions.createticket")}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</Formik>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
}
|
||||
data={null}
|
||||
renderItem={null}
|
||||
ListFooterComponent={
|
||||
<LaborAllocationsTable
|
||||
jobId={curSelClockIntoJob?.id}
|
||||
costCenterDiff={costCenterDiff}
|
||||
selectedCostCenter={currentSCC}
|
||||
style={localStyles.localCardStyle}
|
||||
shouldRefresh={loading}
|
||||
/>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(TimeTicketCreate);
|
||||
|
||||
const localStyles = StyleSheet.create({
|
||||
content: {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
},
|
||||
topContainer: {},
|
||||
bottomContainer: {},
|
||||
input: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
height: 48,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
borderWidth: 0.8,
|
||||
},
|
||||
dateButton: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
height: 48,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
borderColor: "gray",
|
||||
borderWidth: 0.8,
|
||||
},
|
||||
textForButton: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
},
|
||||
inputStyle: {
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 16,
|
||||
height: 48,
|
||||
fontSize: 16,
|
||||
},
|
||||
localCardStyle: {
|
||||
margin: 4,
|
||||
},
|
||||
});
|
||||
@@ -5,12 +5,17 @@ export const QUERY_BODYSHOP = gql`
|
||||
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||
id
|
||||
jobsizelimit
|
||||
cdk_dealerid
|
||||
pbs_serialnumber
|
||||
md_ro_statuses
|
||||
uselocalmediaserver
|
||||
localmediaserverhttp
|
||||
shopname
|
||||
features
|
||||
localmediatoken
|
||||
tt_allow_post_to_invoiced
|
||||
md_responsibility_centers
|
||||
tt_enforce_hours_for_tech_console
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
54
graphql/employees.queries.js
Normal file
54
graphql/employees.queries.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
export const QUERY_EMPLOYEES = gql`
|
||||
query QUERY_EMPLOYEES {
|
||||
employees(order_by: { employee_number: asc }) {
|
||||
last_name
|
||||
id
|
||||
first_name
|
||||
flat_rate
|
||||
employee_number
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const QUERY_EMPLOYEE_BY_ID = gql`
|
||||
query QUERY_EMPLOYEE_BY_ID($id: uuid!) {
|
||||
employees_by_pk(id: $id) {
|
||||
last_name
|
||||
id
|
||||
first_name
|
||||
employee_number
|
||||
active
|
||||
termination_date
|
||||
hire_date
|
||||
flat_rate
|
||||
rates
|
||||
pin
|
||||
user_email
|
||||
external_id
|
||||
employee_vacations(order_by: { start: desc }) {
|
||||
id
|
||||
start
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_ACTIVE_EMPLOYEES = gql`
|
||||
query QUERY_ACTIVE_EMPLOYEES {
|
||||
employees(where: { active: { _eq: true } }) {
|
||||
last_name
|
||||
id
|
||||
first_name
|
||||
employee_number
|
||||
active
|
||||
termination_date
|
||||
hire_date
|
||||
flat_rate
|
||||
rates
|
||||
pin
|
||||
user_email
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -451,6 +451,54 @@ export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
query SEARCH_JOBS_FOR_AUTOCOMPLETE(
|
||||
$search: String
|
||||
$isConverted: Boolean
|
||||
$notExported: Boolean
|
||||
$notInvoiced: Boolean
|
||||
) {
|
||||
search_jobs(
|
||||
args: { search: $search }
|
||||
limit: 50
|
||||
where: {
|
||||
_and: {
|
||||
converted: { _eq: $isConverted }
|
||||
date_exported: { _is_null: $notExported }
|
||||
date_invoiced: { _is_null: $notInvoiced }
|
||||
}
|
||||
}
|
||||
) {
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
clm_no
|
||||
vehicleid
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
|
||||
query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
clm_no
|
||||
vehicleid
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SEARCH_FOR_JOBS = gql`
|
||||
query SEARCH_FOR_JOBS($search: String!) {
|
||||
jobs(where: { ro_number: { _ilike: $search } }) {
|
||||
@@ -875,6 +923,54 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_LINE_TICKET_BY_PK = gql`
|
||||
query GET_LINE_TICKET_BY_PK($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
lbr_adjustments
|
||||
converted
|
||||
}
|
||||
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
||||
id
|
||||
line_desc
|
||||
part_type
|
||||
oem_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
timetickets(where: { jobid: { _eq: $id } }) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
cost_center
|
||||
date
|
||||
id
|
||||
jobid
|
||||
employeeid
|
||||
memo
|
||||
flat_rate
|
||||
clockon
|
||||
clockoff
|
||||
rate
|
||||
employee {
|
||||
id
|
||||
first_name
|
||||
last_name
|
||||
employee_number
|
||||
}
|
||||
productivehrs
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generate_UPDATE_JOB_KANBAN = (
|
||||
oldChildId,
|
||||
oldChildNewParent,
|
||||
|
||||
85
graphql/timetickets.queries.js
Normal file
85
graphql/timetickets.queries.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
|
||||
|
||||
export const INSERT_NEW_TIME_TICKET = gql`
|
||||
mutation INSERT_NEW_TIME_TICKET(
|
||||
$timeTicketInput: [timetickets_insert_input!]!
|
||||
) {
|
||||
insert_timetickets(objects: $timeTicketInput) {
|
||||
returning {
|
||||
id
|
||||
clockon
|
||||
clockoff
|
||||
employeeid
|
||||
productivehrs
|
||||
actualhrs
|
||||
ciecacode
|
||||
date
|
||||
memo
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_TIME_TICKET = gql`
|
||||
mutation UPDATE_TIME_TICKET(
|
||||
$timeticketId: uuid!
|
||||
$timeticket: timetickets_set_input!
|
||||
) {
|
||||
update_timetickets(
|
||||
where: { id: { _eq: $timeticketId } }
|
||||
_set: $timeticket
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
clockon
|
||||
clockoff
|
||||
employeeid
|
||||
productivehrs
|
||||
actualhrs
|
||||
ciecacode
|
||||
created_at
|
||||
updated_at
|
||||
jobid
|
||||
date
|
||||
flat_rate
|
||||
memo
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_ACTIVE_TIME_TICKETS = gql`
|
||||
query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) {
|
||||
timetickets(
|
||||
order_by: { date: desc_nulls_first }
|
||||
where: {
|
||||
_and: {
|
||||
clockoff: { _is_null: true }
|
||||
employeeid: { _eq: $employeeId }
|
||||
clockon: { _is_null: false }
|
||||
jobid: { _is_null: false }
|
||||
}
|
||||
}
|
||||
) {
|
||||
id
|
||||
clockon
|
||||
memo
|
||||
cost_center
|
||||
flat_rate
|
||||
jobid
|
||||
job {
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_model_yr
|
||||
ro_number
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -22,6 +22,7 @@
|
||||
"@react-native-async-storage/async-storage": "^1.17.11",
|
||||
"@react-native-community/art": "^1.2.0",
|
||||
"@react-native-community/cli-debugger-ui": "^9.0.0",
|
||||
"@react-native-community/datetimepicker": "6.7.3",
|
||||
"@react-native-community/masked-view": "^0.1.11",
|
||||
"@react-navigation/bottom-tabs": "^6.3.3",
|
||||
"@react-navigation/drawer": "^6.4.4",
|
||||
@@ -46,7 +47,7 @@
|
||||
"expo-permissions": "^14.1.1",
|
||||
"expo-status-bar": "~1.4.2",
|
||||
"expo-system-ui": "^2.2.1",
|
||||
"expo-updates": "^0.16.3",
|
||||
"expo-updates": "^0.16.4",
|
||||
"expo-video-thumbnails": "^7.2.1",
|
||||
"firebase": "^9.8.3",
|
||||
"formik": "^2.2.9",
|
||||
@@ -62,13 +63,15 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^11.17.2",
|
||||
"react-is": ">= 16.8.0",
|
||||
"react-native": "^0.71.4",
|
||||
"react-native": "0.71.6",
|
||||
"react-native-dnd-board": "hungga1711/react-native-dnd-board#13/head",
|
||||
"react-native-draggable-flatlist": "^3.1.2",
|
||||
"react-native-element-dropdown": "^2.9.0",
|
||||
"react-native-gesture-handler": "^2.9.0",
|
||||
"react-native-image-gallery": "^2.1.5",
|
||||
"react-native-image-viewing": "^0.2.2",
|
||||
"react-native-indicators": "^0.17.0",
|
||||
"react-native-modal-datetime-picker": "^15.0.0",
|
||||
"react-native-pager-view": "6.1.2",
|
||||
"react-native-paper": "^4.12.4",
|
||||
"react-native-progress": "^5.0.0",
|
||||
|
||||
@@ -27,3 +27,8 @@ export const documentUploadFailure = (error) => ({
|
||||
export const toggleDeleteAfterUpload = () => ({
|
||||
type: AppActionTypes.TOGGLE_DLETE_AFTER_UPLOAD,
|
||||
});
|
||||
|
||||
export const setTmTicketJobId = (jobId) => ({
|
||||
type: AppActionTypes.SET_TM_TICKET_JOB_ID,
|
||||
payload: jobId,
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ const INITIAL_STATE = {
|
||||
documentUploadInProgress: null,
|
||||
documentUploadError: null,
|
||||
deleteAfterUpload: false,
|
||||
tmTicketJobId: null,
|
||||
};
|
||||
|
||||
const appReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -43,6 +44,11 @@ const appReducer = (state = INITIAL_STATE, action) => {
|
||||
...state,
|
||||
deleteAfterUpload: !state.deleteAfterUpload,
|
||||
};
|
||||
case AppActionTypes.SET_TM_TICKET_JOB_ID:
|
||||
return {
|
||||
...state,
|
||||
tmTicketJobId: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -26,3 +26,8 @@ export const selectDeleteAfterUpload = createSelector(
|
||||
[selectApp],
|
||||
(app) => app.deleteAfterUpload
|
||||
);
|
||||
|
||||
export const selectCurrentTmTicketJobId = createSelector(
|
||||
[selectApp],
|
||||
(app) => app.tmTicketJobId
|
||||
);
|
||||
|
||||
@@ -5,5 +5,6 @@ const AppActionTypes = {
|
||||
DOCUMENT_UPLOAD_SUCCESS: "DOCUMENT_UPLOAD_SUCCESS",
|
||||
DOCUMENT_UPLOAD_FAILURE: "DOCUMENT_UPLOAD_FAILURE",
|
||||
TOGGLE_DLETE_AFTER_UPLOAD: "TOGGLE_DLETE_AFTER_UPLOAD",
|
||||
SET_TM_TICKET_JOB_ID: "SET_TM_TICKET_JOB_ID"
|
||||
};
|
||||
export default AppActionTypes;
|
||||
|
||||
34
redux/employee/employee.actions.js
Normal file
34
redux/employee/employee.actions.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import EmployeeActionTypes from "./employee.types";
|
||||
|
||||
export const employeeSignInStart = (employeeIdAndPin) => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_SIGN_IN_START,
|
||||
payload: employeeIdAndPin,
|
||||
});
|
||||
|
||||
export const employeeSignInSuccess = (technician) => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_SIGN_IN_SUCCESS,
|
||||
payload: technician,
|
||||
});
|
||||
|
||||
export const employeeSignInFailure = (error) => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_SIGN_IN_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
|
||||
export const employeeSignOut = () => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_SIGN_OUT,
|
||||
});
|
||||
|
||||
export const employeeGetRatesStart = (employeeId) => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_GET_RATES_START,
|
||||
payload: employeeId,
|
||||
});
|
||||
export const employeeGetRatesSuccess = (employees_by_pk) => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_GET_RATES_SUCCESS,
|
||||
payload: employees_by_pk,
|
||||
});
|
||||
export const employeeGetRatesFailure = (error) => ({
|
||||
type: EmployeeActionTypes.EMPLOYEE_GET_RATES_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
|
||||
63
redux/employee/employee.reducer.js
Normal file
63
redux/employee/employee.reducer.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import EmployeeActionTypes from "./employee.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
currentEmployee: {
|
||||
authorized: null,
|
||||
technician: null,
|
||||
},
|
||||
signingIn: false,
|
||||
gettingRates: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const employeeReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case EmployeeActionTypes.EMPLOYEE_SIGN_IN_START:
|
||||
return {
|
||||
...state,
|
||||
signingIn: true,
|
||||
};
|
||||
case EmployeeActionTypes.EMPLOYEE_SIGN_IN_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
currentEmployee: { authorized: true, technician:action.payload },
|
||||
signingIn: false,
|
||||
error: null,
|
||||
};
|
||||
case EmployeeActionTypes.EMPLOYEE_SIGN_IN_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
signingIn: false,
|
||||
error: action.payload,
|
||||
};
|
||||
case EmployeeActionTypes.EMPLOYEE_SIGN_OUT:
|
||||
return {
|
||||
...state,
|
||||
currentEmployee: { authorized: false, technician:null },//TODO check if this needs to do clean up like set loaders all to false
|
||||
error: null,
|
||||
};
|
||||
|
||||
case EmployeeActionTypes.EMPLOYEE_GET_RATES_START:
|
||||
return {
|
||||
...state,
|
||||
gettingRates: true,
|
||||
};
|
||||
case EmployeeActionTypes.EMPLOYEE_GET_RATES_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
currentEmployee: { ...state.currentEmployee, technician:action.payload },
|
||||
gettingRates: false,
|
||||
error: null,
|
||||
};
|
||||
case EmployeeActionTypes.EMPLOYEE_GET_RATES_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
gettingRates: false,
|
||||
error: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default employeeReducer;
|
||||
114
redux/employee/employee.sagas.js
Normal file
114
redux/employee/employee.sagas.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import EmployeeActionTypes from "./employee.types";
|
||||
import {
|
||||
employeeSignInStart,
|
||||
employeeSignInSuccess,
|
||||
employeeSignInFailure,
|
||||
employeeGetRatesStart,
|
||||
employeeGetRatesSuccess,
|
||||
employeeGetRatesFailure
|
||||
|
||||
} from "./employee.actions";
|
||||
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
||||
import { selectBodyshop } from "../user/user.selectors";
|
||||
import axios from "axios";
|
||||
import { client } from "../../graphql/client";
|
||||
import { QUERY_EMPLOYEE_BY_ID } from "../../graphql/employees.queries";
|
||||
import { selectCurrentEmployee } from "./employee.selectors";
|
||||
|
||||
export function* onSignInEmployeeStart() {
|
||||
yield takeLatest(
|
||||
EmployeeActionTypes.EMPLOYEE_SIGN_IN_START,
|
||||
signInWithEmployeeId
|
||||
);
|
||||
}
|
||||
export function* signInWithEmployeeId({ payload: { employeeId, pin } }) {
|
||||
try {
|
||||
logImEXEvent("redux_sign_in_employee_attempt");
|
||||
//console.loging
|
||||
// console.log("Saga", employeeId, pin, pin);
|
||||
const bodyshop = yield select(selectBodyshop);
|
||||
const response = yield call(axios.post, "/tech/login", {
|
||||
shopid: bodyshop.id,
|
||||
employeeid: employeeId,
|
||||
pin: pin,
|
||||
});
|
||||
const { valid, technician, error } = response.data;
|
||||
|
||||
if (valid) {
|
||||
yield put(employeeSignInSuccess(technician));
|
||||
} else {
|
||||
yield put(employeeSignInFailure(error));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put(employeeSignInFailure(error));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//Waits for EMPLOYEE_SIGN_IN_SUCCESS when logging in to then call QUERY_EMPLOYEE_BY_ID with the id in payload to override technician with more data like rates and flat_rate
|
||||
*/
|
||||
export function* onEmployeeSignInSuccessSaga() {
|
||||
yield takeLatest(
|
||||
EmployeeActionTypes.EMPLOYEE_SIGN_IN_SUCCESS,
|
||||
updateEmployeeWithEmployeeId
|
||||
);
|
||||
}
|
||||
export function* updateEmployeeWithEmployeeId({ payload }) {
|
||||
try {
|
||||
const employeeId = payload.id;
|
||||
// console.log("updateEmployeeWithEmployeeId",employeeId);
|
||||
// logImEXEvent("redux_update_employee_with_employee_id_attempt", employeeId);
|
||||
const result = yield client.query({ query: QUERY_EMPLOYEE_BY_ID,
|
||||
variables: {
|
||||
id: employeeId,
|
||||
},
|
||||
skip: !employeeId
|
||||
});
|
||||
const { employees_by_pk } = result?.data;
|
||||
if (employees_by_pk) {
|
||||
yield put(employeeGetRatesSuccess(employees_by_pk));
|
||||
} else {
|
||||
yield put(employeeGetRatesFailure(result?.error));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put(employeeGetRatesFailure(error));
|
||||
// console.log("Error while getting employee rates.", error);
|
||||
Sentry.Native.captureException(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function* onEmployeeGetRatesStart() {
|
||||
yield takeLatest(
|
||||
EmployeeActionTypes.EMPLOYEE_GET_RATES_START,
|
||||
getRatesWithEmployeeId
|
||||
);
|
||||
}
|
||||
export function* getRatesWithEmployeeId({ payload: employeeId }) {
|
||||
try {
|
||||
|
||||
logImEXEvent("redux_employee_get_rates_attempt", employeeId);
|
||||
const result = yield client.query({ query: QUERY_EMPLOYEE_BY_ID,
|
||||
variables: {
|
||||
id: employeeId,
|
||||
}
|
||||
});
|
||||
const { employees_by_pk } = result.data;
|
||||
|
||||
if (employees_by_pk) {
|
||||
yield put(employeeGetRatesSuccess(employees_by_pk));
|
||||
} else {
|
||||
yield put(employeeGetRatesFailure(error));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put(employeeGetRatesFailure(error));
|
||||
// console.log("Error while getting employee rates.", error);
|
||||
Sentry.Native.captureException(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function* employeeSagas() {
|
||||
yield all([call(onSignInEmployeeStart), call(onEmployeeSignInSuccessSaga), call(onEmployeeGetRatesStart)]);
|
||||
}
|
||||
75
redux/employee/employee.selectors.js
Normal file
75
redux/employee/employee.selectors.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectEmployee = (state) => state.employee;
|
||||
|
||||
export const selectCurrentEmployee = createSelector(
|
||||
[selectEmployee],
|
||||
(employee) => employee.currentEmployee
|
||||
);
|
||||
export const selectSigningIn = createSelector(
|
||||
[selectEmployee],
|
||||
(employee) => employee.signingIn
|
||||
);
|
||||
export const selectSignInError = createSelector(
|
||||
[selectEmployee],
|
||||
(employee) => employee.error
|
||||
);
|
||||
export const selectRates = createSelector(
|
||||
[selectEmployee],
|
||||
(employee) => employee.currentEmployee?.technician?.rates?.filter((v) => (v?.cost_center !== "timetickets.labels.shift"))
|
||||
);
|
||||
export const selectGettingRates = createSelector(
|
||||
[selectEmployee],
|
||||
(employee) => employee.gettingRates
|
||||
);
|
||||
export const selectTechnician = createSelector(
|
||||
[selectEmployee],
|
||||
(employee) => {
|
||||
if (!employee.currentEmployee || !employee.currentEmployee.technician) {
|
||||
// console.info("selectTechnician returning null");
|
||||
return null;
|
||||
}
|
||||
// console.info("selectTechnician returning :", employee.currentEmployee.technician);
|
||||
return employee.currentEmployee.technician;
|
||||
}
|
||||
);
|
||||
export const selectEmployeeFirstName = createSelector(
|
||||
[selectTechnician],
|
||||
(techResults) => {
|
||||
if (!techResults || !techResults.first_name) {
|
||||
// console.info("selectEmployeeFirstName returning null");
|
||||
return null;
|
||||
}
|
||||
// console.info("selectEmployeeFirstName returning :", techResults.first_name);
|
||||
return techResults.first_name;
|
||||
}
|
||||
);
|
||||
export const selectEmployeeLastName = createSelector(
|
||||
[selectTechnician],
|
||||
(techResults) => {
|
||||
if (!techResults || !techResults.last_name) {
|
||||
// console.info("selectEmployeeLastName returning null");
|
||||
return null;
|
||||
}
|
||||
// console.info("selectEmployeeLastName returning :", techResults.last_name);
|
||||
return techResults.last_name;
|
||||
}
|
||||
);
|
||||
export const selectEmployeeFullName = createSelector(
|
||||
[selectEmployeeFirstName,selectEmployeeLastName],
|
||||
(fName,lName) => {
|
||||
if (!fName && !lName) {
|
||||
// console.warn("selectEmployeeFullName returning null");
|
||||
return null;
|
||||
}
|
||||
if (fName) {
|
||||
if(lName){
|
||||
return fName + " " + lName;
|
||||
} else {
|
||||
return fName;
|
||||
}
|
||||
} else {
|
||||
return lName;
|
||||
}
|
||||
}
|
||||
);
|
||||
16
redux/employee/employee.types.js
Normal file
16
redux/employee/employee.types.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const EmployeeActionTypes = {
|
||||
EMPLOYEE_SIGN_IN_START: "EMPLOYEE_SIGN_IN_START",
|
||||
EMPLOYEE_SIGN_IN_SUCCESS: "EMPLOYEE_SIGN_IN_SUCCESS",
|
||||
EMPLOYEE_SIGN_IN_FAILURE: "EMPLOYEE_SIGN_IN_FAILURE",
|
||||
EMPLOYEE_AUTHORIZING_REQUEST: "EMPLOYEE_AUTHORIZING_REQUEST",
|
||||
EMPLOYEE_AUTHORIZING_SUCCESS: "EMPLOYEE_AUTHORIZING_SUCCESS",
|
||||
EMPLOYEE_AUTHORIZING_FAILURE: "EMPLOYEE_AUTHORIZING_FAILURE",
|
||||
EMPLOYEE_SIGN_OUT: "EMPLOYEE_SIGN_OUT",
|
||||
EMPLOYEE_GET_RATES_START: "EMPLOYEE_GET_RATES_START",
|
||||
EMPLOYEE_GET_RATES_SUCCESS: "EMPLOYEE_GET_RATES_SUCCESS",
|
||||
EMPLOYEE_GET_RATES_FAILURE: "EMPLOYEE_GET_RATES_FAILURE",
|
||||
|
||||
EMPLOYEE_CHECK_SESSION: "EMPLOYEE_CHECK_SESSION",
|
||||
EMPLOYEE_SET_CURRENT: "EMPLOYEE_SET_CURRENT"
|
||||
};
|
||||
export default EmployeeActionTypes;
|
||||
@@ -4,18 +4,22 @@ import { persistReducer } from "redux-persist";
|
||||
import appReducer from "./app/app.reducer";
|
||||
import photosReducer from "./photos/photos.reducer";
|
||||
import userReducer from "./user/user.reducer";
|
||||
import employeeReducer from './employee/employee.reducer';
|
||||
import timeTicketsReducer from './timetickets/timetickets.reducer';
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage: AsyncStorage,
|
||||
// whitelist: ["photos"],
|
||||
blacklist: ["user"],
|
||||
blacklist: ["user","employee","timeTickets"],
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
user: userReducer,
|
||||
app: appReducer,
|
||||
photos: photosReducer,
|
||||
employee: employeeReducer,
|
||||
timeTickets: timeTicketsReducer,
|
||||
});
|
||||
|
||||
export default persistReducer(persistConfig, rootReducer);
|
||||
|
||||
@@ -2,7 +2,9 @@ import { all, call } from "redux-saga/effects";
|
||||
import { appSagas } from "./app/app.sagas";
|
||||
import { photosSagas } from "./photos/photos.sagas";
|
||||
import { userSagas } from "./user/user.sagas";
|
||||
import { employeeSagas } from "./employee/employee.sagas";
|
||||
import { timeTicketsSagas } from "./timetickets/timetickets.sagas";
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield all([call(userSagas), call(appSagas), call(photosSagas)]);
|
||||
yield all([call(userSagas), call(appSagas), call(photosSagas), call(employeeSagas), call(timeTicketsSagas),]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { applyMiddleware, compose, createStore } from "redux";
|
||||
import { persistStore } from "redux-persist";
|
||||
//import { createLogger } from "redux-logger";
|
||||
import { createLogger } from "redux-logger";
|
||||
import createSagaMiddleware from "redux-saga";
|
||||
import rootReducer from "./root.reducer";
|
||||
import rootSaga from "./root.saga";
|
||||
@@ -9,11 +9,11 @@ const sagaMiddleWare = createSagaMiddleware();
|
||||
|
||||
const middlewares = [sagaMiddleWare];
|
||||
// if (process.env.NODE_ENV === "development") {
|
||||
// middlewares.push(
|
||||
// createLogger({
|
||||
// collapsed: true,
|
||||
// })
|
||||
// );
|
||||
middlewares.push(
|
||||
createLogger({
|
||||
collapsed: true,
|
||||
})
|
||||
);
|
||||
// }
|
||||
|
||||
//Add in for React Native Debugger.
|
||||
|
||||
52
redux/timetickets/timetickets.actions.js
Normal file
52
redux/timetickets/timetickets.actions.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import TimeTicketsActionTypes from "./timetickets.types";
|
||||
|
||||
export const setTimeTicket = (timeTicket) => ({
|
||||
type: TimeTicketsActionTypes.SET_TIME_TICKET,
|
||||
payload: timeTicket,
|
||||
});
|
||||
export const setTimeTicketJobId = (jobId) => ({
|
||||
type: TimeTicketsActionTypes.SET_TIME_TICKET_JOB_ID,
|
||||
payload: jobId,
|
||||
});
|
||||
export const setTimeTicketJob = (job) => ({
|
||||
type: TimeTicketsActionTypes.SET_TIME_TICKET_JOB,
|
||||
payload: job,
|
||||
});
|
||||
export const timeTicketCreateStart = (timeTicket) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CREATE_START,
|
||||
payload: timeTicket,
|
||||
});
|
||||
export const timeTicketCreateSuccess = (insertTimeTickets) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CREATE_SUCCESS,
|
||||
payload: insertTimeTickets,
|
||||
});
|
||||
export const timeTicketCreateFailure = (error) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CREATE_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
|
||||
export const timeTicketClockInStart = (timeTicket) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_START,
|
||||
payload: timeTicket,
|
||||
});
|
||||
export const timeTicketClockInSuccess = (insertTimeTickets) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_SUCCESS,
|
||||
payload: insertTimeTickets,
|
||||
});
|
||||
export const timeTicketClockInFailure = (error) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
|
||||
export const timeTicketClockOutStart = (timeTicket) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_START,
|
||||
payload: timeTicket,
|
||||
});
|
||||
export const timeTicketClockOutSuccess = (insertTimeTickets) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_SUCCESS,
|
||||
payload: insertTimeTickets,
|
||||
});
|
||||
export const timeTicketClockOutFailure = (error) => ({
|
||||
type: TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
70
redux/timetickets/timetickets.reducer.js
Normal file
70
redux/timetickets/timetickets.reducer.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import TimeTicketsActionTypes from "./timetickets.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
ttjobid:null,
|
||||
timeticketjobid: null,
|
||||
timeTicketJob: null,
|
||||
uploadTimeTicketInProgress: false,
|
||||
uploadTimeTicketError: null,
|
||||
clockingIn: false,
|
||||
clockingInError: null,
|
||||
clockingOut: false,
|
||||
clockingOutError: null,
|
||||
};
|
||||
|
||||
const timeTicketsReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case TimeTicketsActionTypes.SET_TIME_TICKET:
|
||||
return {
|
||||
...state,
|
||||
timeTicket: action.payload
|
||||
};
|
||||
case TimeTicketsActionTypes.SET_TIME_TICKET_JOB_ID:
|
||||
return { ...state,timeticketjobid: action.payload };
|
||||
case TimeTicketsActionTypes.SET_TIME_TICKET_JOB:
|
||||
return {
|
||||
...state,
|
||||
timeTicketJob: action.payload
|
||||
};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CREATE_START:
|
||||
return {...state,uploadTimeTicketInProgress: true};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CREATE_SUCCESS:
|
||||
return {...state,
|
||||
uploadTimeTicketInProgress: false,
|
||||
uploadTimeTicketError: null,
|
||||
};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CREATE_FAILURE:
|
||||
return {...state,
|
||||
uploadTimeTicketInProgress: false,
|
||||
uploadTimeTicketError: action.payload,
|
||||
};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_START:
|
||||
return {...state,clockingIn: true};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_SUCCESS:
|
||||
return {...state,
|
||||
clockingIn: false,
|
||||
clockingInError: null,
|
||||
};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_FAILURE:
|
||||
return {...state,
|
||||
clockingIn: false,
|
||||
clockingInError: action.payload,
|
||||
};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_START:
|
||||
return {...state,clockingOut: true};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_SUCCESS:
|
||||
return {...state,
|
||||
clockingOut: false,
|
||||
clockingOutError: null,
|
||||
};
|
||||
case TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_FAILURE:
|
||||
return {...state,
|
||||
clockingOut: false,
|
||||
clockingOutError: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default timeTicketsReducer;
|
||||
152
redux/timetickets/timetickets.sagas.js
Normal file
152
redux/timetickets/timetickets.sagas.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
timeTicketCreateFailure,
|
||||
timeTicketCreateSuccess,
|
||||
timeTicketClockInSuccess,
|
||||
timeTicketClockInFailure,
|
||||
timeTicketClockOutSuccess,
|
||||
timeTicketClockOutFailure,
|
||||
} from "./timetickets.actions";
|
||||
import TimeTicketsActionTypes from "./timetickets.types";
|
||||
import { client } from "../../graphql/client";
|
||||
|
||||
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
||||
import { selectCurrentTimeTicket } from "./timetickets.selectors";
|
||||
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
|
||||
export function* onCreateTimeTicketStart() {
|
||||
yield takeLatest(
|
||||
TimeTicketsActionTypes.TIME_TICKET_CREATE_START,
|
||||
insertNewTimeTicket
|
||||
);
|
||||
}
|
||||
export function* insertNewTimeTicket({ payload: { timeTicketInput } }) {
|
||||
try {
|
||||
logImEXEvent("redux_insertnewtimeticket_attempt");
|
||||
// console.log("Saga, TIME_TICKET_CREATE_START :", timeTicketInput);
|
||||
//console.loging
|
||||
// console.log("Saga", employeeId, pin, pin);
|
||||
const timeTicket = yield select(selectCurrentTimeTicket);
|
||||
// const response = yield call(axios.post, "/tech/login", {
|
||||
// shopid: bodyshop.id,
|
||||
// employeeid: employeeId,
|
||||
// pin: pin,
|
||||
// });
|
||||
// const { valid, data, error } = response.data;
|
||||
// const result = yield client.mutate({
|
||||
// mutation: INSERT_NEW_TIME_TICKET,
|
||||
// variables: {
|
||||
// timeTicketInput: [
|
||||
// // {
|
||||
// // bodyshopid: bodyshop.id,
|
||||
// // employeeid: technician.id,
|
||||
// // date: moment(theTime).format("YYYY-MM-DD"),
|
||||
// // clockon: moment(theTime),
|
||||
// // jobid: values.jobid,
|
||||
// // cost_center: values.cost_center,
|
||||
// // ciecacode:
|
||||
// // bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
// // ? values.cost_center
|
||||
// // : Object.keys(
|
||||
// // bodyshop.md_responsibility_centers.defaults.costs
|
||||
// // ).find((key) => {
|
||||
// // return (
|
||||
// // bodyshop.md_responsibility_centers.defaults.costs[key] ===
|
||||
// // values.cost_center
|
||||
// // );
|
||||
// // }),
|
||||
// // },
|
||||
// ],
|
||||
// },
|
||||
// });
|
||||
// console.log(result);
|
||||
// const { valid, data, error } = result.data;
|
||||
// if (valid) {
|
||||
// yield put(timeTicketCreateSuccess(data));
|
||||
// } else {
|
||||
// yield put(timeTicketCreateFailure(error));
|
||||
// }
|
||||
} catch (error) {
|
||||
yield put(timeTicketCreateFailure(error));
|
||||
}
|
||||
}
|
||||
|
||||
export function* onClockOutStart() {
|
||||
yield takeLatest(
|
||||
TimeTicketsActionTypes.TIME_TICKET_CLOCKOUT_START,
|
||||
clockOutStart
|
||||
);
|
||||
}
|
||||
export function* clockOutStart({ payload: { timeTicketInput } }) {
|
||||
try {
|
||||
logImEXEvent("redux_clockOutStart_attempt");
|
||||
//console.loging
|
||||
|
||||
// console.log("Saga, clockOutStart :", timeTicketInput);
|
||||
|
||||
// const timeTicket = yield select(selectCurrentTimeTicket);
|
||||
// const response = yield call(axios.post, "/tech/login", {
|
||||
// shopid: bodyshop.id,
|
||||
// employeeid: employeeId,
|
||||
// pin: pin,
|
||||
// });
|
||||
// const { valid, data, error } = response.data;
|
||||
// const result = yield client.mutate({
|
||||
// mutation: INSERT_NEW_TIME_TICKET,
|
||||
// variables: {
|
||||
// timeTicketInput: [
|
||||
// // {
|
||||
// // bodyshopid: bodyshop.id,
|
||||
// // employeeid: technician.id,
|
||||
// // date: moment(theTime).format("YYYY-MM-DD"),
|
||||
// // clockon: moment(theTime),
|
||||
// // jobid: values.jobid,
|
||||
// // cost_center: values.cost_center,
|
||||
// // ciecacode:
|
||||
// // bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
// // ? values.cost_center
|
||||
// // : Object.keys(
|
||||
// // bodyshop.md_responsibility_centers.defaults.costs
|
||||
// // ).find((key) => {
|
||||
// // return (
|
||||
// // bodyshop.md_responsibility_centers.defaults.costs[key] ===
|
||||
// // values.cost_center
|
||||
// // );
|
||||
// // }),
|
||||
// // },
|
||||
// ],
|
||||
// },
|
||||
// });
|
||||
// console.log(result);
|
||||
// const { valid, data, error } = result.data;
|
||||
// if (valid) {
|
||||
// yield put(timeTicketCreateSuccess(data));
|
||||
// } else {
|
||||
// yield put(timeTicketCreateFailure(error));
|
||||
// }
|
||||
} catch (error) {
|
||||
yield put(timeTicketClockOutFailure(error));
|
||||
}
|
||||
}
|
||||
export function* onClockInStart() {
|
||||
yield takeLatest(
|
||||
TimeTicketsActionTypes.TIME_TICKET_CLOCKIN_START,
|
||||
clockInStart
|
||||
);
|
||||
}
|
||||
export function* clockInStart({ payload: { timeTicketInput } }) {
|
||||
try {
|
||||
logImEXEvent("redux_clockInStart_attempt");
|
||||
|
||||
// console.log("Saga, clockInStart :", timeTicketInput);
|
||||
} catch (error) {
|
||||
yield put(timeTicketClockInFailure(error));
|
||||
}
|
||||
}
|
||||
export function* timeTicketsSagas() {
|
||||
yield all([
|
||||
call(onCreateTimeTicketStart),
|
||||
call(onClockOutStart),
|
||||
call(onClockInStart),
|
||||
]);
|
||||
}
|
||||
17
redux/timetickets/timetickets.selectors.js
Normal file
17
redux/timetickets/timetickets.selectors.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectTimeTicketsState = (state) => state.timeTickets;
|
||||
|
||||
export const selectCurrentTimeTicketJobId = createSelector(
|
||||
[selectTimeTicketsState],
|
||||
(timeTickets) => timeTickets.timeTicketJobId
|
||||
);
|
||||
export const selectCurrentTimeTicketJob = createSelector(
|
||||
[selectTimeTicketsState],
|
||||
(timeTickets) => timeTickets.timeTicketJob
|
||||
);
|
||||
export const selectCurrentTimeTicket = createSelector(
|
||||
[selectTimeTicketsState],
|
||||
(timeTickets) => timeTickets.timeTicket
|
||||
);
|
||||
|
||||
15
redux/timetickets/timetickets.types.js
Normal file
15
redux/timetickets/timetickets.types.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const TimeTicketsActionTypes = {
|
||||
SET_TIME_TICKET: "SET_TIME_TICKET",
|
||||
SET_TIME_TICKET_JOB: "SET_TIME_TICKET_JOB",
|
||||
SET_TIME_TICKET_JOB_ID: "SET_TIME_TICKET_JOB_ID",
|
||||
TIME_TICKET_CREATE_START: "TIME_TICKET_CREATE_START",
|
||||
TIME_TICKET_CREATE_SUCCESS: "TIME_TICKET_CREATE_SUCCESS",
|
||||
TIME_TICKET_CREATE_FAILURE: "TIME_TICKET_CREATE_FAILURE",
|
||||
TIME_TICKET_CLOCKIN_START: "TIME_TICKET_CLOCKIN_START",
|
||||
TIME_TICKET_CLOCKIN_SUCCESS: "TIME_TICKET_CLOCKIN_SUCCESS",
|
||||
TIME_TICKET_CLOCKIN_FAILURE: "TIME_TICKET_CLOCKIN_FAILURE",
|
||||
TIME_TICKET_CLOCKOUT_START: "TIME_TICKET_CLOCKOUT_START",
|
||||
TIME_TICKET_CLOCKOUT_SUCCESS: "TIME_TICKET_CLOCKOUT_SUCCESS",
|
||||
TIME_TICKET_CLOCKOUT_FAILURE: "TIME_TICKET_CLOCKOUT_FAILURE",
|
||||
};
|
||||
export default TimeTicketsActionTypes;
|
||||
@@ -31,3 +31,15 @@ export const selectSigningIn = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.signingIn
|
||||
);
|
||||
|
||||
export const selectRestrictClaimableHoursFlag = createSelector(
|
||||
[selectUser],
|
||||
(user) => {
|
||||
if (!user.bodyshop || !user.bodyshop.tt_enforce_hours_for_tech_console) {
|
||||
// console.info("selectRestrictClaimableHoursFlag returning null");
|
||||
return null;
|
||||
}
|
||||
// console.info("selectRestrictClaimableHoursFlag returning :", user.bodyshop.tt_enforce_hours_for_tech_console);
|
||||
return user.bodyshop.tt_enforce_hours_for_tech_console;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"signout": "Sign Out"
|
||||
"signout": "Sign Out",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"labels": {
|
||||
"na": "N/A"
|
||||
@@ -305,6 +306,222 @@
|
||||
"email": "Email",
|
||||
"password": "Password"
|
||||
}
|
||||
},
|
||||
"employeesignin": {
|
||||
"actions": {
|
||||
"employeesignin": "Sign In"
|
||||
},
|
||||
"errors": {
|
||||
"employeeidnotfound": "No employee ID found.",
|
||||
"wrongpin": "The pin you entered is not correct."
|
||||
},
|
||||
"fields": {
|
||||
"employeeid": "Employee ID",
|
||||
"pin": "PIN"
|
||||
},
|
||||
"titles": {
|
||||
"signin": "Employee Sign In"
|
||||
}
|
||||
},
|
||||
"timeticketbrowser": {
|
||||
"actions": {
|
||||
"refresh": "Refresh",
|
||||
"upload": "Upload",
|
||||
"ticket":"Ticket",
|
||||
"timetickets": "Time Tickets",
|
||||
"detail": "Time Ticket Details",
|
||||
"notickets": "There are no active tickets.",
|
||||
"clockin": "Clock In"
|
||||
},
|
||||
"labels": {
|
||||
"loggedinemployee": "Logged in Employee",
|
||||
"clockintojob": "Clock In",
|
||||
"nodata":"No Data"
|
||||
},
|
||||
"errors": {
|
||||
"missingvalues": "Please make sure all fields have a value."
|
||||
},
|
||||
"titles": {
|
||||
"timeticketbrowsertab": "Time Tickets"
|
||||
}
|
||||
},
|
||||
"createtimeticket": {
|
||||
"actions": {
|
||||
"refresh": "Refresh",
|
||||
"upload": "Upload",
|
||||
"ticket":"Ticket",
|
||||
"timetickets": "Time Tickets",
|
||||
"detail": "Time Ticket Details",
|
||||
"notickets": "There are no active tickets.",
|
||||
"ticketdate": "Ticket Date: ",
|
||||
"createticket": "Create Ticket"
|
||||
},
|
||||
"labels": {
|
||||
"converting": "Converting",
|
||||
"selectjob": "--- Select a ticket ---",
|
||||
"selectticketassetselector": "Please select a ticket to update. ",
|
||||
"uploading": "Uploading",
|
||||
"employeeplaceholder": "Employee",
|
||||
"actualhoursplaceholder": "Actual Hours",
|
||||
"productivehoursplaceholder": "Productive Hours"
|
||||
},
|
||||
"errors": {
|
||||
"nan": "Please make sure all fields have valid values.",
|
||||
"missingvalues": "Please make sure all fields have a value."
|
||||
},
|
||||
"titles": {
|
||||
"createtimeticket": "New Time Ticket"
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"converttolabor": "Convert amount to Labor.",
|
||||
"new": "New Line"
|
||||
},
|
||||
"errors": {
|
||||
"creating": "Error encountered while creating job line. {{message}}",
|
||||
"updating": "Error encountered updating job line. {{message}}"
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Retail Price",
|
||||
"ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)",
|
||||
"db_price": "List Price",
|
||||
"lbr_types": {
|
||||
"LA1": "LA1",
|
||||
"LA2": "LA2",
|
||||
"LA3": "LA3",
|
||||
"LA4": "LA4",
|
||||
"LAA": "Aluminum",
|
||||
"LAB": "Body",
|
||||
"LAD": "Diagnostic",
|
||||
"LAE": "Electrical",
|
||||
"LAF": "Frame",
|
||||
"LAG": "Glass",
|
||||
"LAM": "Mechanical",
|
||||
"LAR": "Refinish",
|
||||
"LAS": "Structural",
|
||||
"LAU": "User Defined"
|
||||
},
|
||||
"line_desc": "Line Desc.",
|
||||
"line_ind": "S#",
|
||||
"line_no": "Line #",
|
||||
"location": "Location",
|
||||
"mod_lb_hrs": "Hrs",
|
||||
"mod_lbr_ty": "Labor Type",
|
||||
"notes": "Notes",
|
||||
"oem_partno": "OEM Part #",
|
||||
"op_code_desc": "Op Code Description",
|
||||
"part_qty": "Qty.",
|
||||
"part_type": "Part Type",
|
||||
"part_types": {
|
||||
"CCC": "CC Cleaning",
|
||||
"CCD": "CC Damage Waiver",
|
||||
"CCDR": "CC Daily Rate",
|
||||
"CCF": "CC Refuel",
|
||||
"CCM": "CC Mileage",
|
||||
"PAA": "Aftermarket",
|
||||
"PAC": "Rechromed",
|
||||
"PAE": "Existing",
|
||||
"PAG": "Glass",
|
||||
"PAL": "LKQ",
|
||||
"PAM": "Remanufactured",
|
||||
"PAN": "New/OEM",
|
||||
"PAO": "Other",
|
||||
"PAP": "OEM Partial",
|
||||
"PAR": "Recored",
|
||||
"PAS": "Sublet",
|
||||
"PASL": "Sublet (L)"
|
||||
},
|
||||
"profitcenter_labor": "Profit Center: Labor",
|
||||
"profitcenter_part": "Profit Center: Part",
|
||||
"prt_dsmk_m": "Line Discount/Markup $",
|
||||
"prt_dsmk_p": "Line Discount/Markup %",
|
||||
"status": "Status",
|
||||
"tax_part": "Tax Part",
|
||||
"total": "Total",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
"labels": {
|
||||
"adjustmenttobeadded": "Adjustment to be added: {{adjustment}}",
|
||||
"billref": "Latest Bill",
|
||||
"convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.",
|
||||
"edit": "Edit Line",
|
||||
"ioucreated": "IOU",
|
||||
"new": "New Line",
|
||||
"nostatus": "No Status",
|
||||
"presets": "Jobline Presets"
|
||||
},
|
||||
"successes": {
|
||||
"created": "Job line created successfully.",
|
||||
"saved": "Job line saved.",
|
||||
"updated": "Job line updated successfully."
|
||||
},
|
||||
"validations": {
|
||||
"ahdetailonlyonuserdefinedtypes": "Detail line indicator can only be set for LA1, LA2, LA3, LA4, and LAU labor types.",
|
||||
"hrsrequirediflbrtyp": "Labor hours are required if a labor type is selected. Clear the labor type if there are no labor hours.",
|
||||
"requiredifparttype": "Required if a part type has been specified.",
|
||||
"zeropriceexistingpart": "This line cannot have any price since it uses an existing part."
|
||||
}
|
||||
},
|
||||
"timeticketclockoff":{
|
||||
"actions": {
|
||||
"clockoff": "Clock Off",
|
||||
"noinfo": "There is no info to display."
|
||||
},
|
||||
"labels": {
|
||||
"actualhoursplaceholder": "Actual Hours",
|
||||
"productivehoursplaceholder": "Productive Hours"
|
||||
},
|
||||
"errors": {
|
||||
"nan": "Please make sure all fields have valid values.",
|
||||
"missingvalues": "Please make sure all fields have a value.",
|
||||
"hoursenteredmorethanavailable": "The number of hours entered is more than what is available for this cost center."
|
||||
},
|
||||
"titles": {
|
||||
"clockoff": "Clock Off"
|
||||
}
|
||||
},
|
||||
"selectjobid":{
|
||||
"labels": {
|
||||
"placeholder": "RO #",
|
||||
"selectedplaceholder": "...",
|
||||
"searchplaceholder": "Search...",
|
||||
"noselection":"No Selection",
|
||||
"nodata":"No Data"
|
||||
}
|
||||
},
|
||||
"selectcostcenter":{
|
||||
"labels": {
|
||||
"placeholder": "Select Cost Center",
|
||||
"selectedplaceholder": "...",
|
||||
"searchplaceholder": "Search..."
|
||||
}
|
||||
},
|
||||
"employeeclockedinlist": {
|
||||
"labels": {
|
||||
"alreadyclockedon": "Clocked into job(s)"
|
||||
}
|
||||
},
|
||||
"clockedinlistitem": {
|
||||
"labels": {
|
||||
"vehicle": "Vehicle : ",
|
||||
"clockedin": "Clocked In : ",
|
||||
"costcenter": "Cost Center : "
|
||||
},
|
||||
"actions": {
|
||||
"clockout": "Clock Out"
|
||||
}
|
||||
},
|
||||
"laborallocations": {
|
||||
"labels": {
|
||||
"laborallocations": "Labor Allocations",
|
||||
"costcenter": "Cost Center",
|
||||
"hourstotal": "Hours Total",
|
||||
"hoursclaimed": "Hours Claimed",
|
||||
"adjustments": "Adjustments",
|
||||
"difference": "Difference",
|
||||
"totals": "Totals"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"signout": ""
|
||||
"signout": "",
|
||||
"logout": ""
|
||||
},
|
||||
"labels": {
|
||||
"na": ""
|
||||
@@ -305,6 +306,222 @@
|
||||
"email": "Email",
|
||||
"password": ""
|
||||
}
|
||||
},
|
||||
"employeesignin": {
|
||||
"actions": {
|
||||
"employeesignin": ""
|
||||
},
|
||||
"errors": {
|
||||
"employeeidnotfound": "",
|
||||
"wrongpin": ""
|
||||
},
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
"pin": ""
|
||||
},
|
||||
"titles": {
|
||||
"signin": ""
|
||||
}
|
||||
},
|
||||
"timeticketbrowser": {
|
||||
"actions": {
|
||||
"refresh": "",
|
||||
"upload": "",
|
||||
"ticket": "",
|
||||
"timetickets": "",
|
||||
"detail": "",
|
||||
"notickets": "",
|
||||
"clockin": ""
|
||||
},
|
||||
"labels": {
|
||||
"loggedinemployee": "",
|
||||
"clockintojob": "",
|
||||
"nodata":""
|
||||
},
|
||||
"errors": {
|
||||
"missingvalues": ""
|
||||
},
|
||||
"titles": {
|
||||
"timeticketbrowsertab": ""
|
||||
}
|
||||
},
|
||||
"createtimeticket": {
|
||||
"actions": {
|
||||
"refresh": "",
|
||||
"upload": " ",
|
||||
"ticket":" ",
|
||||
"timetickets": "",
|
||||
"detail": "",
|
||||
"notickets": "",
|
||||
"ticketdate": "",
|
||||
"createticket": ""
|
||||
},
|
||||
"labels": {
|
||||
"converting": "",
|
||||
"selectjob": "",
|
||||
"selectticketassetselector": "",
|
||||
"uploading": "",
|
||||
"employeeplaceholder": "",
|
||||
"actualhoursplaceholder": "",
|
||||
"productivehoursplaceholder": ""
|
||||
},
|
||||
"errors": {
|
||||
"nan": "",
|
||||
"missingvalues": ""
|
||||
},
|
||||
"titles": {
|
||||
"createtimeticket": ""
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"converttolabor": "",
|
||||
"new": ""
|
||||
},
|
||||
"errors": {
|
||||
"creating": "",
|
||||
"updating": ""
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Precio actual",
|
||||
"ah_detail_line": "",
|
||||
"db_price": "Precio de base de datos",
|
||||
"lbr_types": {
|
||||
"LA1": "",
|
||||
"LA2": "",
|
||||
"LA3": "",
|
||||
"LA4": "",
|
||||
"LAA": "",
|
||||
"LAB": "",
|
||||
"LAD": "",
|
||||
"LAE": "",
|
||||
"LAF": "",
|
||||
"LAG": "",
|
||||
"LAM": "",
|
||||
"LAR": "",
|
||||
"LAS": "",
|
||||
"LAU": ""
|
||||
},
|
||||
"line_desc": "Descripción de línea",
|
||||
"line_ind": "S#",
|
||||
"line_no": "",
|
||||
"location": "",
|
||||
"mod_lb_hrs": "Horas laborales",
|
||||
"mod_lbr_ty": "Tipo de trabajo",
|
||||
"notes": "",
|
||||
"oem_partno": "OEM parte #",
|
||||
"op_code_desc": "",
|
||||
"part_qty": "",
|
||||
"part_type": "Tipo de parte",
|
||||
"part_types": {
|
||||
"CCC": "",
|
||||
"CCD": "",
|
||||
"CCDR": "",
|
||||
"CCF": "",
|
||||
"CCM": "",
|
||||
"PAA": "",
|
||||
"PAC": "",
|
||||
"PAE": "",
|
||||
"PAG": "",
|
||||
"PAL": "",
|
||||
"PAM": "",
|
||||
"PAN": "",
|
||||
"PAO": "",
|
||||
"PAP": "",
|
||||
"PAR": "",
|
||||
"PAS": "",
|
||||
"PASL": ""
|
||||
},
|
||||
"profitcenter_labor": "",
|
||||
"profitcenter_part": "",
|
||||
"prt_dsmk_m": "",
|
||||
"prt_dsmk_p": "",
|
||||
"status": "Estado",
|
||||
"tax_part": "",
|
||||
"total": "",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
"labels": {
|
||||
"adjustmenttobeadded": "",
|
||||
"billref": "",
|
||||
"convertedtolabor": "",
|
||||
"edit": "Línea de edición",
|
||||
"ioucreated": "",
|
||||
"new": "Nueva línea",
|
||||
"nostatus": "",
|
||||
"presets": ""
|
||||
},
|
||||
"successes": {
|
||||
"created": "",
|
||||
"saved": "",
|
||||
"updated": ""
|
||||
},
|
||||
"validations": {
|
||||
"ahdetailonlyonuserdefinedtypes": "",
|
||||
"hrsrequirediflbrtyp": "",
|
||||
"requiredifparttype": "",
|
||||
"zeropriceexistingpart": ""
|
||||
}
|
||||
},
|
||||
"timeticketclockoff":{
|
||||
"actions": {
|
||||
"clockoff": "",
|
||||
"noinfo": ""
|
||||
},
|
||||
"labels": {
|
||||
"actualhoursplaceholder": "",
|
||||
"productivehoursplaceholder": ""
|
||||
},
|
||||
"errors": {
|
||||
"nan": "",
|
||||
"missingvalues": "",
|
||||
"hoursenteredmorethanavailable": ""
|
||||
},
|
||||
"titles": {
|
||||
"clockoff": ""
|
||||
}
|
||||
},
|
||||
"selectjobid":{
|
||||
"labels": {
|
||||
"placeholder": "",
|
||||
"selectedplaceholder": "...",
|
||||
"searchplaceholder": "",
|
||||
"noselection":"",
|
||||
"nodata":""
|
||||
}
|
||||
},
|
||||
"selectcostcenter":{
|
||||
"labels": {
|
||||
"placeholder": "",
|
||||
"selectedplaceholder": "...",
|
||||
"searchplaceholder": ""
|
||||
}
|
||||
},
|
||||
"employeeclockedinlist": {
|
||||
"labels": {
|
||||
"alreadyclockedon": ""
|
||||
}
|
||||
},
|
||||
"clockedinlistitem": {
|
||||
"labels": {
|
||||
"vehicle": "",
|
||||
"clockedin": "",
|
||||
"costcenter": ""
|
||||
},
|
||||
"actions": {
|
||||
"clockout": ""
|
||||
}
|
||||
},
|
||||
"laborallocations": {
|
||||
"labels": {
|
||||
"laborallocations": "",
|
||||
"costcenter": "",
|
||||
"hourstotal": "",
|
||||
"hoursclaimed": "",
|
||||
"adjustments": "",
|
||||
"difference": "",
|
||||
"totals": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"signout": ""
|
||||
"signout": "",
|
||||
"logout": ""
|
||||
},
|
||||
"labels": {
|
||||
"na": ""
|
||||
@@ -305,6 +306,222 @@
|
||||
"email": "Email",
|
||||
"password": ""
|
||||
}
|
||||
},
|
||||
"employeesignin": {
|
||||
"actions": {
|
||||
"employeesignin": ""
|
||||
},
|
||||
"errors": {
|
||||
"employeeidnotfound": "",
|
||||
"wrongpin": ""
|
||||
},
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
"pin": ""
|
||||
},
|
||||
"titles": {
|
||||
"signin": ""
|
||||
}
|
||||
},
|
||||
"timeticketbrowser": {
|
||||
"actions": {
|
||||
"refresh": "",
|
||||
"upload": "",
|
||||
"ticket": "",
|
||||
"timetickets": "",
|
||||
"detail": "",
|
||||
"notickets": "",
|
||||
"clockin": ""
|
||||
},
|
||||
"labels": {
|
||||
"loggedinemployee": "",
|
||||
"clockintojob": "",
|
||||
"nodata":""
|
||||
},
|
||||
"errors": {
|
||||
"missingvalues": ""
|
||||
},
|
||||
"titles": {
|
||||
"timeticketbrowsertab": ""
|
||||
}
|
||||
},
|
||||
"createtimeticket": {
|
||||
"actions": {
|
||||
"refresh": "",
|
||||
"upload": " ",
|
||||
"ticket":" ",
|
||||
"timetickets": "",
|
||||
"detail": "",
|
||||
"notickets": "",
|
||||
"ticketdate": "",
|
||||
"createticket": ""
|
||||
},
|
||||
"labels": {
|
||||
"converting": "",
|
||||
"selectjob": "",
|
||||
"selectticketassetselector": "",
|
||||
"uploading": "",
|
||||
"employeeplaceholder": "",
|
||||
"actualhoursplaceholder": "",
|
||||
"productivehoursplaceholder": ""
|
||||
},
|
||||
"errors": {
|
||||
"nan": "",
|
||||
"missingvalues": ""
|
||||
},
|
||||
"titles": {
|
||||
"createtimeticket": ""
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"converttolabor": "",
|
||||
"new": ""
|
||||
},
|
||||
"errors": {
|
||||
"creating": "",
|
||||
"updating": ""
|
||||
},
|
||||
"fields": {
|
||||
"act_price": "Prix actuel",
|
||||
"ah_detail_line": "",
|
||||
"db_price": "Prix de la base de données",
|
||||
"lbr_types": {
|
||||
"LA1": "",
|
||||
"LA2": "",
|
||||
"LA3": "",
|
||||
"LA4": "",
|
||||
"LAA": "",
|
||||
"LAB": "",
|
||||
"LAD": "",
|
||||
"LAE": "",
|
||||
"LAF": "",
|
||||
"LAG": "",
|
||||
"LAM": "",
|
||||
"LAR": "",
|
||||
"LAS": "",
|
||||
"LAU": ""
|
||||
},
|
||||
"line_desc": "Description de la ligne",
|
||||
"line_ind": "S#",
|
||||
"line_no": "",
|
||||
"location": "",
|
||||
"mod_lb_hrs": "Heures de travail",
|
||||
"mod_lbr_ty": "Type de travail",
|
||||
"notes": "",
|
||||
"oem_partno": "Pièce OEM #",
|
||||
"op_code_desc": "",
|
||||
"part_qty": "",
|
||||
"part_type": "Type de pièce",
|
||||
"part_types": {
|
||||
"CCC": "",
|
||||
"CCD": "",
|
||||
"CCDR": "",
|
||||
"CCF": "",
|
||||
"CCM": "",
|
||||
"PAA": "",
|
||||
"PAC": "",
|
||||
"PAE": "",
|
||||
"PAG": "",
|
||||
"PAL": "",
|
||||
"PAM": "",
|
||||
"PAN": "",
|
||||
"PAO": "",
|
||||
"PAP": "",
|
||||
"PAR": "",
|
||||
"PAS": "",
|
||||
"PASL": ""
|
||||
},
|
||||
"profitcenter_labor": "",
|
||||
"profitcenter_part": "",
|
||||
"prt_dsmk_m": "",
|
||||
"prt_dsmk_p": "",
|
||||
"status": "Statut",
|
||||
"tax_part": "",
|
||||
"total": "",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
"labels": {
|
||||
"adjustmenttobeadded": "",
|
||||
"billref": "",
|
||||
"convertedtolabor": "",
|
||||
"edit": "Ligne d'édition",
|
||||
"ioucreated": "",
|
||||
"new": "Nouvelle ligne",
|
||||
"nostatus": "",
|
||||
"presets": ""
|
||||
},
|
||||
"successes": {
|
||||
"created": "",
|
||||
"saved": "",
|
||||
"updated": ""
|
||||
},
|
||||
"validations": {
|
||||
"ahdetailonlyonuserdefinedtypes": "",
|
||||
"hrsrequirediflbrtyp": "",
|
||||
"requiredifparttype": "",
|
||||
"zeropriceexistingpart": ""
|
||||
}
|
||||
},
|
||||
"timeticketclockoff":{
|
||||
"actions": {
|
||||
"clockoff": "",
|
||||
"noinfo": ""
|
||||
},
|
||||
"labels": {
|
||||
"actualhoursplaceholder": "",
|
||||
"productivehoursplaceholder": ""
|
||||
},
|
||||
"errors": {
|
||||
"nan": "",
|
||||
"missingvalues": "",
|
||||
"hoursenteredmorethanavailable": ""
|
||||
},
|
||||
"titles": {
|
||||
"clockoff": ""
|
||||
}
|
||||
},
|
||||
"selectjobid":{
|
||||
"labels": {
|
||||
"placeholder": "",
|
||||
"selectedplaceholder": "...",
|
||||
"searchplaceholder": "",
|
||||
"noselection":"",
|
||||
"nodata":""
|
||||
}
|
||||
},
|
||||
"selectcostcenter":{
|
||||
"labels": {
|
||||
"placeholder": "",
|
||||
"selectedplaceholder": "...",
|
||||
"searchplaceholder": ""
|
||||
}
|
||||
},
|
||||
"employeeclockedinlist": {
|
||||
"labels": {
|
||||
"alreadyclockedon": ""
|
||||
}
|
||||
},
|
||||
"clockedinlistitem": {
|
||||
"labels": {
|
||||
"vehicle": "",
|
||||
"clockedin": "",
|
||||
"costcenter": ""
|
||||
},
|
||||
"actions": {
|
||||
"clockout": ""
|
||||
}
|
||||
},
|
||||
"laborallocations": {
|
||||
"labels": {
|
||||
"laborallocations": "",
|
||||
"costcenter": "",
|
||||
"hourstotal": "",
|
||||
"hoursclaimed": "",
|
||||
"adjustments": "",
|
||||
"difference": "",
|
||||
"totals": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from "axios";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
axios.defaults.baseURL =
|
||||
process.env.REACT_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
|
||||
}
|
||||
import env from "../env";
|
||||
//if (process.env.NODE_ENV === "production") {
|
||||
axios.defaults.baseURL = "https://api.test.imex.online/";
|
||||
//env.REACT_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
|
||||
//}
|
||||
|
||||
export const axiosAuthInterceptorId = axios.interceptors.request.use(
|
||||
async (config) => {
|
||||
|
||||
24
util/DateFormater.jsx
Normal file
24
util/DateFormater.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
|
||||
export function DateFormatter(props) {
|
||||
return props.children
|
||||
? moment(props.children).format(
|
||||
props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY"
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
export function DateTimeFormatter(props) {
|
||||
return props.children
|
||||
? moment(props.children).format(
|
||||
props.format ? props.format : "MM/DD/YYYY hh:mm a"
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
export function TimeFormatter(props) {
|
||||
return props.children
|
||||
? moment(props.children).format(props.format ? props.format : "hh:mm a")
|
||||
: null;
|
||||
}
|
||||
54
util/labor-allocations-table.utility.js
Normal file
54
util/labor-allocations-table.utility.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
|
||||
export const CalculateAllocationsTotals = (
|
||||
bodyshop,
|
||||
joblines,
|
||||
timetickets,
|
||||
adjustments = []
|
||||
) => {
|
||||
// console.log("🚀 ~ file: adjustments", adjustments);
|
||||
// console.log("🚀 ~ file: bodyshop", bodyshop);
|
||||
// console.log("🚀 ~ file: joblines", joblines);
|
||||
// console.log("🚀 ~ file: timetickets", timetickets);
|
||||
const responsibilitycenters = bodyshop.md_responsibility_centers;
|
||||
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
|
||||
// console.log("jobCodes :", jobCodes);
|
||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||
const ticketCodes = timetickets.map((item) => item.ciecacode);
|
||||
// console.log("ticketCodes :", ticketCodes);
|
||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||
const adjustmentCodes = Object.keys(adjustments);
|
||||
// console.log("adjustmentCodes :", adjustmentCodes);
|
||||
const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter(
|
||||
(value, index, self) => self.indexOf(value) === index && !!value
|
||||
);
|
||||
// console.log("allCodes :", allCodes);
|
||||
|
||||
const r = allCodes.reduce((acc, value) => {
|
||||
const r = {
|
||||
opcode: value,
|
||||
cost_center:
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
? i18next.t(
|
||||
`joblines.fields.lbr_types.${value && value.toUpperCase()}`
|
||||
)
|
||||
: responsibilitycenters.defaults.costs[value],
|
||||
mod_lbr_ty: value,
|
||||
total: joblines.reduce((acc2, val2) => {
|
||||
return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2;
|
||||
}, 0),
|
||||
adjustments: adjustments[value] || 0,
|
||||
claimed: timetickets.reduce((acc3, val3) => {
|
||||
return val3.ciecacode === value ? acc3 + val3.productivehrs : acc3;
|
||||
}, 0),
|
||||
};
|
||||
|
||||
r.difference = r.total + r.adjustments - r.claimed;
|
||||
acc.push(r);
|
||||
return acc;
|
||||
}, []);
|
||||
//console.log(" r is :", r);
|
||||
|
||||
return r;
|
||||
};
|
||||
91
util/timer.js
Normal file
91
util/timer.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import React from "react";
|
||||
import { View, StyleSheet, Text } from "react-native";
|
||||
import { Button, TextInput } from "react-native-paper";
|
||||
|
||||
export default function Timer() {
|
||||
const [time, setTime] = useState(300000);
|
||||
const [timerOn, setTimerOn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let interval = null;
|
||||
|
||||
if (timerOn) {
|
||||
interval = setInterval(() => {
|
||||
setTime((prevTime) => prevTime - 1000);
|
||||
}, 10);
|
||||
if (time == 0) {
|
||||
//triger logout();
|
||||
}
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
// if (counter > 0) {
|
||||
// setTimeout(() => setCounter(counter - 1), 1000);
|
||||
// } else {
|
||||
// //triger logout();
|
||||
// }
|
||||
}, [timerOn]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
<Text>{time}</Text>
|
||||
</View>
|
||||
<Button mode="outlined" onPress={start}>
|
||||
<Text>{start}</Text>
|
||||
</Button>
|
||||
<Button mode="outlined" onPress={reset}>
|
||||
<Text>{reset}</Text>
|
||||
</Button>
|
||||
<Button mode="outlined" onPress={stop}>
|
||||
<Text>{stop}</Text>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function start() {
|
||||
setTimerOn(true);
|
||||
}
|
||||
function stop() {
|
||||
setTimerOn(false);
|
||||
}
|
||||
function reset() {
|
||||
setTime(300000);
|
||||
}
|
||||
|
||||
|
||||
// The data/time we want to countdown to
|
||||
// var countDownDate = new Date(moment.).getTime();
|
||||
// // Run myfunc every second
|
||||
// var myfunc = setInterval(function() {
|
||||
|
||||
// var now = new Date().getTime();
|
||||
// var timeleft = countDownDate - now;
|
||||
|
||||
// // Calculating the days, hours, minutes and seconds left
|
||||
// var days = Math.floor(timeleft / (1000 * 60 * 60 * 24));
|
||||
// var hours = Math.floor((timeleft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
// var minutes = Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60));
|
||||
// var seconds = Math.floor((timeleft % (1000 * 60)) / 1000);
|
||||
|
||||
// // Result is output to the specific element
|
||||
// document.getElementById("days").innerHTML = days + "d "
|
||||
// document.getElementById("hours").innerHTML = hours + "h "
|
||||
// document.getElementById("mins").innerHTML = minutes + "m "
|
||||
// document.getElementById("secs").innerHTML = seconds + "s "
|
||||
|
||||
// // Display the message when countdown is over
|
||||
// if (timeleft < 0) {
|
||||
// clearInterval(myfunc);
|
||||
// document.getElementById("days").innerHTML = ""
|
||||
// document.getElementById("hours").innerHTML = ""
|
||||
// document.getElementById("mins").innerHTML = ""
|
||||
// document.getElementById("secs").innerHTML = ""
|
||||
// document.getElementById("end").innerHTML = "TIME UP!!";
|
||||
// }
|
||||
// }, 1000);
|
||||
143
yarn.lock
143
yarn.lock
@@ -2233,7 +2233,7 @@
|
||||
dependencies:
|
||||
serve-static "^1.13.1"
|
||||
|
||||
"@react-native-community/cli-doctor@^10.2.0":
|
||||
"@react-native-community/cli-doctor@^10.2.2":
|
||||
version "10.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-10.2.2.tgz#b1893604fa9fc8971064e7c00042350f96868bfe"
|
||||
integrity sha512-49Ep2aQOF0PkbAR/TcyMjOm9XwBa8VQr+/Zzf4SJeYwiYLCT1NZRAVAVjYRXl0xqvq5S5mAGZZShS4AQl4WsZw==
|
||||
@@ -2277,19 +2277,7 @@
|
||||
glob "^7.1.3"
|
||||
logkitty "^0.7.1"
|
||||
|
||||
"@react-native-community/cli-platform-ios@10.2.0":
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.0.tgz#be21c0e3bbf17358d540cc23e5556bf679f6322e"
|
||||
integrity sha512-hIPK3iL/mL+0ChXmQ9uqqzNOKA48H+TAzg+hrxQLll/6dNMxDeK9/wZpktcsh8w+CyhqzKqVernGcQs7tPeKGw==
|
||||
dependencies:
|
||||
"@react-native-community/cli-tools" "^10.1.1"
|
||||
chalk "^4.1.2"
|
||||
execa "^1.0.0"
|
||||
fast-xml-parser "^4.0.12"
|
||||
glob "^7.1.3"
|
||||
ora "^5.4.1"
|
||||
|
||||
"@react-native-community/cli-platform-ios@^10.2.1":
|
||||
"@react-native-community/cli-platform-ios@10.2.1", "@react-native-community/cli-platform-ios@^10.2.1":
|
||||
version "10.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.1.tgz#2e6bd2cb6d48cbb8720d7b7265bb1bab80745f72"
|
||||
integrity sha512-hz4zu4Y6eyj7D0lnZx8Mf2c2si8y+zh/zUTgCTaPPLzQD8jSZNNBtUUiA1cARm2razpe8marCZ1QbTMAGbf3mg==
|
||||
@@ -2301,7 +2289,7 @@
|
||||
glob "^7.1.3"
|
||||
ora "^5.4.1"
|
||||
|
||||
"@react-native-community/cli-plugin-metro@^10.2.0":
|
||||
"@react-native-community/cli-plugin-metro@^10.2.2":
|
||||
version "10.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-10.2.2.tgz#766914e3c8007dfe52b253544c4f6cd8549919ac"
|
||||
integrity sha512-sTGjZlD3OGqbF9v1ajwUIXhGmjw9NyJ/14Lo0sg7xH8Pv4qUd5ZvQ6+DWYrQn3IKFUMfGFWYyL81ovLuPylrpw==
|
||||
@@ -2355,17 +2343,17 @@
|
||||
dependencies:
|
||||
joi "^17.2.1"
|
||||
|
||||
"@react-native-community/cli@10.2.0":
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-10.2.0.tgz#bcb65bb3dcb03b0fc4e49619d51e12d23396b301"
|
||||
integrity sha512-QH7AFBz5FX2zTZRH/o3XehHrZ0aZZEL5Sh+23nSEFgSj3bLFfvjjZhuoiRSAo7iiBdvAoXrfxQ8TXgg4Xf/7fw==
|
||||
"@react-native-community/cli@10.2.2":
|
||||
version "10.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-10.2.2.tgz#3fa438ba7f19f83e07bc337765fc1cabdcf2cac2"
|
||||
integrity sha512-aZVcVIqj+OG6CrliR/Yn8wHxrvyzbFBY9cj7n0MvRw/P54QUru2nNqUTSSbqv0Qaa297yHJbe6kFDojDMSTM8Q==
|
||||
dependencies:
|
||||
"@react-native-community/cli-clean" "^10.1.1"
|
||||
"@react-native-community/cli-config" "^10.1.1"
|
||||
"@react-native-community/cli-debugger-ui" "^10.0.0"
|
||||
"@react-native-community/cli-doctor" "^10.2.0"
|
||||
"@react-native-community/cli-doctor" "^10.2.2"
|
||||
"@react-native-community/cli-hermes" "^10.2.0"
|
||||
"@react-native-community/cli-plugin-metro" "^10.2.0"
|
||||
"@react-native-community/cli-plugin-metro" "^10.2.2"
|
||||
"@react-native-community/cli-server-api" "^10.1.1"
|
||||
"@react-native-community/cli-tools" "^10.1.1"
|
||||
"@react-native-community/cli-types" "^10.0.0"
|
||||
@@ -2378,6 +2366,13 @@
|
||||
prompts "^2.4.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
"@react-native-community/datetimepicker@6.7.3":
|
||||
version "6.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/datetimepicker/-/datetimepicker-6.7.3.tgz#e6d75a42729265d8404d1d668c86926564abca2f"
|
||||
integrity sha512-fXWbEdHMLW/e8cts3snEsbOTbnFXfUHeO2pkiDFX3fWpFoDtUrRWvn50xbY13IJUUKHDhoJ+mj24nMRVIXfX1A==
|
||||
dependencies:
|
||||
invariant "^2.2.4"
|
||||
|
||||
"@react-native-community/masked-view@^0.1.11":
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.11.tgz#2f4c6e10bee0786abff4604e39a37ded6f3980ce"
|
||||
@@ -4777,10 +4772,10 @@ expo-updates-interface@~0.9.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-0.9.1.tgz#e81308d551ed5a4c35c8770ac61434f6ca749610"
|
||||
integrity sha512-wk88LLhseQ7LJvxdN7BTKiryyqALxnrvr+lyHK3/prg76Yy0EGi2Q/oE/rtFyyZ1JmQDRbO/5pdX0EE6QqVQXQ==
|
||||
|
||||
expo-updates@^0.16.3:
|
||||
version "0.16.3"
|
||||
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.16.3.tgz#190f5896f98db2e130b608d61c8359ee4b2c2125"
|
||||
integrity sha512-uFr2Fvq7IbKwz9xEqWE9GNEs0sBAd6uiUI9McTCKw4BzKhjylRbPAN3zewc7MGLOvhTwBASva79VLQVgzdoBRw==
|
||||
expo-updates@^0.16.4:
|
||||
version "0.16.4"
|
||||
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.16.4.tgz#6d05438cf7304add03645a598211ac4ef3cc4f64"
|
||||
integrity sha512-hEUotP10sBiYn6dvkYC2rIa+kAmsBuaMp32sIVNAYEwKMQJqEwqNAKTU6CpJ4Aoc//BYL2Hv8qNo/UsT4rATRg==
|
||||
dependencies:
|
||||
"@expo/code-signing-certificates" "0.0.5"
|
||||
"@expo/config" "~8.0.0"
|
||||
@@ -6604,16 +6599,6 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
metro-babel-transformer@0.73.8:
|
||||
version "0.73.8"
|
||||
resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.73.8.tgz#521374cb9234ba126f3f8d63588db5901308b4ed"
|
||||
integrity sha512-GO6H/W2RjZ0/gm1pIvdO9EP34s3XN6kzoeyxqmfqKfYhJmYZf1SzXbyiIHyMbJNwJVrsKuHqu32+GopTlKscWw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.20.0"
|
||||
hermes-parser "0.8.0"
|
||||
metro-source-map "0.73.8"
|
||||
nullthrows "^1.1.1"
|
||||
|
||||
metro-babel-transformer@0.73.9:
|
||||
version "0.73.9"
|
||||
resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.73.9.tgz#bec8aaaf1bbdc2e469fde586fde455f8b2a83073"
|
||||
@@ -6795,19 +6780,6 @@ metro-react-native-babel-preset@0.73.9:
|
||||
"@babel/template" "^7.0.0"
|
||||
react-refresh "^0.4.0"
|
||||
|
||||
metro-react-native-babel-transformer@0.73.8:
|
||||
version "0.73.8"
|
||||
resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.73.8.tgz#cbcd4b243216878431dc4311ce46f02a928e3991"
|
||||
integrity sha512-oH/LCCJPauteAE28c0KJAiSrkV+1VJbU0PwA9UwaWnle+qevs/clpKQ8LrIr33YbBj4CiI1kFoVRuNRt5h4NFg==
|
||||
dependencies:
|
||||
"@babel/core" "^7.20.0"
|
||||
babel-preset-fbjs "^3.4.0"
|
||||
hermes-parser "0.8.0"
|
||||
metro-babel-transformer "0.73.8"
|
||||
metro-react-native-babel-preset "0.73.8"
|
||||
metro-source-map "0.73.8"
|
||||
nullthrows "^1.1.1"
|
||||
|
||||
metro-react-native-babel-transformer@0.73.9:
|
||||
version "0.73.9"
|
||||
resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.73.9.tgz#4f4f0cfa5119bab8b53e722fabaf90687d0cbff0"
|
||||
@@ -6828,14 +6800,6 @@ metro-resolver@0.73.9:
|
||||
dependencies:
|
||||
absolute-path "^0.0.0"
|
||||
|
||||
metro-runtime@0.73.8:
|
||||
version "0.73.8"
|
||||
resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.73.8.tgz#dadae7c154fbbde24390cf7f7e7d934a2768cd18"
|
||||
integrity sha512-M+Bg9M4EN5AEpJ8NkiUsawD75ifYvYfHi05w6QzHXaqOrsTeaRbbeLuOGCYxU2f/tPg17wQV97/rqUQzs9qEtA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
react-refresh "^0.4.0"
|
||||
|
||||
metro-runtime@0.73.9:
|
||||
version "0.73.9"
|
||||
resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.73.9.tgz#0b24c0b066b8629ee855a6e5035b65061fef60d5"
|
||||
@@ -6844,20 +6808,6 @@ metro-runtime@0.73.9:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
react-refresh "^0.4.0"
|
||||
|
||||
metro-source-map@0.73.8:
|
||||
version "0.73.8"
|
||||
resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.73.8.tgz#5134174e3d43de26ad331b95f637944c6547d441"
|
||||
integrity sha512-wozFXuBYMAy7b8BCYwC+qoXsvayVJBHWtSTlSLva99t+CoUSG9JO9kg1umzbOz28YYPxKmvb/wbnLMkHdas2cA==
|
||||
dependencies:
|
||||
"@babel/traverse" "^7.20.0"
|
||||
"@babel/types" "^7.20.0"
|
||||
invariant "^2.2.4"
|
||||
metro-symbolicate "0.73.8"
|
||||
nullthrows "^1.1.1"
|
||||
ob1 "0.73.8"
|
||||
source-map "^0.5.6"
|
||||
vlq "^1.0.0"
|
||||
|
||||
metro-source-map@0.73.9:
|
||||
version "0.73.9"
|
||||
resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.73.9.tgz#89ca41f6346aeb12f7f23496fa363e520adafebe"
|
||||
@@ -6872,18 +6822,6 @@ metro-source-map@0.73.9:
|
||||
source-map "^0.5.6"
|
||||
vlq "^1.0.0"
|
||||
|
||||
metro-symbolicate@0.73.8:
|
||||
version "0.73.8"
|
||||
resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.73.8.tgz#96920f607bce484283d822ee5fe18d932f69c03d"
|
||||
integrity sha512-xkBAcceYYp0GGdCCuMzkCF1ejHsd0lYlbKBkjSRgM0Nlj80VapPaSwumYoAvSaDxcbkvS7/sCjURGp5DsSFgRQ==
|
||||
dependencies:
|
||||
invariant "^2.2.4"
|
||||
metro-source-map "0.73.8"
|
||||
nullthrows "^1.1.1"
|
||||
source-map "^0.5.6"
|
||||
through2 "^2.0.1"
|
||||
vlq "^1.0.0"
|
||||
|
||||
metro-symbolicate@0.73.9:
|
||||
version "0.73.9"
|
||||
resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.73.9.tgz#cb452299a36e5b86b2826e7426d51221635c48bf"
|
||||
@@ -7326,11 +7264,6 @@ number-is-nan@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
|
||||
|
||||
ob1@0.73.8:
|
||||
version "0.73.8"
|
||||
resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.73.8.tgz#c569f1a15ce2d04da6fd70293ad44b5a93b11978"
|
||||
integrity sha512-1F7j+jzD+edS6ohQP7Vg5f3yiIk5i3x1uLrNIHOmLHWzWK1t3zrDpjnoXghccdVlsU+UjbyURnDynm4p0GgXeA==
|
||||
|
||||
ob1@0.73.9:
|
||||
version "0.73.9"
|
||||
resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.73.9.tgz#d5677a0dd3e2f16ad84231278d79424436c38c59"
|
||||
@@ -8011,6 +7944,13 @@ react-native-draggable-flatlist@^3.1.2:
|
||||
resolved "https://registry.yarnpkg.com/react-native-draggable-flatlist/-/react-native-draggable-flatlist-3.1.2.tgz#b66538007f645ccd851c729d4b8a8f7e07666d3f"
|
||||
integrity sha512-cqBR+lZktTqHY/J7V0aq+TZNkuqeILxwZ5bBXKndokwH4qvZl7ifijM7LlfWhfhU1/pzjketTsX7P2zyojfAaQ==
|
||||
|
||||
react-native-element-dropdown@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-element-dropdown/-/react-native-element-dropdown-2.9.0.tgz#c218ab512bd8be69fa0ac70462293863f8cfd527"
|
||||
integrity sha512-2Y/bmoFPTdKOUXbYloEKpMTWYE2VHrvva/5tgEuHqAlfAtGGr3r/ob0L0YYH1am5N0n1PKo3+eLf0c79QUSzYw==
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
react-native-gesture-handler@^2.4.0, react-native-gesture-handler@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz#2f63812e523c646f25b9ad660fc6f75948e51241"
|
||||
@@ -8022,7 +7962,7 @@ react-native-gesture-handler@^2.4.0, react-native-gesture-handler@^2.9.0:
|
||||
lodash "^4.17.21"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-native-gradle-plugin@^0.71.16:
|
||||
react-native-gradle-plugin@^0.71.17:
|
||||
version "0.71.17"
|
||||
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.17.tgz#cf780a27270f0a32dca8184eff91555d7627dd00"
|
||||
integrity sha512-OXXYgpISEqERwjSlaCiaQY6cTY5CH6j73gdkWpK0hedxtiWMWgH+i5TOi4hIGYitm9kQBeyDu+wim9fA8ROFJA==
|
||||
@@ -8053,6 +7993,13 @@ react-native-iphone-x-helper@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
|
||||
|
||||
react-native-modal-datetime-picker@^15.0.0:
|
||||
version "15.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-15.0.0.tgz#3c2b0a63467a3391dbc202871aa2807bc1a0d8d0"
|
||||
integrity sha512-cHeFEYHUhyIk+Mt9C6RVseg/VMGR4XcxdU9SibF5RMCXiXhrwMkFy7203xg1S331pzCF/Oqhvi4Jh0pYMrTFtQ==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-native-pager-view@6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.2.tgz#3522079b9a9d6634ca5e8d153bc0b4d660254552"
|
||||
@@ -8139,15 +8086,15 @@ react-native-web@~0.18.9:
|
||||
postcss-value-parser "^4.2.0"
|
||||
styleq "^0.1.2"
|
||||
|
||||
react-native@^0.71.4:
|
||||
version "0.71.4"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.4.tgz#f03f600efe68f745d19454ab17f9c1a9ef304790"
|
||||
integrity sha512-3hSYqvWrOdKhpV3HpEKp1/CkWx8Sr/N/miCrmUIAsVTSJUR7JW0VvIsrV9urDhUj/s6v2WF4n7qIEEJsmTCrPw==
|
||||
react-native@0.71.6:
|
||||
version "0.71.6"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.6.tgz#e8f07baf55abd1015eaa7040ceaa4aa632c2c04f"
|
||||
integrity sha512-gHrDj7qaAaiE41JwaFCh3AtvOqOLuRgZtHKzNiwxakG/wvPAYmG73ECfWHGxjxIx/QT17Hp37Da3ipCei/CayQ==
|
||||
dependencies:
|
||||
"@jest/create-cache-key-function" "^29.2.1"
|
||||
"@react-native-community/cli" "10.2.0"
|
||||
"@react-native-community/cli" "10.2.2"
|
||||
"@react-native-community/cli-platform-android" "10.2.0"
|
||||
"@react-native-community/cli-platform-ios" "10.2.0"
|
||||
"@react-native-community/cli-platform-ios" "10.2.1"
|
||||
"@react-native/assets" "1.0.0"
|
||||
"@react-native/normalize-color" "2.1.0"
|
||||
"@react-native/polyfills" "2.0.0"
|
||||
@@ -8160,16 +8107,16 @@ react-native@^0.71.4:
|
||||
jest-environment-node "^29.2.1"
|
||||
jsc-android "^250231.0.0"
|
||||
memoize-one "^5.0.0"
|
||||
metro-react-native-babel-transformer "0.73.8"
|
||||
metro-runtime "0.73.8"
|
||||
metro-source-map "0.73.8"
|
||||
metro-react-native-babel-transformer "0.73.9"
|
||||
metro-runtime "0.73.9"
|
||||
metro-source-map "0.73.9"
|
||||
mkdirp "^0.5.1"
|
||||
nullthrows "^1.1.1"
|
||||
pretty-format "^26.5.2"
|
||||
promise "^8.3.0"
|
||||
react-devtools-core "^4.26.1"
|
||||
react-native-codegen "^0.71.5"
|
||||
react-native-gradle-plugin "^0.71.16"
|
||||
react-native-gradle-plugin "^0.71.17"
|
||||
react-refresh "^0.4.0"
|
||||
react-shallow-renderer "^16.15.0"
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
Reference in New Issue
Block a user