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 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 }) {
|
export default function ErrorDisplay({ errorMessage }) {
|
||||||
return (
|
return (
|
||||||
<View style={{ backgroundColor: "red" }}>
|
<View >
|
||||||
<Text>{errorMessage}</Text>
|
<Title style={localStyles.alert}>{errorMessage}</Title>
|
||||||
|
{/* <Text>{errorMessage}</Text> */}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const localStyles = StyleSheet.create({
|
||||||
|
alert: {
|
||||||
|
color: "red",
|
||||||
|
textAlign: "center",
|
||||||
|
margin: 8,
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const mapStateToProps = createStructuredSelector({});
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||||
|
// setTmTicketJobId:(id) => dispatch(setTmTicketJobId(id)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
||||||
@@ -71,6 +72,7 @@ export function JobListItem({ setCameraJob, setCameraJobId, item }) {
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
logImEXEvent("imexmobile_setcamerajobid_row");
|
logImEXEvent("imexmobile_setcamerajobid_row");
|
||||||
setCameraJobId(item.id);
|
setCameraJobId(item.id);
|
||||||
|
// setTmTicketJobId(item.id);
|
||||||
setCameraJob(item);
|
setCameraJob(item);
|
||||||
navigation.navigate("MediaBrowserTab");
|
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 React, { useEffect, useRef } from "react";
|
||||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
|
||||||
import { NavigationContainer } from "@react-navigation/native";
|
|
||||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useEffect } from "react";
|
import { AppState } from "react-native";
|
||||||
import { Button } from "react-native-paper";
|
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 { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.analytics";
|
|
||||||
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
import { setCameraJob, setCameraJobId } from "../../redux/app/app.actions";
|
||||||
import {
|
import {
|
||||||
checkUserSession,
|
checkUserSession,
|
||||||
@@ -19,6 +22,9 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} 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 ScreenJobDetail from "../screen-job-detail/screen-job-detail.component";
|
||||||
import ScreenJobList from "../screen-job-list/screen-job-list.component";
|
import ScreenJobList from "../screen-job-list/screen-job-list.component";
|
||||||
import ScreenMediaBrowser from "../screen-media-browser/screen-media-browser.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 ScreenSignIn from "../screen-sign-in/screen-sign-in.component";
|
||||||
import ScreenSplash from "../screen-splash/screen-splash.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 ActiveJobStack = createNativeStackNavigator();
|
||||||
const MoreStack = createNativeStackNavigator();
|
const MoreStack = createNativeStackNavigator();
|
||||||
const BottomTabs = createBottomTabNavigator();
|
const BottomTabs = createBottomTabNavigator();
|
||||||
const MediaBrowserStack = createNativeStackNavigator();
|
const MediaBrowserStack = createNativeStackNavigator();
|
||||||
|
const TimeTicketBrowserStack = createNativeStackNavigator();
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
emailSignInStart: (email, password) =>
|
emailSignInStart: (email, password) =>
|
||||||
@@ -44,6 +57,12 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||||
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||||
});
|
});
|
||||||
|
const mapStateToProps2 = createStructuredSelector({
|
||||||
|
currentEmployee: selectCurrentEmployee,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps2 = (dispatch) => ({
|
||||||
|
signOut: () => dispatch(employeeSignOut()),
|
||||||
|
});
|
||||||
|
|
||||||
const JobsTabNavigator = connect(
|
const JobsTabNavigator = connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -104,6 +123,74 @@ const MoreStackNavigator = () => (
|
|||||||
</MoreStack.Navigator>
|
</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 = () => (
|
const BottomTabsNavigator = () => (
|
||||||
<BottomTabs.Navigator
|
<BottomTabs.Navigator
|
||||||
screenOptions={({ route }) => ({
|
screenOptions={({ route }) => ({
|
||||||
@@ -116,6 +203,8 @@ const BottomTabsNavigator = () => (
|
|||||||
iconName = "ios-settings";
|
iconName = "ios-settings";
|
||||||
} else if (route.name === "MediaBrowserTab") {
|
} else if (route.name === "MediaBrowserTab") {
|
||||||
iconName = "ios-camera";
|
iconName = "ios-camera";
|
||||||
|
} else if (route.name === "TimeTicketBrowserTab") {
|
||||||
|
iconName = "ios-stopwatch-outline";
|
||||||
} else {
|
} else {
|
||||||
//iconName = "customerservice";
|
//iconName = "customerservice";
|
||||||
}
|
}
|
||||||
@@ -140,6 +229,14 @@ const BottomTabsNavigator = () => (
|
|||||||
}}
|
}}
|
||||||
component={MediaBrowserStackNavigator}
|
component={MediaBrowserStackNavigator}
|
||||||
/>
|
/>
|
||||||
|
<BottomTabs.Screen
|
||||||
|
name="TimeTicketBrowserTab"
|
||||||
|
options={{
|
||||||
|
title: i18n.t("timeticketbrowser.titles.timeticketbrowsertab"),
|
||||||
|
headerShown: false,
|
||||||
|
}}
|
||||||
|
component={TimeTicketBrowserStackNavigator}
|
||||||
|
/>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="MoreTab"
|
name="MoreTab"
|
||||||
options={{ title: i18n.t("more.titles.moretab"), headerShown: false }}
|
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 { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { signOutStart } from "../../redux/user/user.actions";
|
import { signOutStart } from "../../redux/user/user.actions";
|
||||||
|
import { employeeSignOut } from "../../redux/employee/employee.actions";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
signOutStart: () => dispatch(signOutStart()),
|
signOutStart: () => dispatch(signOutStart()),
|
||||||
|
signOut: () => dispatch(employeeSignOut()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SignOutButton({ signOutStart }) {
|
export function SignOutButton({ signOutStart, signOut }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
style={{ margin: 8 }}
|
style={{ margin: 8 }}
|
||||||
onPress={() => signOutStart()}
|
onPress={() => (signOut(),signOutStart())}
|
||||||
title={t("general.actions.signout")}
|
title={t("general.actions.signout")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,4 +9,23 @@ export default StyleSheet.create({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flex: 1,
|
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 } } }) {
|
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||||
id
|
id
|
||||||
jobsizelimit
|
jobsizelimit
|
||||||
|
cdk_dealerid
|
||||||
|
pbs_serialnumber
|
||||||
md_ro_statuses
|
md_ro_statuses
|
||||||
uselocalmediaserver
|
uselocalmediaserver
|
||||||
localmediaserverhttp
|
localmediaserverhttp
|
||||||
shopname
|
shopname
|
||||||
features
|
features
|
||||||
localmediatoken
|
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`
|
export const SEARCH_FOR_JOBS = gql`
|
||||||
query SEARCH_FOR_JOBS($search: String!) {
|
query SEARCH_FOR_JOBS($search: String!) {
|
||||||
jobs(where: { ro_number: { _ilike: $search } }) {
|
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 = (
|
export const generate_UPDATE_JOB_KANBAN = (
|
||||||
oldChildId,
|
oldChildId,
|
||||||
oldChildNewParent,
|
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-async-storage/async-storage": "^1.17.11",
|
||||||
"@react-native-community/art": "^1.2.0",
|
"@react-native-community/art": "^1.2.0",
|
||||||
"@react-native-community/cli-debugger-ui": "^9.0.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-native-community/masked-view": "^0.1.11",
|
||||||
"@react-navigation/bottom-tabs": "^6.3.3",
|
"@react-navigation/bottom-tabs": "^6.3.3",
|
||||||
"@react-navigation/drawer": "^6.4.4",
|
"@react-navigation/drawer": "^6.4.4",
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
"expo-permissions": "^14.1.1",
|
"expo-permissions": "^14.1.1",
|
||||||
"expo-status-bar": "~1.4.2",
|
"expo-status-bar": "~1.4.2",
|
||||||
"expo-system-ui": "^2.2.1",
|
"expo-system-ui": "^2.2.1",
|
||||||
"expo-updates": "^0.16.3",
|
"expo-updates": "^0.16.4",
|
||||||
"expo-video-thumbnails": "^7.2.1",
|
"expo-video-thumbnails": "^7.2.1",
|
||||||
"firebase": "^9.8.3",
|
"firebase": "^9.8.3",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
@@ -62,13 +63,15 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^11.17.2",
|
"react-i18next": "^11.17.2",
|
||||||
"react-is": ">= 16.8.0",
|
"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-dnd-board": "hungga1711/react-native-dnd-board#13/head",
|
||||||
"react-native-draggable-flatlist": "^3.1.2",
|
"react-native-draggable-flatlist": "^3.1.2",
|
||||||
|
"react-native-element-dropdown": "^2.9.0",
|
||||||
"react-native-gesture-handler": "^2.9.0",
|
"react-native-gesture-handler": "^2.9.0",
|
||||||
"react-native-image-gallery": "^2.1.5",
|
"react-native-image-gallery": "^2.1.5",
|
||||||
"react-native-image-viewing": "^0.2.2",
|
"react-native-image-viewing": "^0.2.2",
|
||||||
"react-native-indicators": "^0.17.0",
|
"react-native-indicators": "^0.17.0",
|
||||||
|
"react-native-modal-datetime-picker": "^15.0.0",
|
||||||
"react-native-pager-view": "6.1.2",
|
"react-native-pager-view": "6.1.2",
|
||||||
"react-native-paper": "^4.12.4",
|
"react-native-paper": "^4.12.4",
|
||||||
"react-native-progress": "^5.0.0",
|
"react-native-progress": "^5.0.0",
|
||||||
|
|||||||
@@ -27,3 +27,8 @@ export const documentUploadFailure = (error) => ({
|
|||||||
export const toggleDeleteAfterUpload = () => ({
|
export const toggleDeleteAfterUpload = () => ({
|
||||||
type: AppActionTypes.TOGGLE_DLETE_AFTER_UPLOAD,
|
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,
|
documentUploadInProgress: null,
|
||||||
documentUploadError: null,
|
documentUploadError: null,
|
||||||
deleteAfterUpload: false,
|
deleteAfterUpload: false,
|
||||||
|
tmTicketJobId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const appReducer = (state = INITIAL_STATE, action) => {
|
const appReducer = (state = INITIAL_STATE, action) => {
|
||||||
@@ -43,6 +44,11 @@ const appReducer = (state = INITIAL_STATE, action) => {
|
|||||||
...state,
|
...state,
|
||||||
deleteAfterUpload: !state.deleteAfterUpload,
|
deleteAfterUpload: !state.deleteAfterUpload,
|
||||||
};
|
};
|
||||||
|
case AppActionTypes.SET_TM_TICKET_JOB_ID:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
tmTicketJobId: action.payload,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ export const selectDeleteAfterUpload = createSelector(
|
|||||||
[selectApp],
|
[selectApp],
|
||||||
(app) => app.deleteAfterUpload
|
(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_SUCCESS: "DOCUMENT_UPLOAD_SUCCESS",
|
||||||
DOCUMENT_UPLOAD_FAILURE: "DOCUMENT_UPLOAD_FAILURE",
|
DOCUMENT_UPLOAD_FAILURE: "DOCUMENT_UPLOAD_FAILURE",
|
||||||
TOGGLE_DLETE_AFTER_UPLOAD: "TOGGLE_DLETE_AFTER_UPLOAD",
|
TOGGLE_DLETE_AFTER_UPLOAD: "TOGGLE_DLETE_AFTER_UPLOAD",
|
||||||
|
SET_TM_TICKET_JOB_ID: "SET_TM_TICKET_JOB_ID"
|
||||||
};
|
};
|
||||||
export default AppActionTypes;
|
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 appReducer from "./app/app.reducer";
|
||||||
import photosReducer from "./photos/photos.reducer";
|
import photosReducer from "./photos/photos.reducer";
|
||||||
import userReducer from "./user/user.reducer";
|
import userReducer from "./user/user.reducer";
|
||||||
|
import employeeReducer from './employee/employee.reducer';
|
||||||
|
import timeTicketsReducer from './timetickets/timetickets.reducer';
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: "root",
|
key: "root",
|
||||||
storage: AsyncStorage,
|
storage: AsyncStorage,
|
||||||
// whitelist: ["photos"],
|
// whitelist: ["photos"],
|
||||||
blacklist: ["user"],
|
blacklist: ["user","employee","timeTickets"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
app: appReducer,
|
app: appReducer,
|
||||||
photos: photosReducer,
|
photos: photosReducer,
|
||||||
|
employee: employeeReducer,
|
||||||
|
timeTickets: timeTicketsReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default persistReducer(persistConfig, rootReducer);
|
export default persistReducer(persistConfig, rootReducer);
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { all, call } from "redux-saga/effects";
|
|||||||
import { appSagas } from "./app/app.sagas";
|
import { appSagas } from "./app/app.sagas";
|
||||||
import { photosSagas } from "./photos/photos.sagas";
|
import { photosSagas } from "./photos/photos.sagas";
|
||||||
import { userSagas } from "./user/user.sagas";
|
import { userSagas } from "./user/user.sagas";
|
||||||
|
import { employeeSagas } from "./employee/employee.sagas";
|
||||||
|
import { timeTicketsSagas } from "./timetickets/timetickets.sagas";
|
||||||
|
|
||||||
export default function* rootSaga() {
|
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 { applyMiddleware, compose, createStore } from "redux";
|
||||||
import { persistStore } from "redux-persist";
|
import { persistStore } from "redux-persist";
|
||||||
//import { createLogger } from "redux-logger";
|
import { createLogger } from "redux-logger";
|
||||||
import createSagaMiddleware from "redux-saga";
|
import createSagaMiddleware from "redux-saga";
|
||||||
import rootReducer from "./root.reducer";
|
import rootReducer from "./root.reducer";
|
||||||
import rootSaga from "./root.saga";
|
import rootSaga from "./root.saga";
|
||||||
@@ -9,11 +9,11 @@ const sagaMiddleWare = createSagaMiddleware();
|
|||||||
|
|
||||||
const middlewares = [sagaMiddleWare];
|
const middlewares = [sagaMiddleWare];
|
||||||
// if (process.env.NODE_ENV === "development") {
|
// if (process.env.NODE_ENV === "development") {
|
||||||
// middlewares.push(
|
middlewares.push(
|
||||||
// createLogger({
|
createLogger({
|
||||||
// collapsed: true,
|
collapsed: true,
|
||||||
// })
|
})
|
||||||
// );
|
);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//Add in for React Native Debugger.
|
//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],
|
[selectUser],
|
||||||
(user) => user.signingIn
|
(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": {
|
"general": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"signout": "Sign Out"
|
"signout": "Sign Out",
|
||||||
|
"logout": "Logout"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"na": "N/A"
|
"na": "N/A"
|
||||||
@@ -305,6 +306,222 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Password"
|
"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": {
|
"general": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"signout": ""
|
"signout": "",
|
||||||
|
"logout": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"na": ""
|
"na": ""
|
||||||
@@ -305,6 +306,222 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": ""
|
"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": {
|
"general": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"signout": ""
|
"signout": "",
|
||||||
|
"logout": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"na": ""
|
"na": ""
|
||||||
@@ -305,6 +306,222 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": ""
|
"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 axios from "axios";
|
||||||
import { auth } from "../firebase/firebase.utils";
|
import { auth } from "../firebase/firebase.utils";
|
||||||
|
import env from "../env";
|
||||||
if (process.env.NODE_ENV === "production") {
|
//if (process.env.NODE_ENV === "production") {
|
||||||
axios.defaults.baseURL =
|
axios.defaults.baseURL = "https://api.test.imex.online/";
|
||||||
process.env.REACT_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
|
//env.REACT_APP_AXIOS_BASE_API_URL || "https://api.imex.online/";
|
||||||
}
|
//}
|
||||||
|
|
||||||
export const axiosAuthInterceptorId = axios.interceptors.request.use(
|
export const axiosAuthInterceptorId = axios.interceptors.request.use(
|
||||||
async (config) => {
|
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:
|
dependencies:
|
||||||
serve-static "^1.13.1"
|
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"
|
version "10.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-10.2.2.tgz#b1893604fa9fc8971064e7c00042350f96868bfe"
|
resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-10.2.2.tgz#b1893604fa9fc8971064e7c00042350f96868bfe"
|
||||||
integrity sha512-49Ep2aQOF0PkbAR/TcyMjOm9XwBa8VQr+/Zzf4SJeYwiYLCT1NZRAVAVjYRXl0xqvq5S5mAGZZShS4AQl4WsZw==
|
integrity sha512-49Ep2aQOF0PkbAR/TcyMjOm9XwBa8VQr+/Zzf4SJeYwiYLCT1NZRAVAVjYRXl0xqvq5S5mAGZZShS4AQl4WsZw==
|
||||||
@@ -2277,19 +2277,7 @@
|
|||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
logkitty "^0.7.1"
|
logkitty "^0.7.1"
|
||||||
|
|
||||||
"@react-native-community/cli-platform-ios@10.2.0":
|
"@react-native-community/cli-platform-ios@10.2.1", "@react-native-community/cli-platform-ios@^10.2.1":
|
||||||
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":
|
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.1.tgz#2e6bd2cb6d48cbb8720d7b7265bb1bab80745f72"
|
||||||
integrity sha512-hz4zu4Y6eyj7D0lnZx8Mf2c2si8y+zh/zUTgCTaPPLzQD8jSZNNBtUUiA1cARm2razpe8marCZ1QbTMAGbf3mg==
|
integrity sha512-hz4zu4Y6eyj7D0lnZx8Mf2c2si8y+zh/zUTgCTaPPLzQD8jSZNNBtUUiA1cARm2razpe8marCZ1QbTMAGbf3mg==
|
||||||
@@ -2301,7 +2289,7 @@
|
|||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
ora "^5.4.1"
|
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"
|
version "10.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-10.2.2.tgz#766914e3c8007dfe52b253544c4f6cd8549919ac"
|
resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-10.2.2.tgz#766914e3c8007dfe52b253544c4f6cd8549919ac"
|
||||||
integrity sha512-sTGjZlD3OGqbF9v1ajwUIXhGmjw9NyJ/14Lo0sg7xH8Pv4qUd5ZvQ6+DWYrQn3IKFUMfGFWYyL81ovLuPylrpw==
|
integrity sha512-sTGjZlD3OGqbF9v1ajwUIXhGmjw9NyJ/14Lo0sg7xH8Pv4qUd5ZvQ6+DWYrQn3IKFUMfGFWYyL81ovLuPylrpw==
|
||||||
@@ -2355,17 +2343,17 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
joi "^17.2.1"
|
joi "^17.2.1"
|
||||||
|
|
||||||
"@react-native-community/cli@10.2.0":
|
"@react-native-community/cli@10.2.2":
|
||||||
version "10.2.0"
|
version "10.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-10.2.0.tgz#bcb65bb3dcb03b0fc4e49619d51e12d23396b301"
|
resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-10.2.2.tgz#3fa438ba7f19f83e07bc337765fc1cabdcf2cac2"
|
||||||
integrity sha512-QH7AFBz5FX2zTZRH/o3XehHrZ0aZZEL5Sh+23nSEFgSj3bLFfvjjZhuoiRSAo7iiBdvAoXrfxQ8TXgg4Xf/7fw==
|
integrity sha512-aZVcVIqj+OG6CrliR/Yn8wHxrvyzbFBY9cj7n0MvRw/P54QUru2nNqUTSSbqv0Qaa297yHJbe6kFDojDMSTM8Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-native-community/cli-clean" "^10.1.1"
|
"@react-native-community/cli-clean" "^10.1.1"
|
||||||
"@react-native-community/cli-config" "^10.1.1"
|
"@react-native-community/cli-config" "^10.1.1"
|
||||||
"@react-native-community/cli-debugger-ui" "^10.0.0"
|
"@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-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-server-api" "^10.1.1"
|
||||||
"@react-native-community/cli-tools" "^10.1.1"
|
"@react-native-community/cli-tools" "^10.1.1"
|
||||||
"@react-native-community/cli-types" "^10.0.0"
|
"@react-native-community/cli-types" "^10.0.0"
|
||||||
@@ -2378,6 +2366,13 @@
|
|||||||
prompts "^2.4.0"
|
prompts "^2.4.0"
|
||||||
semver "^6.3.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":
|
"@react-native-community/masked-view@^0.1.11":
|
||||||
version "0.1.11"
|
version "0.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.11.tgz#2f4c6e10bee0786abff4604e39a37ded6f3980ce"
|
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"
|
resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-0.9.1.tgz#e81308d551ed5a4c35c8770ac61434f6ca749610"
|
||||||
integrity sha512-wk88LLhseQ7LJvxdN7BTKiryyqALxnrvr+lyHK3/prg76Yy0EGi2Q/oE/rtFyyZ1JmQDRbO/5pdX0EE6QqVQXQ==
|
integrity sha512-wk88LLhseQ7LJvxdN7BTKiryyqALxnrvr+lyHK3/prg76Yy0EGi2Q/oE/rtFyyZ1JmQDRbO/5pdX0EE6QqVQXQ==
|
||||||
|
|
||||||
expo-updates@^0.16.3:
|
expo-updates@^0.16.4:
|
||||||
version "0.16.3"
|
version "0.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.16.3.tgz#190f5896f98db2e130b608d61c8359ee4b2c2125"
|
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.16.4.tgz#6d05438cf7304add03645a598211ac4ef3cc4f64"
|
||||||
integrity sha512-uFr2Fvq7IbKwz9xEqWE9GNEs0sBAd6uiUI9McTCKw4BzKhjylRbPAN3zewc7MGLOvhTwBASva79VLQVgzdoBRw==
|
integrity sha512-hEUotP10sBiYn6dvkYC2rIa+kAmsBuaMp32sIVNAYEwKMQJqEwqNAKTU6CpJ4Aoc//BYL2Hv8qNo/UsT4rATRg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@expo/code-signing-certificates" "0.0.5"
|
"@expo/code-signing-certificates" "0.0.5"
|
||||||
"@expo/config" "~8.0.0"
|
"@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"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
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:
|
metro-babel-transformer@0.73.9:
|
||||||
version "0.73.9"
|
version "0.73.9"
|
||||||
resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.73.9.tgz#bec8aaaf1bbdc2e469fde586fde455f8b2a83073"
|
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"
|
"@babel/template" "^7.0.0"
|
||||||
react-refresh "^0.4.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:
|
metro-react-native-babel-transformer@0.73.9:
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
absolute-path "^0.0.0"
|
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:
|
metro-runtime@0.73.9:
|
||||||
version "0.73.9"
|
version "0.73.9"
|
||||||
resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.73.9.tgz#0b24c0b066b8629ee855a6e5035b65061fef60d5"
|
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"
|
"@babel/runtime" "^7.0.0"
|
||||||
react-refresh "^0.4.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:
|
metro-source-map@0.73.9:
|
||||||
version "0.73.9"
|
version "0.73.9"
|
||||||
resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.73.9.tgz#89ca41f6346aeb12f7f23496fa363e520adafebe"
|
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"
|
source-map "^0.5.6"
|
||||||
vlq "^1.0.0"
|
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:
|
metro-symbolicate@0.73.9:
|
||||||
version "0.73.9"
|
version "0.73.9"
|
||||||
resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.73.9.tgz#cb452299a36e5b86b2826e7426d51221635c48bf"
|
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"
|
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||||
integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
|
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:
|
ob1@0.73.9:
|
||||||
version "0.73.9"
|
version "0.73.9"
|
||||||
resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.73.9.tgz#d5677a0dd3e2f16ad84231278d79424436c38c59"
|
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"
|
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==
|
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:
|
react-native-gesture-handler@^2.4.0, react-native-gesture-handler@^2.9.0:
|
||||||
version "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"
|
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"
|
lodash "^4.17.21"
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-native-gradle-plugin@^0.71.16:
|
react-native-gradle-plugin@^0.71.17:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.17.tgz#cf780a27270f0a32dca8184eff91555d7627dd00"
|
||||||
integrity sha512-OXXYgpISEqERwjSlaCiaQY6cTY5CH6j73gdkWpK0hedxtiWMWgH+i5TOi4hIGYitm9kQBeyDu+wim9fA8ROFJA==
|
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"
|
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||||
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
|
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:
|
react-native-pager-view@6.1.2:
|
||||||
version "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"
|
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"
|
postcss-value-parser "^4.2.0"
|
||||||
styleq "^0.1.2"
|
styleq "^0.1.2"
|
||||||
|
|
||||||
react-native@^0.71.4:
|
react-native@0.71.6:
|
||||||
version "0.71.4"
|
version "0.71.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.4.tgz#f03f600efe68f745d19454ab17f9c1a9ef304790"
|
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.6.tgz#e8f07baf55abd1015eaa7040ceaa4aa632c2c04f"
|
||||||
integrity sha512-3hSYqvWrOdKhpV3HpEKp1/CkWx8Sr/N/miCrmUIAsVTSJUR7JW0VvIsrV9urDhUj/s6v2WF4n7qIEEJsmTCrPw==
|
integrity sha512-gHrDj7qaAaiE41JwaFCh3AtvOqOLuRgZtHKzNiwxakG/wvPAYmG73ECfWHGxjxIx/QT17Hp37Da3ipCei/CayQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jest/create-cache-key-function" "^29.2.1"
|
"@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-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/assets" "1.0.0"
|
||||||
"@react-native/normalize-color" "2.1.0"
|
"@react-native/normalize-color" "2.1.0"
|
||||||
"@react-native/polyfills" "2.0.0"
|
"@react-native/polyfills" "2.0.0"
|
||||||
@@ -8160,16 +8107,16 @@ react-native@^0.71.4:
|
|||||||
jest-environment-node "^29.2.1"
|
jest-environment-node "^29.2.1"
|
||||||
jsc-android "^250231.0.0"
|
jsc-android "^250231.0.0"
|
||||||
memoize-one "^5.0.0"
|
memoize-one "^5.0.0"
|
||||||
metro-react-native-babel-transformer "0.73.8"
|
metro-react-native-babel-transformer "0.73.9"
|
||||||
metro-runtime "0.73.8"
|
metro-runtime "0.73.9"
|
||||||
metro-source-map "0.73.8"
|
metro-source-map "0.73.9"
|
||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
nullthrows "^1.1.1"
|
nullthrows "^1.1.1"
|
||||||
pretty-format "^26.5.2"
|
pretty-format "^26.5.2"
|
||||||
promise "^8.3.0"
|
promise "^8.3.0"
|
||||||
react-devtools-core "^4.26.1"
|
react-devtools-core "^4.26.1"
|
||||||
react-native-codegen "^0.71.5"
|
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-refresh "^0.4.0"
|
||||||
react-shallow-renderer "^16.15.0"
|
react-shallow-renderer "^16.15.0"
|
||||||
regenerator-runtime "^0.13.2"
|
regenerator-runtime "^0.13.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user