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" }}
/>
-
-
+ {
+ setVisible(false);
+ setNoteUpsertContext({
+ context: {
+ jobId: record.jobId,
+ text: note,
+ },
+ });
+ }}
+ >
+ Save to Job Notes
+
+
}
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 (