diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index d65185b71..ff388214d 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -13691,48 +13691,6 @@ tech - - clockin - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - clockout - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - home false @@ -13754,6 +13712,48 @@ + + jobclockin + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + jobclockout + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + joblookup false @@ -16860,6 +16860,48 @@ actions + + clockin + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + clockout + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + enter false @@ -16883,6 +16925,53 @@ + + errors + + + clockingin + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + clockingout + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + fields @@ -16928,6 +17017,48 @@ + + clockoff + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + clockon + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + cost_center false @@ -17038,6 +17169,48 @@ labels + + alreadyclockedon + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + clockintojob + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + edit false @@ -17124,6 +17297,53 @@ + + successes + + + clockedin + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + clockedout + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + diff --git a/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx b/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx index e7e6aea22..93ded43a6 100644 --- a/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx +++ b/client/src/components/jobs-detail-labor/jobs-detail-labor.component.jsx @@ -3,19 +3,23 @@ import LaborAllocationsTableComponent from "../labor-allocations-table/labor-all import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; import { useTranslation } from "react-i18next"; + export default function JobsDetailLaborContainer({ jobId, joblines, timetickets, refetch, loading, + techConsole, }) { const { t } = useTranslation(); return (
- - {t("timetickets.actions.enter")} - + {techConsole ? null : ( + + {t("timetickets.actions.enter")} + + )}
); diff --git a/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx b/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx index 51f631a26..22c4f16d7 100644 --- a/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx +++ b/client/src/components/jobs-detail-labor/jobs-detail-labor.container.jsx @@ -4,7 +4,7 @@ import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import AlertComponent from "../alert/alert.component"; import JobsDetailLaborComponent from "./jobs-detail-labor.component"; -export default function JobsDetailLaborContainer({ jobId }) { +export default function JobsDetailLaborContainer({ jobId, techConsole }) { const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, { variables: { id: jobId }, skip: !!!jobId, @@ -19,6 +19,7 @@ export default function JobsDetailLaborContainer({ jobId }) { timetickets={data ? data.timetickets : []} joblines={data ? data.joblines : []} refetch={refetch} + techConsole={techConsole} /> ); } diff --git a/client/src/components/tech-clock-in/tech-clock-in.container.jsx b/client/src/components/tech-clock-in/tech-clock-in.container.jsx deleted file mode 100644 index 277986354..000000000 --- a/client/src/components/tech-clock-in/tech-clock-in.container.jsx +++ /dev/null @@ -1,44 +0,0 @@ -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 ; - if (error) return ; - - console.log("data", data); - - if (data.timetickets.length > 0) { - return
already clock into a job.
; - } - - const handleFinish = (values) => { - console.log("values", values); - }; - - return ( -
-
- - - -
- ); -} -export default connect(mapStateToProps, null)(TechClockInContainer); diff --git a/client/src/components/tech-clock-in/tech-clock-in.component.jsx b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx similarity index 69% rename from client/src/components/tech-clock-in/tech-clock-in.component.jsx rename to client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx index 61c916683..e8a8c0465 100644 --- a/client/src/components/tech-clock-in/tech-clock-in.component.jsx +++ b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.component.jsx @@ -1,17 +1,17 @@ import { useQuery } from "@apollo/react-hooks"; +import { Form } from "antd"; import React from "react"; +import { useTranslation } from "react-i18next"; 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"; +import JobsDetailLaborContainer from "../jobs-detail-labor/jobs-detail-labor.container"; + const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - - }); + bodyshop: selectBodyshop, +}); export function TechClockInComponent({ form, bodyshop }) { const { t } = useTranslation(); @@ -35,6 +35,22 @@ export function TechClockInComponent({ form, bodyshop }) { > + + + prevValues.jobid !== curValues.jobid + } + > + {() => { + if (!form.getFieldValue("jobid")) return null; + return ( + + ); + }} + ); } diff --git a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx new file mode 100644 index 000000000..9d6c823fb --- /dev/null +++ b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx @@ -0,0 +1,76 @@ +import { Button, Form, notification, Card } from "antd"; +import React from "react"; +import { useMutation } from "react-apollo"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import TechClockInComponent from "./tech-job-clock-in-form.component"; + +const mapStateToProps = createStructuredSelector({ + technician: selectTechnician, + bodyshop: selectBodyshop, +}); + +export function TechClockInContainer({ technician, bodyshop }) { + const [form] = Form.useForm(); + const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, { + refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"], + }); + const { t } = useTranslation(); + + const handleFinish = async (values) => { + const result = await insertTimeTicket({ + variables: { + timeTicketInput: { + employeeid: technician.id, + date: new Date(), + clockon: new Date(), + jobid: values.jobid, + cost_center: technician.cost_center, + ciecacode: Object.keys( + bodyshop.md_responsibility_centers.defaults.costs + ).find((key) => { + console.log( + "key", + key, + bodyshop.md_responsibility_centers.defaults.costs[key] + ); + return ( + bodyshop.md_responsibility_centers.defaults.costs[key] === + values.cost_center + ); + }), + }, + }, + }); + + if (!!result.errors) { + notification["error"]({ + message: t("timetickets.errors.clockingin", { + message: JSON.stringify(result.errors), + }), + }); + } else { + notification["success"]({ + message: t("timetickets.successes.clockedin"), + }); + } + }; + + return ( +
+ +
+ + + +
+
+ ); +} +export default connect(mapStateToProps, null)(TechClockInContainer); diff --git a/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx new file mode 100644 index 000000000..961cebae3 --- /dev/null +++ b/client/src/components/tech-job-clock-out-button/tech-job-clock-out-button.component.jsx @@ -0,0 +1,130 @@ +import { useMutation } from "@apollo/react-hooks"; +import { + Button, + Card, + Form, + InputNumber, + notification, + Popover, + Select, +} from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries"; + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, + technician: selectTechnician, +}); + +export function TechClockOffButton({ + bodyshop, + technician, + timeTicketId, + completedCallback, + otherBtnProps, +}) { + const [loading, setLoading] = useState(false); + const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET); + const [form] = Form.useForm(); + + const { t } = useTranslation(); + + const handleFinish = async (values) => { + setLoading(true); + const result = await updateTimeticket({ + variables: { + timeticketId: timeTicketId, + timeticket: { clockoff: new Date(), ...values }, + }, + }); + + if (!!result.errors) { + notification["error"]({ + message: t("timetickets.errors.clockingout", { + message: JSON.stringify(result.errors), + }), + }); + } else { + notification["success"]({ + message: t("timetickets.successes.clockedout"), + }); + } + setLoading(false); + if (completedCallback) completedCallback(); + }; + + const overlay = ( + +
+
+ + + + + + + + + + +
+
+
+ ); + + return ( + + + + ); +} +export default connect(mapStateToProps, null)(TechClockOffButton); diff --git a/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx b/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx new file mode 100644 index 000000000..bda243583 --- /dev/null +++ b/client/src/components/tech-job-clocked-in-list/tech-job-clocked-in-list.component.jsx @@ -0,0 +1,93 @@ +import { Card, List, Typography } from "antd"; +import React from "react"; +import { useQuery } from "react-apollo"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import AlertComponent from "../alert/alert.component"; +import DataLabel from "../data-label/data-label.component"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +const mapStateToProps = createStructuredSelector({ + technician: selectTechnician, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export function TechClockedInList({ technician }) { + const { loading, error, data, refetch } = useQuery( + QUERY_ACTIVE_TIME_TICKETS, + { + variables: { + employeeId: technician.id, + }, + } + ); + + const { t } = useTranslation(); + + if (loading) return ; + if (error) return ; + + return ( +
+ {data.timetickets.length > 0 ? ( +
+ + {t("timetickets.labels.alreadyclockedon")} + + ( + + + {`${ticket.job.ro_number || ticket.job.est_number} ${ + ticket.job.ownr_fn || "" + } ${ticket.job.ownr_ln || ""} ${ + ticket.job.ownr_co_nm || "" + }`} + + } + actions={[ + , + ]} + > +
+ {` + ${ticket.job.v_model_yr || ""} ${ + ticket.job.v_make_desc || "" + } ${ticket.job.v_model_desc || ""}`} +
+ + {ticket.clockon} + +
+
+ )} + >
+
+ ) : null} +
+ ); +} +export default connect(mapStateToProps, mapDispatchToProps)(TechClockedInList); diff --git a/client/src/components/tech-sider/tech-sider.component.jsx b/client/src/components/tech-sider/tech-sider.component.jsx index 3839fe849..9c118914a 100644 --- a/client/src/components/tech-sider/tech-sider.component.jsx +++ b/client/src/components/tech-sider/tech-sider.component.jsx @@ -43,7 +43,7 @@ export function TechSider({ technician, techLogout }) { disabled={!!!technician} icon={} > - {t("menus.tech.clockin")} + {t("menus.tech.jobclockin")} ( - - {t("general.actions.edit")} - - ), + render: (text, record) => { + if (techConsole) return null; + return ( + + {t("general.actions.edit")} + + ); + }, }, ]; diff --git a/client/src/graphql/timetickets.queries.js b/client/src/graphql/timetickets.queries.js index 58d27bad4..c39b509d4 100644 --- a/client/src/graphql/timetickets.queries.js +++ b/client/src/graphql/timetickets.queries.js @@ -26,6 +26,13 @@ export const INSERT_NEW_TIME_TICKET = gql` insert_timetickets(objects: $timeTicketInput) { returning { id + clockon + clockoff + employeeid + productivehrs + actualhrs + ciecacode + date } } } @@ -42,6 +49,16 @@ export const UPDATE_TIME_TICKET = gql` ) { returning { id + clockon + clockoff + employeeid + productivehrs + actualhrs + ciecacode + created_at + updated_at + jobid + date } } } @@ -51,11 +68,24 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql` query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) { timetickets( where: { - _and: { clockoff: { _is_null: true }, employeeid: { _eq: $employeeId } } + _and: { + clockoff: { _is_null: true } + employeeid: { _eq: $employeeId } + clockon: { _is_null: false } + } } ) { id + clockon job { + id + est_number + ownr_fn + ownr_ln + ownr_co_nm + v_model_desc + v_make_desc + v_model_yr ro_number } } diff --git a/client/src/pages/tech-job-clock/tech-job-clock.component.jsx b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx new file mode 100644 index 000000000..db792b98a --- /dev/null +++ b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx @@ -0,0 +1,12 @@ +import React from "react"; +import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container"; +import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component"; + +export default function TechClockComponent() { + return ( +
+ + +
+ ); +} diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx index efaed2e77..5af9d75e5 100644 --- a/client/src/pages/tech/tech.page.component.jsx +++ b/client/src/pages/tech/tech.page.component.jsx @@ -27,8 +27,8 @@ 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 TechJobClockIn = lazy(() => + import("../tech-job-clock/tech-job-clock.component") ); const { Content } = Layout; @@ -81,8 +81,8 @@ export function TechPage({ technician, match }) { />{" "} { + res.status(200).send(new Date()); +};