Framework for entering time tickets BOD-183 BOD-185

This commit is contained in:
Patrick Fic
2020-07-02 15:01:44 -07:00
parent 044fb4b9e0
commit c89e0565a0
11 changed files with 224 additions and 18 deletions

View File

@@ -4,7 +4,14 @@ const { Option } = Select;
//To be used as a form element only.
const JobSearchSelect = ({ value, onChange, options, onBlur, disabled }) => {
const JobSearchSelect = ({
value,
onChange,
options,
onBlur,
disabled,
loading,
}) => {
const [option, setOption] = useState(value);
useEffect(() => {
@@ -22,6 +29,7 @@ const JobSearchSelect = ({ value, onChange, options, onBlur, disabled }) => {
style={{
width: 300,
}}
loading={loading}
onChange={setOption}
optionFilterProp="children"
onBlur={onBlur}

View File

@@ -0,0 +1,41 @@
import { useQuery } from "@apollo/react-hooks";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { ACTIVE_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import { useTranslation } from "react-i18next";
import { Form } from "antd";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function TechClockInComponent({ form, bodyshop }) {
const { t } = useTranslation();
const { loading, data } = useQuery(ACTIVE_JOBS_FOR_AUTOCOMPLETE, {
variables: {
statuses: bodyshop.md_ro_statuses.open_statuses || ["Open"],
},
});
return (
<div>
<Form.Item
name="jobid"
label={t("jobs.fields.ro_number")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<JobSearchSelect loading={loading} options={data ? data.jobs : []} />
</Form.Item>
</div>
);
}
export default connect(mapStateToProps, null)(TechClockInComponent);

View File

@@ -0,0 +1,44 @@
import { Form, Button } from "antd";
import React from "react";
import { useQuery } from "react-apollo";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import TechClockInComponent from "./tech-clock-in.component";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
export function TechClockInContainer({ technician }) {
const { loading, error, data } = useQuery(QUERY_ACTIVE_TIME_TICKETS);
const [form] = Form.useForm();
const { t } = useTranslation();
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
console.log("data", data);
if (data.timetickets.length > 0) {
return <div>already clock into a job.</div>;
}
const handleFinish = (values) => {
console.log("values", values);
};
return (
<div>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<TechClockInComponent form={form} />
<Button type="primary" htmlType="submit">
{t("timetickets.actions.clockon")}
</Button>
</Form>
</div>
);
}
export default connect(mapStateToProps, null)(TechClockInContainer);

View File

@@ -1,6 +1,6 @@
import { PrinterFilled } from "@ant-design/icons";
import { useQuery } from "@apollo/react-hooks";
import { Button, Col, Drawer, Grid, PageHeader, Row, Tag } from "antd";
import { Button, Col, Drawer, Grid, PageHeader, Row, Tag, Tabs } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -12,6 +12,9 @@ import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import OwnerTagPopoverComponent from "../owner-tag-popover/owner-tag-popover.component";
import VehicleTagPopoverComponent from "../vehicle-tag-popover/vehicle-tag-popover.component";
import JobLinesContainer from "../job-detail-lines/job-lines.container";
import JobsDocumentsGalleryContainer from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobNotesContainer from "../jobs-notes/jobs-notes.container";
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
@@ -23,7 +26,7 @@ const colBreakPoints = {
span: 24,
},
sm: {
span: 12,
span: 8,
},
};
@@ -115,10 +118,21 @@ export function JobDetailCards({ setPrintCenterContext }) {
}
>
<Row gutter={[16, 16]}>
<Col {...colBreakPoints}></Col>
<Col {...colBreakPoints}></Col>
<Col {...colBreakPoints}></Col>
<Col {...colBreakPoints}> What would be good to have here?</Col>
<Col {...colBreakPoints}> What would be good to have here?</Col>
<Col {...colBreakPoints}> What would be good to have here?</Col>
</Row>
<Tabs size="large">
<Tabs.TabPane key="lines" tab={t("jobs.labels.lines")}>
<JobLinesContainer jobId={searchParams.selected} />
</Tabs.TabPane>
<Tabs.TabPane key="documents" tab={t("jobs.labels.documents")}>
<JobsDocumentsGalleryContainer jobId={searchParams.selected} />
</Tabs.TabPane>
<Tabs.TabPane key="notes" tab={t("jobs.labels.notes")}>
<JobNotesContainer jobId={searchParams.selected} />
</Tabs.TabPane>
</Tabs>
</PageHeader>
) : null}
</Drawer>

View File

@@ -52,7 +52,7 @@ export const QUERY_INVOICES_FOR_EXPORT = gql`
export const QUERY_PAYMENTS_FOR_EXPORT = gql`
query QUERY_PAYMENTS_FOR_EXPORT {
payments(
where: { exportedat: { _eq: null } }
where: { exportedat: { _is_null: true } }
order_by: { created_at: desc }
) {
id

View File

@@ -371,6 +371,72 @@ export const QUERY_JOB_CARD_DETAILS = gql`
}
`;
export const QUERY_TECH_JOB_DETAILS = gql`
query QUERY_TECH_JOB_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
ownr_fn
ownr_ln
v_model_yr
v_make_desc
v_model_desc
v_color
plate_no
actual_completion
actual_delivery
actual_in
est_number
id
ins_co_nm
clm_no
status
job_totals
area_of_damage
ro_number
scheduled_completion
scheduled_in
scheduled_delivery
date_invoiced
date_open
date_exported
date_closed
date_scheduled
date_estimated
joblines {
id
unq_seq
line_ind
tax_part
line_desc
prt_dsmk_p
prt_dsmk_m
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
}
notes {
id
text
critical
private
created_at
}
updated_at
documents(order_by: { created_at: desc }) {
id
key
}
}
}
`;
export const UPDATE_JOB = gql`
mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) {
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {

View File

@@ -46,3 +46,18 @@ export const UPDATE_TIME_TICKET = gql`
}
}
`;
export const QUERY_ACTIVE_TIME_TICKETS = gql`
query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) {
timetickets(
where: {
_and: { clockoff: { _is_null: true }, employeeid: { _eq: $employeeId } }
}
) {
id
job {
ro_number
}
}
}
`;

View File

@@ -27,6 +27,10 @@ const ProductionListPage = lazy(() =>
const ProductionBoardPage = lazy(() =>
import("../production-board/production-board.container")
);
const TechClockIn = lazy(() =>
import("../../components/tech-clock-in/tech-clock-in.container")
);
const { Content } = Layout;
const mapStateToProps = createStructuredSelector({
@@ -74,6 +78,11 @@ export function TechPage({ technician, match }) {
exact
path={`${match.path}/list`}
component={ProductionListPage}
/>{" "}
<Route
exact
path={`${match.path}/clockin`}
component={TechClockIn}
/>
<Route
exact

View File

@@ -1,6 +1,11 @@
import TechActionTypes from "./tech.types";
const INITIAL_STATE = {
technician: null,
//technician: null,
technician: {
employee_number: "101",
first_name: "***HARDCODED",
last_name: "IN REDUCER***",
},
loginLoading: false,
loginError: null,
};

View File

@@ -22,10 +22,12 @@ import {
} from "./user.actions";
import UserActionTypes from "./user.types";
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
}
export function* signInWithEmail({ payload: { email, password } }) {
try {
const { user } = yield auth.signInWithEmailAndPassword(email, password);
LogRocket.identify(user.email);
yield put(
signInSuccess({
@@ -40,9 +42,9 @@ export function* signInWithEmail({ payload: { email, password } }) {
yield put(signInFailure(error));
}
}
//This is the listener fo rthe call, and when it finds it, it triggers somethign else.
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
export function* onCheckUserSession() {
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
}
export function* isUserAuthenticated() {
try {
@@ -66,8 +68,9 @@ export function* isUserAuthenticated() {
yield put(signInFailure(error));
}
}
export function* onCheckUserSession() {
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
}
export function* signOutStart() {
try {
@@ -78,9 +81,7 @@ export function* signOutStart() {
yield put(signOutFailure(error.message));
}
}
export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
}
export function* onUpdateUserDetails() {
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
}
@@ -140,7 +141,7 @@ export function* checkInstanceIdSaga({ payload: uid }) {
} catch (error) {
console.log("error", error);
//TODO error handling
}
}
}
export function* onSignInSuccess() {
@@ -148,6 +149,7 @@ export function* onSignInSuccess() {
}
export function* signInSuccessSaga({ payload }) {
LogRocket.identify(user.email);
yield put(setInstanceId(payload.uid));
}

View File

@@ -181,6 +181,8 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
last_name
first_name
employee_number
id
cost_center
pin
}
}`;