IO-2206 Refine claim tasks modal to add validations and insertions.
This commit is contained in:
@@ -256,7 +256,7 @@ export function JobsDetailHeaderActions({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTimeTicketTaskContext({
|
setTimeTicketTaskContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: { jobId: job.id },
|
context: { jobid: job.id },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { DeleteFilled } from "@ant-design/icons";
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Space,
|
||||||
Card,
|
Card,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
@@ -383,16 +384,18 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
|||||||
>
|
>
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<DeleteFilled
|
<Space align="center">
|
||||||
onClick={() => {
|
<DeleteFilled
|
||||||
remove(field.name);
|
onClick={() => {
|
||||||
}}
|
remove(field.name);
|
||||||
/>
|
}}
|
||||||
<FormListMoveArrows
|
/>
|
||||||
move={move}
|
<FormListMoveArrows
|
||||||
index={index}
|
move={move}
|
||||||
total={fields.length}
|
index={index}
|
||||||
/>
|
total={fields.length}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useQuery } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Form,
|
Form,
|
||||||
@@ -9,17 +10,16 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
Typography,
|
Typography,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
@@ -30,20 +30,20 @@ export default connect(
|
|||||||
)(TimeTicketListTeamPay);
|
)(TimeTicketListTeamPay);
|
||||||
|
|
||||||
export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
||||||
const { refetch } = actions;
|
//const { refetch } = actions;
|
||||||
const { jobId } = context;
|
const { jobId } = context;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { loading, data: lineTicketData } = useQuery(
|
const {
|
||||||
GET_JOB_INFO_DRAW_CALCULATIONS,
|
//loading,
|
||||||
{
|
data: lineTicketData,
|
||||||
variables: { id: jobId },
|
} = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||||
skip: !jobId,
|
variables: { id: jobId },
|
||||||
fetchPolicy: "network-only",
|
skip: !jobId,
|
||||||
nextFetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
}
|
nextFetchPolicy: "network-only",
|
||||||
);
|
});
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@@ -124,7 +124,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
|||||||
<Form.Item shouldUpdate noStyle>
|
<Form.Item shouldUpdate noStyle>
|
||||||
{({ getFieldsValue }) => {
|
{({ getFieldsValue }) => {
|
||||||
const formData = getFieldsValue();
|
const formData = getFieldsValue();
|
||||||
console.log("🚀 ~ file: time-ticket-list-team-pay.component.jsx:127 ~ TimeTicketListTeamPay ~ formData:", formData)
|
|
||||||
let data = [];
|
let data = [];
|
||||||
let eligibleHours = 0;
|
let eligibleHours = 0;
|
||||||
const theTeam = Teams.find((team) => team.name === formData.team);
|
const theTeam = Teams.find((team) => team.name === formData.team);
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
import { EditFilled } from "@ant-design/icons";
|
import { EditFilled } from "@ant-design/icons";
|
||||||
import { Card, Space, Table } from "antd";
|
import { Button, Card, Space, Table } from "antd";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import {
|
import {
|
||||||
selectAuthLevel,
|
selectAuthLevel,
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import RbacWrapper, {
|
import RbacWrapper, {
|
||||||
HasRbacAccess,
|
HasRbacAccess,
|
||||||
} from "../rbac-wrapper/rbac-wrapper.component";
|
} from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||||
import TimeTicketListTeamPay from "./time-ticket-list-team-pay.component";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
authLevel: selectAuthLevel,
|
authLevel: selectAuthLevel,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
setTimeTicketTaskContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
|
export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
|
||||||
|
|
||||||
export function TimeTicketList({
|
export function TimeTicketList({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
|
setTimeTicketTaskContext,
|
||||||
authLevel,
|
authLevel,
|
||||||
disabled,
|
disabled,
|
||||||
loading,
|
loading,
|
||||||
@@ -261,10 +263,23 @@ export function TimeTicketList({
|
|||||||
title={t("timetickets.labels.timetickets")}
|
title={t("timetickets.labels.timetickets")}
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<TimeTicketListTeamPay
|
{
|
||||||
actions={{ refetch }}
|
// <TimeTicketListTeamPay
|
||||||
context={{ jobId: jobId }}
|
// actions={{ refetch }}
|
||||||
/>
|
// context={{ jobId: jobId }}
|
||||||
|
// />
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setTimeTicketTaskContext();
|
||||||
|
setTimeTicketTaskContext({
|
||||||
|
actions: {},
|
||||||
|
context: { jobid: jobId },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("timetickets.actions.claimtasks")}
|
||||||
|
</Button>
|
||||||
{jobId &&
|
{jobId &&
|
||||||
(techConsole ? null : (
|
(techConsole ? null : (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
|
|||||||
@@ -184,51 +184,51 @@ export function TimeTicketModalComponent({
|
|||||||
name="productivehrs"
|
name="productivehrs"
|
||||||
rules={[
|
rules={[
|
||||||
({ getFieldValue }) => ({
|
({ getFieldValue }) => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
if (!bodyshop.tt_enforce_hours_for_tech_console) {
|
if (!bodyshop.tt_enforce_hours_for_tech_console) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!value ||
|
!value ||
|
||||||
getFieldValue("cost_center") === null ||
|
getFieldValue("cost_center") === null ||
|
||||||
!lineTicketData
|
!lineTicketData
|
||||||
)
|
)
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
||||||
//Check the cost center,
|
//Check the cost center,
|
||||||
const totals = CalculateAllocationsTotals(
|
const totals = CalculateAllocationsTotals(
|
||||||
bodyshop,
|
bodyshop,
|
||||||
lineTicketData.joblines,
|
lineTicketData.joblines,
|
||||||
lineTicketData.timetickets,
|
lineTicketData.timetickets,
|
||||||
lineTicketData.jobs_by_pk.lbr_adjustments
|
lineTicketData.jobs_by_pk.lbr_adjustments
|
||||||
);
|
|
||||||
|
|
||||||
const fieldTypeToCheck =
|
|
||||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
|
||||||
? "mod_lbr_ty"
|
|
||||||
: "cost_center";
|
|
||||||
|
|
||||||
const costCenterDiff =
|
|
||||||
Math.round(
|
|
||||||
totals.find(
|
|
||||||
(total) =>
|
|
||||||
total[fieldTypeToCheck] ===
|
|
||||||
getFieldValue("cost_center")
|
|
||||||
)?.difference * 10
|
|
||||||
) / 10;
|
|
||||||
|
|
||||||
if (value > costCenterDiff)
|
|
||||||
return Promise.reject(
|
|
||||||
t(
|
|
||||||
"timetickets.validation.hoursenteredmorethanavailable"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
else {
|
|
||||||
return Promise.resolve();
|
const fieldTypeToCheck =
|
||||||
}
|
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||||
},
|
? "mod_lbr_ty"
|
||||||
}),
|
: "cost_center";
|
||||||
{
|
|
||||||
|
const costCenterDiff =
|
||||||
|
Math.round(
|
||||||
|
totals.find(
|
||||||
|
(total) =>
|
||||||
|
total[fieldTypeToCheck] ===
|
||||||
|
getFieldValue("cost_center")
|
||||||
|
)?.difference * 10
|
||||||
|
) / 10;
|
||||||
|
|
||||||
|
if (value > costCenterDiff)
|
||||||
|
return Promise.reject(
|
||||||
|
t(
|
||||||
|
"timetickets.validation.hoursenteredmorethanavailable"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
required:
|
required:
|
||||||
form.getFieldValue("cost_center") !==
|
form.getFieldValue("cost_center") !==
|
||||||
"timetickets.labels.shift",
|
"timetickets.labels.shift",
|
||||||
@@ -371,7 +371,12 @@ export function TimeTicketModalComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
|
export function LaborAllocationContainer({
|
||||||
|
jobid,
|
||||||
|
loading,
|
||||||
|
lineTicketData,
|
||||||
|
hideTimeTickets = false,
|
||||||
|
}) {
|
||||||
if (loading) return <LoadingSkeleton />;
|
if (loading) return <LoadingSkeleton />;
|
||||||
if (!lineTicketData) return null;
|
if (!lineTicketData) return null;
|
||||||
return (
|
return (
|
||||||
@@ -382,12 +387,13 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData }) {
|
|||||||
timetickets={lineTicketData.timetickets}
|
timetickets={lineTicketData.timetickets}
|
||||||
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
||||||
/>
|
/>
|
||||||
|
{!hideTimeTickets && (
|
||||||
<TimeTicketList
|
<TimeTicketList
|
||||||
loading={loading}
|
loading={loading}
|
||||||
timetickets={lineTicketData.timetickets}
|
timetickets={lineTicketData.timetickets}
|
||||||
techConsole
|
techConsole
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
import { Button, Form, Input, InputNumber, Radio } from "antd";
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Row,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -6,9 +17,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
||||||
import EmployeeTeamSearchSelectComponent from "../employee-team-search-select/employee-team-search-select.component";
|
import EmployeeTeamSearchSelectComponent from "../employee-team-search-select/employee-team-search-select.component";
|
||||||
import FormDatePickerComponent from "../form-date-picker/form-date-picker.component";
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||||
|
import TimeTicketsTasksPresets from "../time-ticket-tasks-presets/time-ticket-tasks-presets.component";
|
||||||
|
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -26,17 +40,59 @@ export function TimeTicketTaskModalComponent({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
form,
|
form,
|
||||||
employeeAutoCompleteOptions,
|
employeeAutoCompleteOptions,
|
||||||
|
lineTicketCalled,
|
||||||
|
calculateTimeTickets,
|
||||||
|
lineTicketLoading,
|
||||||
|
lineTicketData,
|
||||||
|
queryJobInfo,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LayoutFormRow grow noDivider>
|
<TimeTicketsTasksPresets
|
||||||
<Form.Item shouldUpdate>
|
form={form}
|
||||||
{() => (
|
calculateTimeTickets={calculateTimeTickets}
|
||||||
|
/>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col lg={12} md={24}>
|
||||||
|
<Form.Item
|
||||||
|
name="jobid"
|
||||||
|
label={t("timetickets.fields.ro_number")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<JobSearchSelectComponent
|
||||||
|
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||||
|
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="employeeid" label={t("timetickets.fields.employee")}>
|
||||||
|
<EmployeeSearchSelectComponent
|
||||||
|
options={employeeAutoCompleteOptions}
|
||||||
|
allowClear
|
||||||
|
onSelect={(value) => {
|
||||||
|
const emps =
|
||||||
|
employeeAutoCompleteOptions &&
|
||||||
|
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
|
||||||
|
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="employeeteamid"
|
||||||
|
label={t("timetickets.fields.employee_team")}
|
||||||
|
>
|
||||||
|
<EmployeeTeamSearchSelectComponent />
|
||||||
|
</Form.Item>
|
||||||
|
<Space wrap>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="jobid"
|
name="hourstype"
|
||||||
label={t("timetickets.fields.ro_number")}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -44,180 +100,277 @@ export function TimeTicketTaskModalComponent({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<JobSearchSelectComponent
|
<Checkbox.Group>
|
||||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
<Space wrap>
|
||||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
<Checkbox value="LAB" style={{ display: "flex" }}>
|
||||||
/>
|
Body
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox value="LAR" style={{ display: "flex" }}>
|
||||||
|
Refinish
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox value="LAM" style={{ display: "flex" }}>
|
||||||
|
Mechanical
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox value="LAF" style={{ display: "flex" }}>
|
||||||
|
Frame
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox value="LAG" style={{ display: "flex" }}>
|
||||||
|
Glass
|
||||||
|
</Checkbox>
|
||||||
|
</Space>
|
||||||
|
</Checkbox.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.Item name="employeeid" label={t("timetickets.fields.employee")}>
|
name="percent"
|
||||||
<EmployeeSearchSelectComponent
|
rules={[
|
||||||
options={employeeAutoCompleteOptions}
|
{
|
||||||
allowClear
|
required: true,
|
||||||
onSelect={(value) => {
|
//message: t("general.validation.required"),
|
||||||
const emps =
|
},
|
||||||
employeeAutoCompleteOptions &&
|
]}
|
||||||
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
|
>
|
||||||
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
|
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
<Button onClick={calculateTimeTickets}>Calculate</Button>
|
||||||
|
</Col>
|
||||||
|
<Col lg={12} md={24}>
|
||||||
|
<Form.Item shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
const data = form.getFieldValue("timetickets");
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
dataSource={data}
|
||||||
|
rowKey={"employeeid"}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.employee"),
|
||||||
|
dataIndex: "employee",
|
||||||
|
key: "employee",
|
||||||
|
render: (text, record) => {
|
||||||
|
const emp = bodyshop.employees.find(
|
||||||
|
(e) => e.id === record.employeeid
|
||||||
|
);
|
||||||
|
return `${emp?.first_name} ${emp?.last_name}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.cost_center"),
|
||||||
|
dataIndex: "cost_center",
|
||||||
|
key: "cost_center",
|
||||||
|
|
||||||
|
render: (text, record) =>
|
||||||
|
record.cost_center === "timetickets.labels.shift"
|
||||||
|
? t(record.cost_center)
|
||||||
|
: record.cost_center,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("timetickets.fields.productivehrs"),
|
||||||
|
dataIndex: "productivehrs",
|
||||||
|
key: "productivehrs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Percentage",
|
||||||
|
dataIndex: "percentage",
|
||||||
|
key: "percentage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Rate",
|
||||||
|
dataIndex: "rate",
|
||||||
|
key: "rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pay",
|
||||||
|
dataIndex: "pay",
|
||||||
|
key: "pay",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="employeeteamid"
|
|
||||||
label={t("timetickets.fields.employee_team")}
|
|
||||||
>
|
|
||||||
<EmployeeTeamSearchSelectComponent />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="hourstype"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Radio.Group>
|
|
||||||
<Radio value="LAB">Body</Radio>
|
|
||||||
<Radio value="LAR">Refinish</Radio>
|
|
||||||
<Radio value="LAM">Mechanical</Radio>
|
|
||||||
<Radio value="LAF">Frame</Radio>
|
|
||||||
<Radio value="LAG">Glass</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
<Form.List
|
||||||
name="percent"
|
name={["timetickets"]}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
validator: (rule, value) => {
|
||||||
//message: t("general.validation.required"),
|
//Check the cost center,
|
||||||
},
|
const totals = CalculateAllocationsTotals(
|
||||||
]}
|
bodyshop,
|
||||||
>
|
lineTicketData.joblines,
|
||||||
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
lineTicketData.timetickets,
|
||||||
</Form.Item>
|
lineTicketData.jobs_by_pk.lbr_adjustments
|
||||||
</LayoutFormRow>
|
);
|
||||||
|
|
||||||
<Form.List name={["timetickets"]}>
|
const grouped = _.groupBy(value, "cost_center");
|
||||||
{(fields, { add, remove, move }) => {
|
let error = false;
|
||||||
|
Object.keys(grouped).forEach((key) => {
|
||||||
|
const totalProdTicketHours = grouped[key].reduce(
|
||||||
|
(acc, val) => acc + val.productivehrs,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldTypeToCheck = "cost_center";
|
||||||
|
// bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||||
|
// ? "mod_lbr_ty"
|
||||||
|
// : "cost_center";
|
||||||
|
|
||||||
|
const costCenterDiff =
|
||||||
|
Math.round(
|
||||||
|
totals.find((total) => total[fieldTypeToCheck] === key)
|
||||||
|
?.difference * 10
|
||||||
|
) / 10;
|
||||||
|
|
||||||
|
if (totalProdTicketHours > costCenterDiff) error = true;
|
||||||
|
else {
|
||||||
|
// return Promise.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!error) return Promise.resolve();
|
||||||
|
return Promise.reject(
|
||||||
|
"Too many hours are being claimed as a part of this task"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{(fields, { add, remove, move }, { errors }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{errors.map((e, idx) => (
|
||||||
|
<Alert key={idx} message={e} />
|
||||||
|
))}
|
||||||
|
<div style={{ display: "none" }}>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<Form.Item
|
||||||
|
key={field.key}
|
||||||
|
style={{ padding: 0, margin: 2 }}
|
||||||
|
>
|
||||||
|
<Space wrap>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.employeeid")}
|
||||||
|
key={`${index}employeeid`}
|
||||||
|
name={[field.name, "employeeid"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<EmployeeSearchSelectComponent
|
||||||
|
options={bodyshop.employees}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.date")}
|
||||||
|
key={`${index}date`}
|
||||||
|
name={[field.name, "date"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.productivehrs")}
|
||||||
|
key={`${index}productivehrs`}
|
||||||
|
name={[field.name, "productivehrs"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.actualhrs")}
|
||||||
|
key={`${index}actualhrs`}
|
||||||
|
name={[field.name, "actualhrs"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.rate")}
|
||||||
|
key={`${index}rate`}
|
||||||
|
name={[field.name, "rate"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.cost_center")}
|
||||||
|
key={`${index}cost_center`}
|
||||||
|
name={[field.name, "cost_center"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("timetickets.fields.memo")}
|
||||||
|
key={`${index}memo`}
|
||||||
|
name={[field.name, "memo"]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item dependencies={["jobid"]}>
|
||||||
|
{() => {
|
||||||
|
const jobid = form.getFieldValue("jobid");
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!lineTicketCalled && jobid) ||
|
||||||
|
(jobid &&
|
||||||
|
lineTicketData?.jobs_by_pk?.id !== jobid &&
|
||||||
|
!lineTicketLoading)
|
||||||
|
) {
|
||||||
|
queryJobInfo({ variables: { id: jobid } }).then(() =>
|
||||||
|
calculateTimeTickets()
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<LaborAllocationContainer
|
||||||
{fields.map((field, index) => (
|
jobid={jobid || null}
|
||||||
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
loading={lineTicketLoading}
|
||||||
<LayoutFormRow grow>
|
lineTicketData={lineTicketData}
|
||||||
<Form.Item
|
hideTimeTickets
|
||||||
label={t("timetickets.fields.employeeid")}
|
/>
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "employeeid"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<EmployeeSearchSelectComponent
|
|
||||||
options={bodyshop.employees}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("timetickets.fields.date")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "date"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<FormDatePickerComponent />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("timetickets.fields.productivehrs")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "productivehrs"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber min={0} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("timetickets.fields.actualhrs")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "actualhrs"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber min={0} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("timetickets.fields.rate")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "rate"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("timetickets.fields.cost_center")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "cost_center"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("timetickets.fields.memo")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "memo"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => {
|
|
||||||
add();
|
|
||||||
}}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{t("employee_teams.actions.newmember")}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form.List>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
import { useLazyQuery, useMutation, useQuery } from "@apollo/client";
|
||||||
|
import { Form, Modal, notification } from "antd";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
import _ from "lodash";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
|
||||||
import { Modal, Form } from "antd";
|
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
|
||||||
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
||||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
|
||||||
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
|
||||||
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
||||||
|
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||||
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
|
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import Dinero from "dinero.js";
|
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
timeTicketTasksModal: selectTimeTicketTasks,
|
timeTicketTasksModal: selectTimeTicketTasks,
|
||||||
@@ -36,30 +40,63 @@ export function TimeTickeTaskModalContainer({
|
|||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
});
|
});
|
||||||
//Query the Job Information and Prefill the Form.
|
const { t } = useTranslation();
|
||||||
const [queryJobInfo, { loading, data: lineTicketData }] = useLazyQuery(
|
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||||
GET_JOB_INFO_DRAW_CALCULATIONS,
|
const [queryJobInfo, { called, loading, data: lineTicketData }] =
|
||||||
{
|
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleFinish(values) {
|
||||||
|
console.log(
|
||||||
|
"🚀 ~ file: time-ticket-task-modal.container.jsx:52 ~ handleFinish ~ values:",
|
||||||
|
values
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const result = await insertTimeTickets({
|
||||||
|
variables: {
|
||||||
|
timeTicketInput: values.timetickets.map((ticket) =>
|
||||||
|
_.omit(ticket, "pay")
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (result.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "error",
|
||||||
|
message: t("timetickets.errors.creating", {
|
||||||
|
message: JSON.stringify(result.errors),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
message: t("timetickets.successes.created"),
|
||||||
|
});
|
||||||
|
toggleModalVisible();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
async function handleFinish(values) {}
|
useEffect(() => {
|
||||||
|
if (context.jobid) {
|
||||||
|
console.log("UE Fired.");
|
||||||
|
queryJobInfo({ variables: { id: context.jobid } });
|
||||||
|
}
|
||||||
|
}, [context.jobid, queryJobInfo]);
|
||||||
|
|
||||||
const handleFieldsChange = async (changed, allFields) => {
|
const calculateTimeTickets = (presetMemo) => {
|
||||||
const formData = form.getFieldsValue();
|
const formData = form.getFieldsValue();
|
||||||
if (changed[0].name[0] === "jobid") {
|
|
||||||
await queryJobInfo({ variables: { id: changed[0].value } });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!formData.jobid ||
|
!formData.jobid ||
|
||||||
!formData.employeeteamid ||
|
!formData.employeeteamid ||
|
||||||
!formData.hourstype ||
|
!formData.hourstype ||
|
||||||
!formData.percent
|
formData.hourstype.length === 0 ||
|
||||||
|
!formData.percent ||
|
||||||
|
!lineTicketData
|
||||||
) {
|
) {
|
||||||
console.log("Not everything populated.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let data = [];
|
let data = [];
|
||||||
@@ -68,56 +105,75 @@ export function TimeTickeTaskModalContainer({
|
|||||||
const theTeam = JSON.parse(formData.employeeteamid);
|
const theTeam = JSON.parse(formData.employeeteamid);
|
||||||
|
|
||||||
if (theTeam) {
|
if (theTeam) {
|
||||||
eligibleHours =
|
data = [];
|
||||||
lineTicketData.joblines.reduce(
|
|
||||||
(acc, val) =>
|
|
||||||
acc + (formData.hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0),
|
|
||||||
0
|
|
||||||
) * (formData.percent / 100 || 0);
|
|
||||||
|
|
||||||
data = theTeam.employee_team_members.map((e) => {
|
formData.hourstype.forEach((hourstype) => {
|
||||||
return {
|
eligibleHours =
|
||||||
employeeid: e.id,
|
lineTicketData.joblines.reduce(
|
||||||
date: 0,
|
(acc, val) =>
|
||||||
percentage: e.percentage,
|
acc + (hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0),
|
||||||
rate: e.labor_rates[formData.hourstype],
|
0
|
||||||
cost_center:
|
) * (formData.percent / 100 || 0);
|
||||||
bodyshop.md_responsibility_centers.defaults.costs[
|
|
||||||
formData.hourstype
|
theTeam.employee_team_members.forEach((e) => {
|
||||||
],
|
const newTicket = {
|
||||||
productivehrs:
|
employeeid: e.employeeid,
|
||||||
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100,
|
bodyshopid: bodyshop.id,
|
||||||
pay: Dinero({
|
date: moment().format("YYYY-MM-DD"),
|
||||||
amount: Math.round((e.labor_rates[formData.hourstype] || 0) * 100),
|
jobid: formData.jobid,
|
||||||
})
|
rate: e.labor_rates[hourstype],
|
||||||
.multiply(
|
actualhrs: 0,
|
||||||
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100
|
memo: presetMemo,
|
||||||
)
|
flat_rate: true,
|
||||||
.toFormat("$0.00"),
|
cost_center:
|
||||||
};
|
bodyshop.md_responsibility_centers.defaults.costs[hourstype],
|
||||||
|
productivehrs:
|
||||||
|
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100,
|
||||||
|
pay: Dinero({
|
||||||
|
amount: Math.round((e.labor_rates[hourstype] || 0) * 100),
|
||||||
|
})
|
||||||
|
.multiply(
|
||||||
|
Math.round(eligibleHours * 100 * (e.percentage / 100)) / 100
|
||||||
|
)
|
||||||
|
.toFormat("$0.00"),
|
||||||
|
};
|
||||||
|
data.push(newTicket);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
form.setFieldsValue({ timetickets: data });
|
|
||||||
|
form.setFieldsValue({
|
||||||
|
timetickets: data.filter((d) => d.productivehrs > 0),
|
||||||
|
});
|
||||||
|
form.validateFields();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
open={visible}
|
open={visible}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
width="80%"
|
width="80%"
|
||||||
|
onOk={() => form.submit()}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
autoComplete={"off"}
|
autoComplete={"off"}
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
onHandleFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
onFieldsChange={handleFieldsChange}
|
// onFieldsChange={handleFieldsChange}
|
||||||
|
initialValues={context}
|
||||||
>
|
>
|
||||||
<TimeTicketTaskModalComponent
|
<TimeTicketTaskModalComponent
|
||||||
form={form}
|
form={form}
|
||||||
employeeAutoCompleteOptions={
|
employeeAutoCompleteOptions={
|
||||||
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
|
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
|
||||||
}
|
}
|
||||||
|
lineTicketData={lineTicketData}
|
||||||
|
lineTicketLoading={loading}
|
||||||
|
lineTicketCalled={called}
|
||||||
|
calculateTimeTickets={calculateTimeTickets}
|
||||||
|
queryJobInfo={queryJobInfo}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Button, Dropdown } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function TimeTicketsTasksPresets({
|
||||||
|
form,
|
||||||
|
calculateTimeTickets,
|
||||||
|
}) {
|
||||||
|
const handleClick = (props) => {
|
||||||
|
const preset = samplePresets.find((p) => {
|
||||||
|
return p.name === props.key;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (preset) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
percent: preset.percent,
|
||||||
|
hourstype: preset.hourstype,
|
||||||
|
});
|
||||||
|
calculateTimeTickets(preset.memo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger="click"
|
||||||
|
menu={{
|
||||||
|
items: samplePresets.map((p) => ({ label: p.name, key: p.name })),
|
||||||
|
onClick: handleClick,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>Presets</Button>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const samplePresets = [
|
||||||
|
{
|
||||||
|
name: "Teardown",
|
||||||
|
hourstype: ["LAB", "LAM"],
|
||||||
|
percent: 10,
|
||||||
|
memo: "Teardown Preset Task",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Disassembly",
|
||||||
|
hourstype: ["LAB", "LAD"],
|
||||||
|
percent: 20,
|
||||||
|
memo: "Disassy Preset Claim",
|
||||||
|
},
|
||||||
|
{ name: "Body", hourstype: ["LAB", "LAD"], percent: 20 },
|
||||||
|
{ name: "Prep", hourstype: ["LAR"], percent: 20 },
|
||||||
|
];
|
||||||
@@ -7,6 +7,7 @@ export const QUERY_TEAMS = gql`
|
|||||||
name
|
name
|
||||||
employee_team_members {
|
employee_team_members {
|
||||||
id
|
id
|
||||||
|
employeeid
|
||||||
labor_rates
|
labor_rates
|
||||||
percentage
|
percentage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,27 @@ export const GET_JOB_INFO_DRAW_CALCULATIONS = gql`
|
|||||||
rate_laf
|
rate_laf
|
||||||
rate_lam
|
rate_lam
|
||||||
}
|
}
|
||||||
|
timetickets(where: { jobid: { _eq: $id } }) {
|
||||||
|
actualhrs
|
||||||
|
ciecacode
|
||||||
|
cost_center
|
||||||
|
date
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
employeeid
|
||||||
|
memo
|
||||||
|
flat_rate
|
||||||
|
clockon
|
||||||
|
clockoff
|
||||||
|
rate
|
||||||
|
employee {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
employee_number
|
||||||
|
}
|
||||||
|
productivehrs
|
||||||
|
}
|
||||||
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
line_desc
|
line_desc
|
||||||
|
|||||||
Reference in New Issue
Block a user