diff --git a/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx b/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx index b1c1ebcc6..0a1476ee5 100644 --- a/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx +++ b/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx @@ -10,7 +10,10 @@ export default function CABCpvrtCalculator({ disabled, form }) { const handleFinish = async (values) => { logImEXEvent("job_ca_bc_pvrt_calculate"); - form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) }); + form.setFieldsValue({ + ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2), + }); + form.setFields([{ name: "ca_bc_pvrt", touched: true }]); setVisibility(false); }; diff --git a/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx b/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx index a5ff684f7..d4c57c403 100644 --- a/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx +++ b/client/src/components/note-upsert-modal/note-upsert-modal.container.jsx @@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({ const [updateNote] = useMutation(UPDATE_NOTE); const { visible, context, actions } = noteUpsertModal; - const { jobId, existingNote } = context; + const { jobId, existingNote, text } = context; const { refetch } = actions; const [form] = Form.useForm(); @@ -45,8 +45,12 @@ export function NoteUpsertModalContainer({ form.setFieldsValue(existingNote); } else if (!existingNote && visible) { form.resetFields(); + + if (text) { + form.setFieldValue("text", text); + } } - }, [existingNote, form, visible]); + }, [existingNote, form, visible, text]); const handleFinish = async (formValues) => { const { relatedros, ...values } = formValues; diff --git a/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx b/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx index 5ff201972..217be5205 100644 --- a/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx @@ -1,12 +1,23 @@ import Icon from "@ant-design/icons"; import { useMutation } from "@apollo/client"; -import { Button, Input, Popover } from "antd"; +import { Button, Input, Popover, Space } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { FaRegStickyNote } from "react-icons/fa"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; -export default function ProductionListColumnProductionNote({ record }) { +import { setModalContext } from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; + +const mapStateToProps = createStructuredSelector({}); + +const mapDispatchToProps = (dispatch) => ({ + setNoteUpsertContext: (context) => + dispatch(setModalContext({ context: context, modal: "noteUpsert" })), +}); + +function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) { const { t } = useTranslation(); const [note, setNote] = useState( @@ -60,12 +71,26 @@ export default function ProductionListColumnProductionNote({ record }) { // onPressEnter={handleSaveNote} autoFocus allowClear + style={{ marginBottom: "1em" }} /> -
- -
+ + } trigger={["click"]} @@ -85,3 +110,8 @@ export default function ProductionListColumnProductionNote({ record }) { ); } + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ProductionListColumnProductionNote); diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx index 138e54228..0fc84af6d 100644 --- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx +++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx @@ -241,9 +241,11 @@ export default function ScoreboardTimeTickets() { ); ret.totalEffieciencyOverPeriod = - (totalActualAndProductive.totalOverPeriod / - totalActualAndProductive.actualTotalOverPeriod) * - 100; + totalActualAndProductive.actualTotalOverPeriod + ? (totalActualAndProductive.totalOverPeriod / + totalActualAndProductive.actualTotalOverPeriod) * + 100 + : 0; roundObject(ret); roundObject(totals); diff --git a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx index e31cec3af..40f3ed32c 100644 --- a/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx +++ b/client/src/components/scoreboard-timetickets/scoreboard-timetickets.stats.component.jsx @@ -116,7 +116,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) { diff --git a/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx b/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx new file mode 100644 index 000000000..ec26a8069 --- /dev/null +++ b/client/src/components/tech-job-statistics/tech-job-statistics.component.jsx @@ -0,0 +1,136 @@ +import { Card, Col, Space, Statistic, Typography } from "antd"; +import { useLocation } from "react-router-dom"; +import queryString from "query-string"; +import moment from "moment"; +import { useQuery } from "@apollo/client"; +import { QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE } from "../../graphql/timetickets.queries"; +import { createStructuredSelector } from "reselect"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { connect } from "react-redux"; +import AlertComponent from "../alert/alert.component"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import { useMemo } from "react"; + +const { Title } = Typography; + +const mapStateToProps = createStructuredSelector({ + technician: selectTechnician, +}); +const mapDispatchToProps = (dispatch) => ({}); + +const TechJobStatistics = ({ technician }) => { + const searchParams = queryString.parse(useLocation().search); + const { start, end } = searchParams; + + const startDate = start + ? moment(start) + : moment().startOf("week").subtract(7, "days"); + const endDate = end ? moment(end) : moment().endOf("week"); + + const { loading, error, data } = useQuery( + QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE, + { + variables: { + start: startDate.format("YYYY-MM-DD"), + end: endDate.format("YYYY-MM-DD"), + fixedStart: moment().startOf("month").format("YYYY-MM-DD"), + fixedEnd: moment().endOf("month").format("YYYY-MM-DD"), + employeeid: technician.id, + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + } + ); + + const totals = useMemo(() => { + if (data && data.timetickets && data.fixedperiod) { + const week = data.timetickets.reduce( + (acc, val) => { + acc.productivehrs = acc.productivehrs + val.productivehrs; + acc.actualhrs = acc.actualhrs + val.actualhrs; + return acc; + }, + { productivehrs: 0, actualhrs: 0 } + ); + + const month = data.fixedperiod.reduce( + (acc, val) => { + acc.productivehrs = acc.productivehrs + val.productivehrs; + acc.actualhrs = acc.actualhrs + val.actualhrs; + return acc; + }, + { productivehrs: 0, actualhrs: 0 } + ); + + return { + week, + month, + }; + } + + return { + week: { productivehrs: 0, actualhrs: 0 }, + month: { productivehrs: 0, actualhrs: 0 }, + }; + }, [data]); + + if (loading) return ; + if (error) return ; + + return ( + + + + This Week + + + + + + + + This Month + + + + + + + + + ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics); diff --git a/client/src/graphql/timetickets.queries.js b/client/src/graphql/timetickets.queries.js index fd593a003..e6f04868f 100644 --- a/client/src/graphql/timetickets.queries.js +++ b/client/src/graphql/timetickets.queries.js @@ -59,6 +59,81 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql` } `; +export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql` + query QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE( + $employeeid: uuid! + $start: date! + $end: date! + $fixedStart: date! + $fixedEnd: date! + ) { + timetickets( + where: { + date: { _gte: $start, _lte: $end } + employeeid: { _eq: $employeeid } + } + order_by: { date: desc_nulls_first } + ) { + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + date + id + rate + productivehrs + memo + jobid + flat_rate + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } + } + fixedperiod: timetickets( + where: { + date: { _gte: $fixedStart, _lte: $fixedEnd } + employeeid: { _eq: $employeeid } + } + order_by: { date: desc_nulls_first } + ) { + actualhrs + ciecacode + clockoff + clockon + cost_center + created_at + date + id + rate + productivehrs + memo + jobid + flat_rate + job { + id + ro_number + } + employeeid + employee { + id + employee_number + first_name + last_name + } + } + } +`; + export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql` query QUERY_TIME_TICKETS_IN_RANGE_SB( $start: date! 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 index 092510b02..0d64068eb 100644 --- a/client/src/pages/tech-job-clock/tech-job-clock.component.jsx +++ b/client/src/pages/tech-job-clock/tech-job-clock.component.jsx @@ -2,10 +2,12 @@ import { Divider } from "antd"; 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"; +import TechJobStatistics from "../../components/tech-job-statistics/tech-job-statistics.component"; export default function TechClockComponent() { return (
+