@@ -10,7 +10,10 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
|||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("job_ca_bc_pvrt_calculate");
|
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);
|
setVisibility(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({
|
|||||||
const [updateNote] = useMutation(UPDATE_NOTE);
|
const [updateNote] = useMutation(UPDATE_NOTE);
|
||||||
|
|
||||||
const { visible, context, actions } = noteUpsertModal;
|
const { visible, context, actions } = noteUpsertModal;
|
||||||
const { jobId, existingNote } = context;
|
const { jobId, existingNote, text } = context;
|
||||||
const { refetch } = actions;
|
const { refetch } = actions;
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -45,8 +45,12 @@ export function NoteUpsertModalContainer({
|
|||||||
form.setFieldsValue(existingNote);
|
form.setFieldsValue(existingNote);
|
||||||
} else if (!existingNote && visible) {
|
} else if (!existingNote && visible) {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
form.setFieldValue("text", text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [existingNote, form, visible]);
|
}, [existingNote, form, visible, text]);
|
||||||
|
|
||||||
const handleFinish = async (formValues) => {
|
const handleFinish = async (formValues) => {
|
||||||
const { relatedros, ...values } = formValues;
|
const { relatedros, ...values } = formValues;
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Input, Popover } from "antd";
|
import { Button, Input, Popover, Space } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FaRegStickyNote } from "react-icons/fa";
|
import { FaRegStickyNote } from "react-icons/fa";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
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 { t } = useTranslation();
|
||||||
|
|
||||||
const [note, setNote] = useState(
|
const [note, setNote] = useState(
|
||||||
@@ -60,12 +71,26 @@ export default function ProductionListColumnProductionNote({ record }) {
|
|||||||
// onPressEnter={handleSaveNote}
|
// onPressEnter={handleSaveNote}
|
||||||
autoFocus
|
autoFocus
|
||||||
allowClear
|
allowClear
|
||||||
|
style={{ marginBottom: "1em" }}
|
||||||
/>
|
/>
|
||||||
<div>
|
<Space>
|
||||||
<Button onClick={handleSaveNote}>
|
<Button onClick={handleSaveNote} type="primary">
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setVisible(false);
|
||||||
|
setNoteUpsertContext({
|
||||||
|
context: {
|
||||||
|
jobId: record.jobId,
|
||||||
|
text: note,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save to Job Notes
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
@@ -85,3 +110,8 @@ export default function ProductionListColumnProductionNote({ record }) {
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ProductionListColumnProductionNote);
|
||||||
|
|||||||
@@ -241,9 +241,11 @@ export default function ScoreboardTimeTickets() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ret.totalEffieciencyOverPeriod =
|
ret.totalEffieciencyOverPeriod =
|
||||||
(totalActualAndProductive.totalOverPeriod /
|
totalActualAndProductive.actualTotalOverPeriod
|
||||||
totalActualAndProductive.actualTotalOverPeriod) *
|
? (totalActualAndProductive.totalOverPeriod /
|
||||||
100;
|
totalActualAndProductive.actualTotalOverPeriod) *
|
||||||
|
100
|
||||||
|
: 0;
|
||||||
|
|
||||||
roundObject(ret);
|
roundObject(ret);
|
||||||
roundObject(totals);
|
roundObject(totals);
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
|||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("scoreboard.labels.efficiencyoverperiod")}
|
title={t("scoreboard.labels.efficiencyoverperiod")}
|
||||||
value={`${data.totalEffieciencyOverPeriod}%`}
|
value={`${data.totalEffieciencyOverPeriod || 0}%`}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -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 <LoadingSpinner />;
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title="Productive Hours Statistics">
|
||||||
|
<Space size={100}>
|
||||||
|
<Col>
|
||||||
|
<Title level={5}>This Week</Title>
|
||||||
|
<Space size={20}>
|
||||||
|
<Statistic
|
||||||
|
title="Productive Hours"
|
||||||
|
value={totals.week.productivehrs.toFixed(2)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title="Actual Hours"
|
||||||
|
value={totals.week.actualhrs.toFixed(2)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title="Efficiency %"
|
||||||
|
value={
|
||||||
|
totals.week.actualhrs
|
||||||
|
? `${(
|
||||||
|
(totals.week.productivehrs / totals.week.actualhrs) *
|
||||||
|
100
|
||||||
|
).toFixed(2)}%`
|
||||||
|
: "0%"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Title level={5}>This Month</Title>
|
||||||
|
<Space size={20}>
|
||||||
|
<Statistic
|
||||||
|
title="Productive Hours"
|
||||||
|
value={totals.month.productivehrs.toFixed(2)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title="Actual Hours"
|
||||||
|
value={totals.month.actualhrs.toFixed(2)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title="Efficiency %"
|
||||||
|
value={
|
||||||
|
totals.month.actualhrs
|
||||||
|
? `${(
|
||||||
|
(totals.month.productivehrs / totals.month.actualhrs) *
|
||||||
|
100
|
||||||
|
).toFixed(2)}%`
|
||||||
|
: "0%"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics);
|
||||||
@@ -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`
|
export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||||
query QUERY_TIME_TICKETS_IN_RANGE_SB(
|
query QUERY_TIME_TICKETS_IN_RANGE_SB(
|
||||||
$start: date!
|
$start: date!
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { Divider } from "antd";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container";
|
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 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() {
|
export default function TechClockComponent() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<TechJobStatistics />
|
||||||
<TechClockInFormContainer />
|
<TechClockInFormContainer />
|
||||||
<Divider />
|
<Divider />
|
||||||
<TechClockedInList />
|
<TechClockedInList />
|
||||||
|
|||||||
Reference in New Issue
Block a user