fixbugwithselectordropdownaddedclockedinlist
This commit is contained in:
@@ -53,16 +53,16 @@ export function CostCenterSelect(props) {
|
|||||||
selectedTextStyle={styles.selectedTextStyle}
|
selectedTextStyle={styles.selectedTextStyle}
|
||||||
inputSearchStyle={styles.inputSearchStyle}
|
inputSearchStyle={styles.inputSearchStyle}
|
||||||
iconStyle={styles.iconStyle}
|
iconStyle={styles.iconStyle}
|
||||||
data={costCenters}
|
|
||||||
search
|
search
|
||||||
maxHeight={300}
|
maxHeight={300}
|
||||||
labelField="label"
|
labelField="label"
|
||||||
valueField="value"
|
valueField="value"
|
||||||
placeholder={!isFocus ? "Select Cost Center" : "..."}
|
placeholder={!isFocus ? "Select Cost Center" : "..."}
|
||||||
searchPlaceholder="Search..."
|
searchPlaceholder="Search..."
|
||||||
value={props.currentValue?.value}
|
|
||||||
onFocus={() => setIsFocus(true)}
|
onFocus={() => setIsFocus(true)}
|
||||||
onBlur={() => setIsFocus(false)}
|
onBlur={() => setIsFocus(false)}
|
||||||
|
data={costCenters}
|
||||||
|
value={props.currentValue?.value}
|
||||||
onChange={(item) => {
|
onChange={(item) => {
|
||||||
props.onValueSelected(item);
|
props.onValueSelected(item);
|
||||||
//setValue(item.value);
|
//setValue(item.value);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
import React, { forwardRef, useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
@@ -12,17 +12,24 @@ import { Dropdown } from "react-native-element-dropdown";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
///This Component is for testing select. This is not for prod. Justin
|
|
||||||
|
|
||||||
export function JobIdSearchSelect(
|
export function JobIdSearchSelect(
|
||||||
convertedOnly = false,
|
props
|
||||||
notInvoiced = false,
|
// //currentValue,
|
||||||
notExported = true,
|
// ...restProps
|
||||||
clm_no = false,
|
|
||||||
onValueSelected,
|
|
||||||
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 { t } = useTranslation();
|
||||||
const [theOptions, setTheOptions] = useState([]);
|
const [theOptions, setTheOptions] = useState([]);
|
||||||
|
|
||||||
@@ -33,19 +40,18 @@ export function JobIdSearchSelect(
|
|||||||
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
|
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
|
||||||
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
||||||
|
|
||||||
|
|
||||||
const [callSearch, { loading, error, data }] = useLazyQuery(
|
const [callSearch, { loading, error, data }] = useLazyQuery(
|
||||||
SEARCH_JOBS_FOR_AUTOCOMPLETE,
|
SEARCH_JOBS_FOR_AUTOCOMPLETE,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (v) => {
|
||||||
console.log("executeSearchWithV:", v);
|
// console.log("executeSearchWithV:", v);
|
||||||
if (v && v !== "") callSearch(v);
|
if (v && v !== "") callSearch(v);
|
||||||
};
|
};
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
console.log("handleSearchWithValue:", value);
|
// console.log("handleSearchWithValue:", value);
|
||||||
debouncedExecuteSearch({
|
debouncedExecuteSearch({
|
||||||
variables: {
|
variables: {
|
||||||
search: value,
|
search: value,
|
||||||
@@ -60,51 +66,58 @@ export function JobIdSearchSelect(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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(() => {
|
useEffect(() => {
|
||||||
console.log("useEfectDependentOn: restProps.value, callIdSearch", restProps);
|
// console.log("useEfectDependentOn: [data, idData]");
|
||||||
if (restProps.value) {
|
|
||||||
console.log("restPropsValue:", restProps.value);
|
// console.log( "idDataValue:", idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []);
|
||||||
callIdSearch({ variables: { id: restProps.value } });
|
// 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"
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [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 : []));
|
|
||||||
setTheOptions(
|
|
||||||
_.uniqBy(
|
|
||||||
[
|
|
||||||
...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []),
|
|
||||||
...(data && data.search_jobs ? data.search_jobs : []),
|
|
||||||
],
|
|
||||||
"id"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [data, idData]);
|
}, [data, idData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("useEfectDependentOn: [theOptions]");
|
// console.log("useEfectDependentOn: [theOptions]");
|
||||||
var count = Object.keys(theOptions).length;
|
var count = Object.keys(theOptions).length;
|
||||||
|
console.log("useEfectDependentOn: [theOptions] count:", count);
|
||||||
let selectDataArray = [];
|
let selectDataArray = [];
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
// let o = theOptions[i];
|
|
||||||
selectDataArray.push({
|
selectDataArray.push({
|
||||||
value: theOptions[i].id,
|
value: theOptions[i].id,
|
||||||
label: theOptions[i].ro_number,
|
label: `${clm_no && theOptions[i].clm_no ? `${theOptions[i].clm_no} | ` : ""}${
|
||||||
|
theOptions[i].ro_number || t("general.labels.na")
|
||||||
// `${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
|
} | ${OwnerNameDisplayFunction(theOptions[i])} | ${theOptions[i].v_model_yr || ""} ${theOptions[i].v_make_desc || ""} ${
|
||||||
// o.ro_number || t("general.labels.na")
|
theOptions[i].v_model_desc || ""
|
||||||
// } | ${OwnerNameDisplayFunction(o)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
|
}`,
|
||||||
// o.v_model_desc || ""
|
|
||||||
// }`,
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setSelectorData(selectDataArray);
|
setSelectorData(selectDataArray);
|
||||||
}, [theOptions]);
|
}, [theOptions]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// console.log("useEffectonselectedvaluechange:", selectedvalue.value);
|
||||||
|
// if (typeof onJobSelected !== "undefined") {
|
||||||
|
// console.log("onJobSelected:", selectedvalue.value);
|
||||||
|
// onJobSelected(selectedvalue.value);
|
||||||
|
// }
|
||||||
|
// }, [selectedvalue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@@ -114,29 +127,41 @@ export function JobIdSearchSelect(
|
|||||||
selectedTextStyle={styles.selectedTextStyle}
|
selectedTextStyle={styles.selectedTextStyle}
|
||||||
inputSearchStyle={styles.inputSearchStyle}
|
inputSearchStyle={styles.inputSearchStyle}
|
||||||
iconStyle={styles.iconStyle}
|
iconStyle={styles.iconStyle}
|
||||||
data={selectorData}
|
|
||||||
search
|
search
|
||||||
maxHeight={300}
|
maxHeight={300}
|
||||||
labelField="label"
|
labelField="label"
|
||||||
valueField="value"
|
valueField="value"
|
||||||
placeholder={!isFocus ? "Job Id to Post Against" : "..."}
|
placeholder={!isFocus ? "Job Id to Post Against" : "..."}
|
||||||
searchPlaceholder="Search..."
|
searchPlaceholder="Search..."
|
||||||
value={selectedvalue}//{selectedvalue}
|
|
||||||
onFocus={() => setIsFocus(true)}
|
onFocus={() => setIsFocus(true)}
|
||||||
onBlur={() => setIsFocus(false)}
|
onBlur={() => setIsFocus(false)}
|
||||||
onChange={(item) => {
|
data={selectorData}
|
||||||
console.log("onChangefired!!!!");
|
value={selectedvalue} //{selectedvalue}
|
||||||
console.log("itemSelected", item);
|
onChange={(item) => props.onJobSelected(item)}//TODO: add setIsFocus(false); to this
|
||||||
setSelectedValue(item.value);
|
// {
|
||||||
// onValueSelected(item);
|
// console.log("onValueSelected!!!!");
|
||||||
//console.log(item);
|
// // (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);
|
// setSelectedValue(item.value);
|
||||||
// if (onValueSelected) onValueSelected(item.value);
|
// if (onValueSelected) onValueSelected(item.value);
|
||||||
setIsFocus(false);
|
|
||||||
}}
|
// setIsFocus(false);
|
||||||
|
// }}
|
||||||
onChangeText={(search) => {
|
onChangeText={(search) => {
|
||||||
|
if (search && search !== "") {
|
||||||
console.log("onChangeTextFired!!!!");
|
console.log("onChangeTextFired!!!!");
|
||||||
handleSearch(search);
|
handleSearch(search);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* {theOptions ? console.log(theOptions): null} */}
|
{/* {theOptions ? console.log(theOptions): null} */}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";
|
||||||
|
import { Card, DataTable } from "react-native-paper";
|
||||||
|
|
||||||
|
export default function LaborAllocationsTable({ job, loading, refetch }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!job) {
|
||||||
|
<Card>
|
||||||
|
<Text>Job is not defined.</Text>
|
||||||
|
</Card>;
|
||||||
|
}
|
||||||
|
const onRefresh = async () => {
|
||||||
|
return refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<DataTable>
|
||||||
|
<DataTable.Header>
|
||||||
|
<DataTable.Title style={{ flex: 4 }}>
|
||||||
|
{t("jobdetail.labels.lines_desc")}
|
||||||
|
</DataTable.Title>
|
||||||
|
<DataTable.Title style={{ flex: 2 }}>
|
||||||
|
{t("jobdetail.labels.lines_lbr_ty")}
|
||||||
|
</DataTable.Title>
|
||||||
|
<DataTable.Title style={{ flex: 1 }}>
|
||||||
|
{t("jobdetail.labels.lines_lb_hrs")}
|
||||||
|
</DataTable.Title>
|
||||||
|
<DataTable.Title style={{ flex: 2 }}>
|
||||||
|
{t("jobdetail.labels.lines_part_type")}
|
||||||
|
</DataTable.Title>
|
||||||
|
<DataTable.Title style={{ flex: 1 }}>
|
||||||
|
{t("jobdetail.labels.lines_qty")}
|
||||||
|
</DataTable.Title>
|
||||||
|
</DataTable.Header>
|
||||||
|
</DataTable>
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={job.joblines}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||||
|
}
|
||||||
|
keyExtractor={(item) => item.id}
|
||||||
|
renderItem={(object) => (
|
||||||
|
<DataTable.Row>
|
||||||
|
<DataTable.Cell style={{ flex: 4 }}>
|
||||||
|
{object.item.line_desc}
|
||||||
|
</DataTable.Cell>
|
||||||
|
<DataTable.Cell style={{ flex: 2 }}>
|
||||||
|
{object.item.mod_lbr_ty &&
|
||||||
|
t(`jobdetail.lbr_types.${object.item.mod_lbr_ty}`)}
|
||||||
|
</DataTable.Cell>
|
||||||
|
<DataTable.Cell style={{ flex: 1 }}>
|
||||||
|
{object.item.mod_lb_hrs}
|
||||||
|
</DataTable.Cell>
|
||||||
|
<DataTable.Cell style={{ flex: 2 }}>
|
||||||
|
{object.item.part_type &&
|
||||||
|
t(`jobdetail.part_types.${object.item.part_type}`)}
|
||||||
|
</DataTable.Cell>
|
||||||
|
<DataTable.Cell style={{ flex: 1 }}>
|
||||||
|
{object.item.part_qty}
|
||||||
|
</DataTable.Cell>
|
||||||
|
</DataTable.Row>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const localStyles = StyleSheet.create({});
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { View, Text } from "react-native";
|
import { View, Text } from "react-native";
|
||||||
|
import axios from "axios";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { employeeGetRatesStart } from "../../redux/employee/employee.actions";
|
import { employeeGetRatesStart } from "../../redux/employee/employee.actions";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -16,13 +16,14 @@ import { Button } from "react-native-paper";
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import ErrorDisplay from "../error-display/error-display.component";
|
import ErrorDisplay from "../error-display/error-display.component";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { JobSearchSelect } from "../Selects/select-job-name";
|
import { JobIdSearchSelect } from "../Selects/select-job-id";
|
||||||
import { CostCenterSelect } from "../Selects/select-cost-center";
|
import { CostCenterSelect } from "../Selects/select-cost-center";
|
||||||
import {
|
import {
|
||||||
selectCurrentTimeTicketJob,
|
selectCurrentTimeTicketJob,
|
||||||
selectCurrentTimeTicketJobId,
|
selectCurrentTimeTicketJobId,
|
||||||
} from "../../redux/timetickets/timetickets.selectors";
|
} from "../../redux/timetickets/timetickets.selectors";
|
||||||
import { JobIdSearchSelect } from "../Selects/select-job-id";
|
import moment from "moment";
|
||||||
|
import { EmployeeClockedInList } from "../time-ticket-lists/employee-clockedin-list.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentEmployee: selectCurrentEmployee,
|
currentEmployee: selectCurrentEmployee,
|
||||||
@@ -51,7 +52,7 @@ export function ScreenTimeTicketBrowser({
|
|||||||
}) {
|
}) {
|
||||||
//const employeeId = currentEmployee.technician.id;
|
//const employeeId = currentEmployee.technician.id;
|
||||||
const [currentSCC, setCurrentSCC] = useState(null);
|
const [currentSCC, setCurrentSCC] = useState(null);
|
||||||
const [currentSJob, setCurrentSJob] = useState(null);
|
// const [currentSJob, setCurrentSJob] = useState(null);
|
||||||
const [currentSJobId, setCurrentSJobId] = useState(null);
|
const [currentSJobId, setCurrentSJobId] = useState(null);
|
||||||
|
|
||||||
// const { error, data } = useQuery(QUERY_EMPLOYEE_BY_ID, {
|
// const { error, data } = useQuery(QUERY_EMPLOYEE_BY_ID, {
|
||||||
@@ -60,43 +61,65 @@ export function ScreenTimeTicketBrowser({
|
|||||||
// const signingErrorMsg = error ? (<ErrorDisplay errorMessage={error.message} />) : null;
|
// const signingErrorMsg = error ? (<ErrorDisplay errorMessage={error.message} />) : null;
|
||||||
// const signingErrorMsg = signingError ? (<ErrorDisplay errorMessage={signingError} />) : null;
|
// const signingErrorMsg = signingError ? (<ErrorDisplay errorMessage={signingError} />) : null;
|
||||||
|
|
||||||
const wrapperSetCurrentSJobState = useCallback(val => {
|
// const wrapperSetCurrentSJobState = useCallback(
|
||||||
setCurrentSJob(val);
|
// (val) => {
|
||||||
}, [setCurrentSJob]);
|
// setCurrentSJobId(val);
|
||||||
|
// },
|
||||||
|
// [setCurrentSJobId]
|
||||||
|
// );
|
||||||
|
|
||||||
const getRates = (currentEmployee) => {
|
const getRates = (currentEmployee) => {
|
||||||
employeeGetRatesStart(currentEmployee.technician.id);
|
employeeGetRatesStart(currentEmployee.technician.id);
|
||||||
};
|
};
|
||||||
const createTheTimeTicketOBJ = (
|
const createTheTimeTicketOBJ = async (
|
||||||
currentEmployee,
|
currentEmployee,
|
||||||
currentBodyshop,
|
currentBodyshop,
|
||||||
currentSCC,
|
currentSCC,
|
||||||
currentSJob,
|
|
||||||
currentSJobId
|
currentSJobId
|
||||||
) => {
|
) => {
|
||||||
console.log("currentSCC", currentSCC.value);
|
const theTime = (await axios.post("/utils/time")).data;
|
||||||
console.log("currentSJob", currentSJob.value);
|
if (currentBodyshop) console.log("bodyshopid", currentBodyshop?.id);
|
||||||
console.log("currentSJobId", currentSJobId.value);
|
if (currentEmployee) console.log("employeeid", currentEmployee?.technician.id);
|
||||||
console.log("bodyshopid", currentBodyshop.id);
|
if (theTime) console.log("date", moment(theTime).format("YYYY-MM-DD"));
|
||||||
console.log("employeeid", currentEmployee.technician.id);
|
// if (currentSJob) console.log("currentSJob", currentSJob?.value);
|
||||||
console.log(currentBodyshop);
|
if (currentSJobId) console.log("jobid or currentSJobId", currentSJobId?.value);
|
||||||
|
if (currentSCC) console.log("cost_center or currentSCC", currentSCC?.value);
|
||||||
|
//if(currentBodyshop)console.log(currentBodyshop);
|
||||||
|
if (currentBodyshop)
|
||||||
|
console.log(
|
||||||
|
"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
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (currentEmployee)
|
||||||
|
console.log("flat_rate", currentEmployee?.technician?.flat_rate);
|
||||||
|
|
||||||
|
if (currentSCC) console.log("rate or currentSCC", currentSCC?.rate);
|
||||||
// const temp = {
|
// const temp = {
|
||||||
// timeTicketInput: [
|
// timeTicketInput: [
|
||||||
// {
|
// {
|
||||||
// bodyshopid: currentBodyshop?.id,
|
//have bodyshopid: currentBodyshop?.id,
|
||||||
// employeeid: currentEmployee?.technician?.id,
|
//have employeeid: currentEmployee?.technician?.id,
|
||||||
// date: "2023-05-11", //moment(theTime).format("YYYY-MM-DD"),
|
//have date: "2023-05-11", //moment(theTime).format("YYYY-MM-DD"),
|
||||||
// //clockon: moment(theTime),
|
// //clockon: moment(theTime),
|
||||||
// jobid: "temp",//values.jobid,
|
// jobid: "temp",//values.jobid,
|
||||||
// cost_center: "temp",//values.cost_center,
|
//have cost_center: "temp",//values.cost_center,
|
||||||
// ciecacode: currentBodyshop?.cdk_dealerid || currentBodyshop?.pbs_serialnumber
|
//have ciecacode: currentBodyshop?.cdk_dealerid || currentBodyshop?.pbs_serialnumber
|
||||||
// ? values.cost_center
|
// ? values.cost_center
|
||||||
// : Object.keys(currentBodyshop.md_responsibility_centers.defaults.costs).find((key) => {
|
// : Object.keys(currentBodyshop.md_responsibility_centers.defaults.costs).find((key) => {
|
||||||
// return (currentBodyshop.md_responsibility_centers.defaults.costs[key] === "temp");//values.cost_center);
|
// return (currentBodyshop.md_responsibility_centers.defaults.costs[key] === "temp");//values.cost_center);
|
||||||
// }),
|
// }),
|
||||||
// flat_rate: currentEmployee.technician.flat_rate,
|
//have flat_rate: currentEmployee.technician.flat_rate,
|
||||||
// rate: 1,
|
//have rate: 1,
|
||||||
// },
|
// },
|
||||||
// ],
|
// ],
|
||||||
// };
|
// };
|
||||||
@@ -111,7 +134,7 @@ export function ScreenTimeTicketBrowser({
|
|||||||
loading={loaderGettingRates}
|
loading={loaderGettingRates}
|
||||||
//onPress={() => getRates(currentEmployee)}
|
//onPress={() => getRates(currentEmployee)}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
createTheTimeTicketOBJ(currentEmployee, currentBodyshop, currentSCC)
|
createTheTimeTicketOBJ(currentEmployee, currentBodyshop, currentSCC, currentSJobId)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text>text here</Text>
|
<Text>text here</Text>
|
||||||
@@ -119,14 +142,7 @@ export function ScreenTimeTicketBrowser({
|
|||||||
{/* {signingErrorMsg} */}
|
{/* {signingErrorMsg} */}
|
||||||
<JobIdSearchSelect
|
<JobIdSearchSelect
|
||||||
currentValue={currentSJobId}
|
currentValue={currentSJobId}
|
||||||
//onValueSelected={setCurrentSJobId}
|
onJobSelected={setCurrentSJobId}
|
||||||
convertedOnly={!currentBodyshop.tt_allow_post_to_invoiced}
|
|
||||||
notExported={!currentBodyshop.tt_allow_post_to_invoiced}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<JobSearchSelect
|
|
||||||
currentValue={currentSJob}
|
|
||||||
onValueSelected={setCurrentSJob}
|
|
||||||
convertedOnly={!currentBodyshop.tt_allow_post_to_invoiced}
|
convertedOnly={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||||
notExported={!currentBodyshop.tt_allow_post_to_invoiced}
|
notExported={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||||
/>
|
/>
|
||||||
@@ -139,10 +155,11 @@ export function ScreenTimeTicketBrowser({
|
|||||||
mode="outlined"
|
mode="outlined"
|
||||||
//loading={loaderClockIn}
|
//loading={loaderClockIn}
|
||||||
//onPress={() => clockIn()}
|
//onPress={() => clockIn()}
|
||||||
|
|
||||||
>
|
>
|
||||||
<Text>Clock In</Text>
|
<Text>Clock In</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<EmployeeClockedInList technician={currentEmployee} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button, List, Title, Card, Text } from "react-native-paper";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
import { DateTimeFormatter } from "../../util/DateFormater";
|
||||||
|
|
||||||
|
// const mapStateToProps = createStructuredSelector({});
|
||||||
|
// const mapDispatchToProps = (dispatch) => ({
|
||||||
|
// setCameraJobId: (id) => dispatch(setCameraJobId(id)),
|
||||||
|
// setCameraJob: (job) => dispatch(setCameraJob(job)),
|
||||||
|
// });
|
||||||
|
|
||||||
|
export function ClockedinListItem({ ticket }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
|
const onPress = () => {
|
||||||
|
// logImEXEvent("imexmobile_view_job_detail");
|
||||||
|
// navigation.push("JobDetail", {
|
||||||
|
// jobId: item.id,
|
||||||
|
// title: item.ro_number || t("general.labels.na"),
|
||||||
|
// job: item,
|
||||||
|
// });
|
||||||
|
console.log("ClockedinListItem, onPress called");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ margin: 8 }}>
|
||||||
|
<Card.Title
|
||||||
|
title={`${
|
||||||
|
ticket.job.ro_number || t("general.labels.na")
|
||||||
|
} ${OwnerNameDisplayFunction(ticket.job)}`}
|
||||||
|
/>
|
||||||
|
<Card.Content>
|
||||||
|
<Text>Vehicle :
|
||||||
|
{`${ticket.job.v_model_yr || ""} ${
|
||||||
|
ticket.job.v_make_desc || ""
|
||||||
|
} ${ticket.job.v_model_desc || ""}`}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
Clocked In : <DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
Cost Center : {ticket.cost_center === "timetickets.labels.shift"
|
||||||
|
? t(ticket.cost_center)
|
||||||
|
: ticket.cost_center}
|
||||||
|
</Text>
|
||||||
|
</Card.Content>
|
||||||
|
<Card.Actions>
|
||||||
|
{/* <TechClockOffButton
|
||||||
|
jobId={ticket.jobid}
|
||||||
|
timeTicketId={ticket.id}
|
||||||
|
completedCallback={refetch}
|
||||||
|
/> */}
|
||||||
|
<Button
|
||||||
|
mode="outlined" onPress={() => onPress}>Clock Out</Button>
|
||||||
|
</Card.Actions>
|
||||||
|
</Card>
|
||||||
|
// <List.Item
|
||||||
|
// onPress={onPress}
|
||||||
|
// title={<Title>{item.ro_number || t("general.labels.na")}</Title>}
|
||||||
|
// descriptionNumberOfLines={2}
|
||||||
|
// description={`${item.ownr_fn || ""} ${item.ownr_ln || ""} ${
|
||||||
|
// item.ownr_co_nm || ""
|
||||||
|
// } - ${item.v_model_yr || ""} ${item.v_make_desc || ""} ${
|
||||||
|
// item.v_model_desc || ""
|
||||||
|
// }`}
|
||||||
|
// right={({ style }) => (
|
||||||
|
// <Button
|
||||||
|
// style={[style, { alignSelf: "center" }]}
|
||||||
|
// onPress={() => {
|
||||||
|
// logImEXEvent("imexmobile_setcamerajobid_row");
|
||||||
|
// setCameraJobId(item.id);
|
||||||
|
// setCameraJob(item);
|
||||||
|
// navigation.navigate("MediaBrowserTab");
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Ionicons
|
||||||
|
// style={[style, { alignSelf: "center" }]}
|
||||||
|
// name="ios-add"
|
||||||
|
// size={32}
|
||||||
|
// color="dodgerblue"
|
||||||
|
// />
|
||||||
|
// </Button>
|
||||||
|
// )}
|
||||||
|
// />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, null)(ClockedinListItem);
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { connect } from "react-redux";
|
||||||
|
import { selectCurrentEmployee, selectTechnician } from "../../redux/employee/employee.selectors";
|
||||||
|
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
|
||||||
|
import { ActivityIndicator,Button, List, Modal, Portal, Searchbar } from "react-native-paper";
|
||||||
|
import ErrorDisplay from "../error-display/error-display.component";
|
||||||
|
import { View,Text,FlatList, RefreshControl, } from "react-native";
|
||||||
|
import { useQuery } from "@apollo/client";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ClockedinListItem } from "../time-ticket-items/clockedin-list-item.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
technician: selectTechnician,
|
||||||
|
//currentEmployee: selectCurrentEmployee,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
|
export function EmployeeClockedInList({ technician }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { loading, error, data, refetch } = useQuery(QUERY_ACTIVE_TIME_TICKETS, {
|
||||||
|
variables: {
|
||||||
|
employeeId: technician?.id,
|
||||||
|
},
|
||||||
|
skip: !technician,
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
nextFetchPolicy: "network-only",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (loading) return <ActivityIndicator color="dodgerblue" size="large" />;
|
||||||
|
if (error) return <ErrorDisplay errorMessage={error.message} />;
|
||||||
|
//if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
console.log("QUERY_ACTIVE_TIME_TICKETS data",data)
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
return refetch();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{data.timetickets.length > 0 ? (
|
||||||
|
<View>
|
||||||
|
<Text>You are already clocked in to the following job(s):</Text>
|
||||||
|
<FlatList data={data.timetickets}
|
||||||
|
refreshControl={<RefreshControl refreshing={loading} onRefresh={onRefresh} />}
|
||||||
|
renderItem={(object) => <ClockedinListItem ticket={object.item} />} />
|
||||||
|
</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,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(EmployeeClockedInList);
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Formik } from "formik";
|
import { Formik } from "formik";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { StyleSheet, Text,View, ScrollView } from "react-native";
|
import { StyleSheet, Text, View, ScrollView } from "react-native";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { Button, Dialog, TextInput } from "react-native-paper";
|
import { Button, Dialog, TextInput } from "react-native-paper";
|
||||||
import { CostCenterSelect } from "../Selects/select-cost-center";
|
import { CostCenterSelect } from "../Selects/select-cost-center";
|
||||||
import { JobSearchSelect } from "../Selects/select-job-name";
|
import { JobIdSearchSelect } from "../Selects/select-job-id";
|
||||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||||
import {
|
import {
|
||||||
selectCurrentEmployee,
|
selectCurrentEmployee,
|
||||||
@@ -26,7 +26,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
export function TimeTicketCreate({
|
export function TimeTicketCreate({
|
||||||
currentEmployee,
|
currentEmployee,
|
||||||
currentRatesNCostCenters,
|
currentRatesNCostCenters,
|
||||||
currentBodyshop
|
currentBodyshop,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -35,10 +35,14 @@ export function TimeTicketCreate({
|
|||||||
|
|
||||||
const [currentSCC, setCurrentSCC] = useState(null);
|
const [currentSCC, setCurrentSCC] = useState(null);
|
||||||
const [currentSJob, setCurrentSJob] = useState(null);
|
const [currentSJob, setCurrentSJob] = useState(null);
|
||||||
|
const [currentSJobId, setCurrentSJobId] = useState(null);
|
||||||
|
|
||||||
const wrapperSetCurrentSJobState = useCallback(val => {
|
const wrapperSetCurrentSJobState = useCallback(
|
||||||
setCurrentSJob(val);
|
(val) => {
|
||||||
}, [setCurrentSJob]);
|
setCurrentSJob(val);
|
||||||
|
},
|
||||||
|
[setCurrentSJob]
|
||||||
|
);
|
||||||
|
|
||||||
const showDatePicker = () => {
|
const showDatePicker = () => {
|
||||||
setDatePickerVisibility(true);
|
setDatePickerVisibility(true);
|
||||||
@@ -48,7 +52,7 @@ export function TimeTicketCreate({
|
|||||||
};
|
};
|
||||||
const handleConfirm = (date) => {
|
const handleConfirm = (date) => {
|
||||||
setDate2(date);
|
setDate2(date);
|
||||||
//console.warn("A date has been picked: ", date);
|
//console.war1n("A date has been picked: ", date);
|
||||||
hideDatePicker();
|
hideDatePicker();
|
||||||
};
|
};
|
||||||
const formSubmit = (values) => {
|
const formSubmit = (values) => {
|
||||||
@@ -72,14 +76,23 @@ export function TimeTicketCreate({
|
|||||||
>
|
>
|
||||||
{({ handleChange, handleBlur, handleSubmit, values }) => (
|
{({ handleChange, handleBlur, handleSubmit, values }) => (
|
||||||
<View style={localStyles.topTimeTicketContainer}>
|
<View style={localStyles.topTimeTicketContainer}>
|
||||||
{/* Below will be replaced with a copy of SelectCostCenter but for jobs*/}
|
<JobIdSearchSelect
|
||||||
<JobSearchSelect convertedOnly={!currentBodyshop.tt_allow_post_to_invoiced}
|
currentValue={currentSJobId}
|
||||||
notExported={!currentBodyshop.tt_allow_post_to_invoiced} />
|
onJobSelected={setCurrentSJobId}
|
||||||
|
convertedOnly={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||||
|
notExported={!currentBodyshop.tt_allow_post_to_invoiced}
|
||||||
|
/>
|
||||||
{/* Below will be replaced with a Date Picker*/}
|
{/* Below will be replaced with a Date Picker*/}
|
||||||
{/* <TextInput style={localStyles.input} mode="flat" onChangeText={handleChange("ticketdate")} onBlur={handleBlur("ticketdate")} value={values.ticketdate} label={"Ticket Date"} /> */}
|
{/* <TextInput style={localStyles.input} mode="flat" onChangeText={handleChange("ticketdate")} onBlur={handleBlur("ticketdate")} value={values.ticketdate} label={"Ticket Date"} /> */}
|
||||||
<Button mode="outlined" title="TicketDatePickerButton" onPress={showDatePicker} style={localStyles.dateButton}>
|
<Button
|
||||||
<Text style={localStyles.textForButton}>Ticket Date: {date2.toLocaleDateString()}</Text>
|
mode="outlined"
|
||||||
|
title="TicketDatePickerButton"
|
||||||
|
onPress={showDatePicker}
|
||||||
|
style={localStyles.dateButton}
|
||||||
|
>
|
||||||
|
<Text style={localStyles.textForButton}>
|
||||||
|
Ticket Date: {date2.toLocaleDateString()}
|
||||||
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<DateTimePickerModal
|
<DateTimePickerModal
|
||||||
isVisible={isDatePickerVisible}
|
isVisible={isDatePickerVisible}
|
||||||
@@ -99,7 +112,12 @@ export function TimeTicketCreate({
|
|||||||
label={"Employee"}
|
label={"Employee"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CostCenterSelect currentRatesNCostCenters={currentRatesNCostCenters} currentValue={currentSCC} onValueSelected={setCurrentSCC}/>
|
<CostCenterSelect
|
||||||
|
currentRatesNCostCenters={currentRatesNCostCenters}
|
||||||
|
currentValue={currentSCC}
|
||||||
|
onValueSelected={setCurrentSCC}
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={localStyles.inputStyle}
|
style={localStyles.inputStyle}
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
@@ -149,8 +167,8 @@ const localStyles = StyleSheet.create({
|
|||||||
height: 40,
|
height: 40,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignContent: "center",
|
alignContent: "center",
|
||||||
borderColor:"blue",
|
borderColor: "blue",
|
||||||
borderWidth:0.8,
|
borderWidth: 0.8,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
textForButton: {
|
textForButton: {
|
||||||
|
|||||||
@@ -50,3 +50,36 @@ export const UPDATE_TIME_TICKET = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
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,3 +22,7 @@ export const selectGettingRates = createSelector(
|
|||||||
[selectEmployee],
|
[selectEmployee],
|
||||||
(employee) => employee.gettingRates
|
(employee) => employee.gettingRates
|
||||||
);
|
);
|
||||||
|
export const selectTechnician = createSelector(
|
||||||
|
[selectEmployee],
|
||||||
|
(employee) => employee?.currentEmployee?.technician
|
||||||
|
);
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user