even more updates.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2023-12-14 16:27:00 -05:00
parent 1261e8001b
commit b2c8e45d5e
41 changed files with 7326 additions and 7388 deletions

View File

@@ -104,7 +104,7 @@ export function App({
/> />
); );
// Any route that is not assigned and matched will default to the Landing Page component
return ( return (
<Suspense fallback={<LoadingSpinner message="ImEX Online"/>}> <Suspense fallback={<LoadingSpinner message="ImEX Online"/>}>
<Routes> <Routes>

View File

@@ -1,6 +1,6 @@
import Icon, {UploadOutlined} from "@ant-design/icons"; import Icon, {UploadOutlined} from "@ant-design/icons";
import {useApolloClient} from "@apollo/client"; import {useApolloClient} from "@apollo/client";
import {useTreatments} from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd"; import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
@@ -27,34 +27,18 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({}); const mapDispatchToProps = (dispatch) => ({});
export function BillFormComponent({ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteOptions, lineData, responsibilityCenters, loadLines, billEdit, disableInvNumber, job, loadOutstandingReturns, loadInventory, preferredMake}) {
bodyshop,
disabled,
form,
vendorAutoCompleteOptions,
lineData,
responsibilityCenters,
loadLines,
billEdit,
disableInvNumber,
job,
loadOutstandingReturns,
loadInventory,
preferredMake,
}) {
const {t} = useTranslation(); const {t} = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
const [discount, setDiscount] = useState(0); const [discount, setDiscount] = useState(0);
const {Extended_Bill_Posting} = useTreatments(
["Extended_Bill_Posting"], const { treatments: {Extended_Bill_Posting, ClosingPeriod} } = useSplitTreatments({
{}, attributes: {},
bodyshop.imexshopid names: ["Extended_Bill_Posting", "ClosingPeriod"],
); splitKey: bodyshop.imexshopid,
const {ClosingPeriod} = useTreatments( });
["ClosingPeriod"],
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => { const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount); setDiscount(opt.discount);

View File

@@ -1,5 +1,5 @@
import { useLazyQuery, useQuery } from "@apollo/client"; import { useLazyQuery, useQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -23,11 +23,11 @@ export function BillFormContainer({
disabled, disabled,
disableInvNumber, disableInvNumber,
}) { }) {
const { Simple_Inventory } = useTreatments( const { treatments: {Simple_Inventory} } = useSplitTreatments({
["Simple_Inventory"], attributes: {},
{}, names: ["Simple_Inventory"],
bodyshop && bodyshop.imexshopid splitKey: bodyshop && bodyshop.imexshopid,
); });
const { data: VendorAutoCompleteData } = useQuery( const { data: VendorAutoCompleteData } = useQuery(
SEARCH_VENDOR_AUTOCOMPLETE, SEARCH_VENDOR_AUTOCOMPLETE,

View File

@@ -1,5 +1,5 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons"; import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { import {
Button, Form, Button, Form,
Input, Input,
@@ -40,11 +40,14 @@ export function BillEnterModalLinesComponent({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form; const { setFieldsValue, getFieldsValue, getFieldValue } = form;
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"], const { treatments: {Simple_Inventory} } = useSplitTreatments({
{}, attributes: {},
bodyshop && bodyshop.imexshopid names: ["Simple_Inventory"],
); splitKey: bodyshop && bodyshop.imexshopid,
});
const columns = (remove) => { const columns = (remove) => {
return [ return [
{ {

View File

@@ -9,7 +9,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import GlobalSearchOs from "../global-search/global-search-os.component"; import GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss"; import "./breadcrumbs.styles.scss";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs, breadcrumbs: selectBreadcrumbs,
@@ -17,13 +17,14 @@ const mapStateToProps = createStructuredSelector({
}); });
export function BreadCrumbs({ breadcrumbs, bodyshop }) { export function BreadCrumbs({ breadcrumbs, bodyshop }) {
const { OpenSearch } = useTreatments(
["OpenSearch"],
{},
bodyshop && bodyshop.imexshopid
);
return ( const { treatments: {OpenSearch} } = useSplitTreatments({
attributes: {},
names: ["OpenSearch"],
splitKey: bodyshop && bodyshop.imexshopid,
});
return (
<Row className="breadcrumb-container"> <Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}> <Col xs={24} sm={24} md={16}>
<Breadcrumb separator=">"> <Breadcrumb separator=">">

View File

@@ -48,7 +48,7 @@ export function ContractConvertToRo({
const contractLength = dayjs(contract.actualreturn).diff( const contractLength = dayjs(contract.actualreturn).diff(
dayjs(contract.start), dayjs(contract.start),
"days" "day"
); );
const billingLines = []; const billingLines = [];
if (contractLength > 0) if (contractLength > 0)

View File

@@ -153,7 +153,7 @@ export function ContractsList({
(record.actualreturn && (record.actualreturn &&
record.start && record.start &&
`${dayjs(record.actualreturn) `${dayjs(record.actualreturn)
.diff(dayjs(record.start), "days", true) .diff(dayjs(record.start), "day", true)
.toFixed(1)} days`) || .toFixed(1)} days`) ||
"", "",
}, },

View File

@@ -25,7 +25,7 @@ import Icon, {
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -87,21 +87,12 @@ function Header({
recentItems, recentItems,
setCardPaymentContext, setCardPaymentContext,
}) { }) {
const { Simple_Inventory } = useTreatments( // TODO: Client Update - New Split Treatments usage example
["Simple_Inventory"], const { treatments: {ImEXPay, DmsAp, Simple_Inventory} } = useSplitTreatments({
{}, attributes: {},
bodyshop && bodyshop.imexshopid names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
); splitKey: bodyshop && bodyshop.imexshopid,
const { DmsAp } = useTreatments( });
["DmsAp"],
{},
bodyshop && bodyshop.imexshopid
);
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -89,7 +89,7 @@ export function JobChecklistForm({
bodyshop.intakechecklist.next_contact_hours > 0 && { bodyshop.intakechecklist.next_contact_hours > 0 && {
date_next_contact: dayjs().add( date_next_contact: dayjs().add(
bodyshop.intakechecklist.next_contact_hours, bodyshop.intakechecklist.next_contact_hours,
"hours" "hour"
), ),
}), }),
...(type === "deliver" && { ...(type === "deliver" && {
@@ -226,7 +226,7 @@ export function JobChecklistForm({
(job.labhrs.aggregate.sum.mod_lb_hrs || (job.labhrs.aggregate.sum.mod_lb_hrs ||
0 + job.larhrs.aggregate.sum.mod_lb_hrs || 0 + job.larhrs.aggregate.sum.mod_lb_hrs ||
0) / bodyshop.target_touchtime, 0) / bodyshop.target_touchtime,
"days" "day"
)), )),
scheduled_delivery: scheduled_delivery:
job.scheduled_delivery && dayjs(job.scheduled_delivery), job.scheduled_delivery && dayjs(job.scheduled_delivery),

View File

@@ -1,5 +1,5 @@
import { useApolloClient } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, notification, Popconfirm } from "antd"; import { Button, notification, Popconfirm } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -27,11 +27,13 @@ export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) {
const client = useApolloClient(); const client = useApolloClient();
const history = useNavigate(); const history = useNavigate();
const { IOU_Tracking } = useTreatments(
["IOU_Tracking"], const { treatments: {IOU_Tracking} } = useSplitTreatments({
{}, attributes: {},
bodyshop.imexshopid names: ["IOU_Tracking"],
); splitKey: bodyshop.imexshopid,
});
if (IOU_Tracking.treatment !== "on") return null; if (IOU_Tracking.treatment !== "on") return null;
const handleCreateIou = async () => { const handleCreateIou = async () => {

View File

@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import InputCurrency from "../form-items-formatted/currency-form-item.component"; import InputCurrency from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component"; import JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -35,16 +35,12 @@ export function JobLinesUpsertModalComponent({
form.resetFields(); form.resetFields();
}, [visible, form]); }, [visible, form]);
const { Allow_Negative_Jobline_Price } = useTreatments(
["Allow_Negative_Jobline_Price"], const { treatments: {Allow_Negative_Jobline_Price, Autohouse_Detail_line} } = useSplitTreatments({
{}, attributes: {},
bodyshop.imexshopid names: ["Allow_Negative_Jobline_Price", "Autohouse_Detail_line"],
); splitKey: bodyshop.imexshopid,
const { Autohouse_Detail_line } = useTreatments( });
["Autohouse_Detail_line"],
{},
bodyshop.imexshopid
);
return ( return (
<Modal <Modal

View File

@@ -16,7 +16,7 @@ import Axios from "axios";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import CriticalPartsScan from "../../utils/criticalPartsScan"; import CriticalPartsScan from "../../utils/criticalPartsScan";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobLineEditModal: selectJobLineEditModal, jobLineEditModal: selectJobLineEditModal,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -30,11 +30,13 @@ function JobLinesUpsertModalContainer({
toggleModalVisible, toggleModalVisible,
bodyshop, bodyshop,
}) { }) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"], const { treatments: {CriticalPartsScanning} } = useSplitTreatments({
{}, attributes: {},
bodyshop.imexshopid names: ['CriticalPartsScanning'],
); splitKey: bodyshop.imexshopid,
});
const { t } = useTranslation(); const { t } = useTranslation();
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE); const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
const [updateJobLine] = useMutation(UPDATE_JOB_LINE); const [updateJobLine] = useMutation(UPDATE_JOB_LINE);

View File

@@ -20,7 +20,7 @@ import DataLabel from "../data-label/data-label.component";
import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component"; import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component"; import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -36,21 +36,13 @@ const mapDispatchToProps = (dispatch) => ({
setMessage: (text) => dispatch(setMessage(text)), setMessage: (text) => dispatch(setMessage(text)),
}); });
export function JobPayments({ export function JobPayments({job, jobRO, bodyshop, setMessage, openChatByPhone, setPaymentContext, setCardPaymentContext, refetch}) {
job,
jobRO, const { treatments: {ImEXPay} } = useSplitTreatments({
bodyshop, attributes: {},
setMessage, names: ["ImEXPay"],
openChatByPhone, splitKey:bodyshop && bodyshop.imexshopid,
setPaymentContext, });
setCardPaymentContext,
refetch,
}) {
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({

View File

@@ -5,7 +5,7 @@ import {
useMutation, useMutation,
useQuery, useQuery,
} from "@apollo/client"; } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd"; import { Col, notification, Row } from "antd";
import Axios from "axios"; import Axios from "axios";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
@@ -50,16 +50,14 @@ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function JobsAvailableContainer({ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,}) {
bodyshop,
currentUser, const { treatments: {CriticalPartsScanning} } = useSplitTreatments({
insertAuditTrail, attributes: {},
}) { names: ["CriticalPartsScanning"],
const { CriticalPartsScanning } = useTreatments( splitKey: bodyshop.imexshopid,
["CriticalPartsScanning"], });
{},
bodyshop.imexshopid
);
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -6,7 +6,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import cleanAxios from "../../utils/CleanAxios"; import cleanAxios from "../../utils/CleanAxios";
import formatBytes from "../../utils/formatbytes"; import formatBytes from "../../utils/formatbytes";
//import yauzl from "yauzl"; //import yauzl from "yauzl";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -22,18 +22,17 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(JobsDocumentsDownloadButton); )(JobsDocumentsDownloadButton);
export function JobsDocumentsDownloadButton({ export function JobsDocumentsDownloadButton({ bodyshop, galleryImages, identifier}) {
bodyshop,
galleryImages,
identifier,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [download, setDownload] = useState(null); const [download, setDownload] = useState(null);
const { Direct_Media_Download } = useTreatments(
["Direct_Media_Download"], const { treatments: {Direct_Media_Download} } = useSplitTreatments({
{}, attributes: {},
bodyshop.imexshopid names: ["Direct_Media_Download"],
); splitKey: bodyshop.imexshopid,
});
const imagesToDownload = [ const imagesToDownload = [
...galleryImages.images.filter((image) => image.isSelected), ...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected), ...galleryImages.other.filter((image) => image.isSelected),

View File

@@ -13,7 +13,8 @@ export default function LoadingSpinner({ loading = true, message, ...props }) {
alignContent: "center" alignContent: "center"
}} }}
delay={200} delay={200}
tip={message ? message : null} // TODO: Client Update - tip only works when there are actually children, and this component is used in a lot of places where there are no children
// tip={message ? message : null}
> >
{props.children} {props.children}
</Spin> </Spin>

View File

@@ -1,23 +1,11 @@
import { DeleteFilled, WarningFilled, DownOutlined } from "@ant-design/icons"; import {DeleteFilled, DownOutlined, WarningFilled} from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { import {Checkbox, Divider, Dropdown, Form, Input, InputNumber, Menu, Radio, Select, Space, Tag,} from "antd";
Divider,
Form,
Input,
InputNumber,
Radio,
Space,
Tag,
Select,
Menu,
Dropdown,
Checkbox,
} 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";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
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 CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
@@ -26,329 +14,319 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component"; import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(PartsOrderModalComponent); )(PartsOrderModalComponent);
export function PartsOrderModalComponent({ export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, isReturn, preferredMake, job, form,}) {
bodyshop, const [sendType, setSendType] = sendTypeState;
vendorList,
sendTypeState,
isReturn,
preferredMake,
job,
form,
}) {
const [sendType, setSendType] = sendTypeState;
const { OEConnection } = useTreatments(
["OEConnection"],
{},
bodyshop.imexshopid
);
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
form.setFieldsValue({ comments: item.props.value });
};
const menu = ( const {treatments: {OEConnection, OEConnection_PriceChange}} = useSplitTreatments({
<div> attributes: {},
<Menu onClick={handleClick}> names: ["OEConnection", "OEConnection_PriceChange"],
{bodyshop.md_parts_order_comment.map((comment, idx) => ( splitKey: bodyshop.imexshopid,
<Menu.Item value={comment.comment} key={idx}> });
{comment.label}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
<Form.Item name="returnfrombill" style={{ display: "none" }}>
<Input />
</Form.Item>
<LayoutFormRow grow noDivider>
<Form.Item
name="vendorid"
label={t("vendors.fields.name")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<VendorSearchSelect
options={vendorList}
disabled={isReturn}
preferredMake={preferredMake}
showPhone
/>
</Form.Item>
<Form.Item
name="deliver_by"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
label={t("parts_orders.fields.deliver_by")}
>
<FormDatePicker onlyFuture />
</Form.Item>
{job && job.special_coverage_policy && (
<Tag color="tomato">
<Space>
<WarningFilled />
<span>{t("jobs.labels.specialcoveragepolicy")}</span>
</Space>
</Tag>
)}
{!isReturn && (
<Form.Item
name="removefrompartsqueue"
label={t("parts_orders.labels.removefrompartsqueue")}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
)}
{OEConnection.treatment === "on" && !isReturn && (
<Form.Item
name="is_quote"
label={t("parts_orders.labels.is_quote")}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
)}
<Form.Item const {t} = useTranslation();
name="order_type" const handleClick = ({item, key, keyPath}) => {
initialValue="parts_order" form.setFieldsValue({comments: item.props.value});
label={t("parts_orders.labels.order_type")} };
>
<Radio.Group disabled={sendType === "oec"}> const menu = (
<Radio value={"parts_order"}> <div>
{t("parts_orders.labels.parts_order")} <Menu onClick={handleClick}>
</Radio> {bodyshop.md_parts_order_comment.map((comment, idx) => (
<Radio value={"sublet"}> <Menu.Item value={comment.comment} key={idx}>
{t("parts_orders.labels.sublet_order")} {comment.label}
</Radio> </Menu.Item>
</Radio.Group> ))}
</Form.Item> </Menu>
</LayoutFormRow> </div>
<Divider orientation="left"> );
{t("parts_orders.labels.inthisorder")}
</Divider> return (
<Form.List name={["parts_order_lines", "data"]}> <div>
{(fields, { add, remove, move }) => { <Form.Item name="returnfrombill" style={{display: "none"}}>
return ( <Input/>
<div> </Form.Item>
{fields.map((field, index) => ( <LayoutFormRow grow noDivider>
<Form.Item required={false} key={field.key}> <Form.Item
<div style={{ display: "flex" }}> name="vendorid"
<LayoutFormRow grow noDivider style={{ flex: 1 }}> label={t("vendors.fields.name")}
<Form.Item rules={[
//span={8} {
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
{
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
> >
<Input /> <VendorSearchSelect
</Form.Item> options={vendorList}
<Form.Item disabled={isReturn}
label={t("parts_orders.fields.line_remarks")} preferredMake={preferredMake}
key={`${index}line_remarks`} showPhone
name={[field.name, "line_remarks"]} />
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
disabled={
!(
sendType === "oec" &&
OEConnection_PriceChange.treatment === "on"
)
}
>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAG">
{t("joblines.fields.part_types.PAG")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAP">
{t("joblines.fields.part_types.PAP")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input />
</Form.Item>
{
// <Form.Item
// label={t("parts_orders.fields.db_price")}
// key={`${index}db_price`}
// name={[field.name, "db_price"]}
// >
// <CurrencyInput />
// </Form.Item>
}
<Form.Item
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput
addonBefore={
<PartsOrderModalPriceChange
form={form}
field={field}
/>
}
/>
</Form.Item>
{isReturn && (
<Form.Item
label={t("parts_orders.fields.cost")}
key={`${index}cost`}
name={[field.name, "cost"]}
>
<CurrencyInput />
</Form.Item>
)}
</LayoutFormRow>
<Space wrap size="small" align="center">
<div>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
</div>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</div>
</Form.Item> </Form.Item>
))} <Form.Item
</div> name="deliver_by"
); rules={[
}} {
</Form.List> required: true,
<Form.Item //message: t("general.validation.required"),
name="comments" },
label={ ]}
<Space> label={t("parts_orders.fields.deliver_by")}
{t("parts_orders.fields.comments")} >
<Dropdown overlay={menu}> <FormDatePicker onlyFuture/>
<a </Form.Item>
className="ant-dropdown-link" {job && job.special_coverage_policy && (
href=" #" <Tag color="tomato">
onClick={(e) => e.preventDefault()} <Space>
> <WarningFilled/>
<DownOutlined /> <span>{t("jobs.labels.specialcoveragepolicy")}</span>
</a> </Space>
</Dropdown> </Tag>
</Space> )}
} {!isReturn && (
> <Form.Item
<Input.TextArea rows={3} /> name="removefrompartsqueue"
</Form.Item> label={t("parts_orders.labels.removefrompartsqueue")}
valuePropName="checked"
>
<Checkbox/>
</Form.Item>
)}
{OEConnection.treatment === "on" && !isReturn && (
<Form.Item
name="is_quote"
label={t("parts_orders.labels.is_quote")}
valuePropName="checked"
>
<Checkbox/>
</Form.Item>
)}
<Form.Item noStyle shouldUpdate> <Form.Item
{() => { name="order_type"
const is_quote = form.getFieldValue("is_quote"); initialValue="parts_order"
if (is_quote) setSendType("oec"); label={t("parts_orders.labels.order_type")}
return ( >
<Radio.Group <Radio.Group disabled={sendType === "oec"}>
defaultValue={sendType} <Radio value={"parts_order"}>
value={sendType} {t("parts_orders.labels.parts_order")}
onChange={(e) => setSendType(e.target.value)} </Radio>
<Radio value={"sublet"}>
{t("parts_orders.labels.sublet_order")}
</Radio>
</Radio.Group>
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">
{t("parts_orders.labels.inthisorder")}
</Divider>
<Form.List name={["parts_order_lines", "data"]}>
{(fields, {add, remove, move}) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<div style={{display: "flex"}}>
<LayoutFormRow grow noDivider style={{flex: 1}}>
<Form.Item
//span={8}
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input/>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.line_remarks")}
key={`${index}line_remarks`}
name={[field.name, "line_remarks"]}
>
<Input/>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
disabled={
!(
sendType === "oec" &&
OEConnection_PriceChange.treatment === "on"
)
}
>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAG">
{t("joblines.fields.part_types.PAG")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAP">
{t("joblines.fields.part_types.PAP")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input/>
</Form.Item>
{
// <Form.Item
// label={t("parts_orders.fields.db_price")}
// key={`${index}db_price`}
// name={[field.name, "db_price"]}
// >
// <CurrencyInput />
// </Form.Item>
}
<Form.Item
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber/>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput
addonBefore={
<PartsOrderModalPriceChange
form={form}
field={field}
/>
}
/>
</Form.Item>
{isReturn && (
<Form.Item
label={t("parts_orders.fields.cost")}
key={`${index}cost`}
name={[field.name, "cost"]}
>
<CurrencyInput/>
</Form.Item>
)}
</LayoutFormRow>
<Space wrap size="small" align="center">
<div>
<DeleteFilled
style={{margin: "1rem"}}
onClick={() => {
remove(field.name);
}}
/>
</div>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</div>
</Form.Item>
))}
</div>
);
}}
</Form.List>
<Form.Item
name="comments"
label={
<Space>
{t("parts_orders.fields.comments")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined/>
</a>
</Dropdown>
</Space>
}
> >
<Radio disabled={is_quote} value={"none"}> <Input.TextArea rows={3}/>
{t("general.labels.none")} </Form.Item>
</Radio>
<Radio disabled={is_quote} value={"e"}> <Form.Item noStyle shouldUpdate>
{t("parts_orders.labels.email")} {() => {
</Radio> const is_quote = form.getFieldValue("is_quote");
<Radio disabled={is_quote} value={"p"}> if (is_quote) setSendType("oec");
{t("parts_orders.labels.print")} return (
</Radio> <Radio.Group
{OEConnection.treatment === "on" && !isReturn && ( defaultValue={sendType}
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio> value={sendType}
)} onChange={(e) => setSendType(e.target.value)}
</Radio.Group> >
); <Radio disabled={is_quote} value={"none"}>
}} {t("general.labels.none")}
</Form.Item> </Radio>
</div> <Radio disabled={is_quote} value={"e"}>
); {t("parts_orders.labels.email")}
</Radio>
<Radio disabled={is_quote} value={"p"}>
{t("parts_orders.labels.print")}
</Radio>
{OEConnection.treatment === "on" && !isReturn && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>
);
}}
</Form.Item>
</div>
);
} }

View File

@@ -1,395 +1,380 @@
import { useMutation, useQuery, useApolloClient } from "@apollo/client"; import {useApolloClient, useMutation, useQuery} from "@apollo/client";
import { Form, Modal, notification } from "antd"; import {Form, Modal, notification} from "antd";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import React, { useEffect, useState } from "react"; import React, {useEffect, 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 { logImEXEvent, auth } from "../../firebase/firebase.utils"; import {auth, logImEXEvent} from "../../firebase/firebase.utils";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries"; import {UPDATE_JOB_LINE_STATUS} from "../../graphql/jobs-lines.queries";
import { import {INSERT_NEW_PARTS_ORDERS, QUERY_PARTS_ORDER_OEC,} from "../../graphql/parts-orders.queries";
INSERT_NEW_PARTS_ORDERS, import {QUERY_ALL_VENDORS_FOR_ORDER} from "../../graphql/vendors.queries";
QUERY_PARTS_ORDER_OEC, import {insertAuditTrail} from "../../redux/application/application.actions";
} from "../../graphql/parts-orders.queries"; import {setEmailOptions} from "../../redux/email/email.actions";
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries"; import {setModalContext, toggleModalVisible,} from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions"; import {selectPartsOrder} from "../../redux/modals/modals.selectors";
import { setEmailOptions } from "../../redux/email/email.actions"; import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {
setModalContext,
toggleModalVisible,
} from "../../redux/modals/modals.actions";
import { selectPartsOrder } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate"; import {GenerateDocument} from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import {TemplateList} from "../../utils/TemplateConstants";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component"; import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios"; import axios from "axios";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import _ from "lodash"; import _ from "lodash";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import {UPDATE_JOB} from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
partsOrderModal: selectPartsOrder, partsOrderModal: selectPartsOrder,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")), toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
setBillEnterContext: (context) => setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })), dispatch(setModalContext({context: context, modal: "billEnter"})),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({jobid, operation})),
}); });
export function PartsOrderModalContainer({ export function PartsOrderModalContainer({partsOrderModal,toggleModalVisible,currentUser, bodyshop, setEmailOptions, setBillEnterContext, insertAuditTrail,}) {
partsOrderModal, const {t} = useTranslation();
toggleModalVisible, const client = useApolloClient();
currentUser,
bodyshop,
setEmailOptions,
setBillEnterContext,
insertAuditTrail,
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { visible, context, actions } = partsOrderModal;
const {
jobId,
linesToOrder,
isReturn,
vendorId,
returnFromBill,
invoiceNumber,
job,
} = context;
const { refetch } = actions; const {treatments: {OEConnection_PriceChange}} = useSplitTreatments({
const [form] = Form.useForm(); attributes: {},
const [saving, setSaving] = useState(false); names: ["OEConnection_PriceChange"],
const sendTypeState = useState("e"); splitKey: bodyshop.imexshopid,
const sendType = sendTypeState[0];
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !visible,
variables: { jobId: jobId },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async ({
order_type,
removefrompartsqueue,
is_quote,
...values
}) => {
logImEXEvent("parts_order_insert");
setSaving(true);
let insertResult;
insertResult = await insertPartOrder({
variables: {
po: [
{
...values,
order_date: dayjs().format("YYYY-MM-DD"),
orderedby: currentUser.email,
jobid: jobId,
user_email: currentUser.email,
return: isReturn,
status: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
}); });
const jobLinesResult = await updateJobLines({ const {visible, context, actions} = partsOrderModal;
variables: { const {
ids: values.parts_order_lines.data jobId,
.filter((item) => item.job_line_id) linesToOrder,
.map((item) => item.job_line_id), isReturn,
status: isReturn vendorId,
? bodyshop.md_order_statuses.default_returned || "Returned*" returnFromBill,
: is_quote invoiceNumber,
? bodyshop.md_order_statuses.default_quote || "Quote" job,
: bodyshop.md_order_statuses.default_ordered || "Ordered*", } = context;
},
const {refetch} = actions;
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const sendTypeState = useState("e");
const sendType = sendTypeState[0];
const {loading, error, data} = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !visible,
variables: {jobId: jobId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
}); });
if (!isReturn && removefrompartsqueue) { const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
await updateJob({ const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
variables: { const [updateJob] = useMutation(UPDATE_JOB);
jobId: jobId,
job: {
queued_for_parts: false,
},
},
});
}
if (!!jobLinesResult.errors) { const handleFinish = async ({
notification["error"]({ order_type,
message: t("parts_orders.errors.creating"), removefrompartsqueue,
description: JSON.stringify(jobLinesResult.errors), is_quote,
}); ...values
} }) => {
logImEXEvent("parts_order_insert");
setSaving(true);
let insertResult;
if (values.vendorid === bodyshop.inhousevendorid) { insertResult = await insertPartOrder({
setBillEnterContext({ variables: {
actions: { refetch: refetch }, po: [
context: { {
disableInvNumber: true, ...values,
job: { id: jobId }, order_date: dayjs().format("YYYY-MM-DD"),
bill: { orderedby: currentUser.email,
vendorid: bodyshop.inhousevendorid, jobid: jobId,
invoice_number: "ih", user_email: currentUser.email,
isinhouse: true, return: isReturn,
date: new dayjs(), status: is_quote
total: 0, ? bodyshop.md_order_statuses.default_quote || "Quote"
billlines: values.parts_order_lines.data.map((p) => { : bodyshop.md_order_statuses.default_ordered || "Ordered*",
return { },
joblineid: p.job_line_id, ],
actual_price: p.act_price,
actual_cost: 0, //p.act_price,
line_desc: p.line_desc,
line_remarks: p.line_remarks,
part_type: p.part_type,
quantity: p.quantity || 1,
applicable_taxes: {
local: false,
state: false,
federal: false,
},
};
}),
},
},
});
toggleModalVisible();
return;
}
if (refetch) refetch();
const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
const matchingVendor = data.vendors.filter(
(item) => item.id === values.vendorid
)[0];
let vendorEmails =
matchingVendor &&
matchingVendor.email &&
matchingVendor.email.split(RegExp("[;,]"));
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{
to: matchingVendor ? vendorEmails : null,
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
: order_type === "parts_order"
? Templates.parts_order.subject
: Templates.sublet_order.subject,
},
"e",
jobId
);
} else if (sendType === "p") {
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{},
"p"
);
} else if (sendType === "oec") {
//Send to Partner OEC.
try {
const partsOrder = await client.query({
query: QUERY_PARTS_ORDER_OEC,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
let po;
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
if (OEConnection_PriceChange.treatment === "on") {
//Set the flag to include the override.
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
po.parts_order_lines.forEach((pol) => {
pol.priceChange = true;
});
}
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
po || partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
}, },
} refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
);
if (oecResponse.data && oecResponse.data.success === false) {
notification.open({
type: "error",
message: t("parts_orders.errors.oec", {
error: oecResponse.data.error,
}),
});
}
} catch (error) {
console.log("Error OEC.", error);
notification["error"]({
message: t("parts_orders.errors.oec", {
error: JSON.stringify(error.message),
}),
}); });
setSaving(false); if (!!insertResult.errors) {
return; notification["error"]({
} message: t("parts_orders.errors.creating"),
} description: JSON.stringify(insertResult.errors),
setSaving(false);
toggleModalVisible();
};
const initialValues = {
jobid: jobId,
return: isReturn,
deliver_by: isReturn ? dayjs(new Date()) : null,
vendorid: vendorId,
returnfrombill: returnFromBill,
parts_order_lines: {
data: linesToOrder
? linesToOrder.reduce((acc, value) => {
acc.push({
line_desc: value.line_desc,
oem_partno: value.oem_partno,
db_price: value.db_price,
act_price: value.act_price,
cost: value.cost,
quantity: value.part_qty,
job_line_id: isReturn ? value.joblineid : value.id,
part_type: value.part_type,
...(isReturn && { cm_received: false }),
}); });
return acc; return;
}, []) }
: [], notification["success"]({
}, message: values.isReturn
}; ? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
useEffect(() => { const jobLinesResult = await updateJobLines({
if (visible && !!linesToOrder) { variables: {
form.resetFields(); ids: values.parts_order_lines.data
} .filter((item) => item.job_line_id)
}, [visible, linesToOrder, form]); .map((item) => item.job_line_id),
status: isReturn
? bodyshop.md_order_statuses.default_returned || "Returned*"
: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
});
return ( if (!isReturn && removefrompartsqueue) {
<Modal await updateJob({
open={visible} variables: {
title={ jobId: jobId,
isReturn job: {
? `${t("parts_orders.labels.returnpartsorder")} ${invoiceNumber}` queued_for_parts: false,
: t("parts_orders.labels.newpartsorder") },
} },
onCancel={() => toggleModalVisible()} });
onOk={() => form.submit()} }
okButtonProps={{ loading: saving }}
cancelButtonProps={{ loading: saving }} if (!!jobLinesResult.errors) {
destroyOnClose notification["error"]({
width="75%" message: t("parts_orders.errors.creating"),
forceRender description: JSON.stringify(jobLinesResult.errors),
> });
{error ? <AlertComponent message={error.message} type="error" /> : null} }
<Form
form={form} if (values.vendorid === bodyshop.inhousevendorid) {
layout="vertical" setBillEnterContext({
autoComplete="no" actions: {refetch: refetch},
onFinish={handleFinish} context: {
initialValues={initialValues} disableInvNumber: true,
> job: {id: jobId},
{loading ? ( bill: {
<LoadingSpinner /> vendorid: bodyshop.inhousevendorid,
) : ( invoice_number: "ih",
<PartsOrderModalComponent isinhouse: true,
form={form} date: new dayjs(),
vendorList={(data && data.vendors) || []} total: 0,
sendTypeState={sendTypeState} billlines: values.parts_order_lines.data.map((p) => {
isReturn={isReturn} return {
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc} joblineid: p.job_line_id,
job={job} actual_price: p.act_price,
/> actual_cost: 0, //p.act_price,
)} line_desc: p.line_desc,
</Form> line_remarks: p.line_remarks,
</Modal> part_type: p.part_type,
); quantity: p.quantity || 1,
applicable_taxes: {
local: false,
state: false,
federal: false,
},
};
}),
},
},
});
toggleModalVisible();
return;
}
if (refetch) refetch();
const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
const matchingVendor = data.vendors.filter(
(item) => item.id === values.vendorid
)[0];
let vendorEmails =
matchingVendor &&
matchingVendor.email &&
matchingVendor.email.split(RegExp("[;,]"));
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{
to: matchingVendor ? vendorEmails : null,
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
: order_type === "parts_order"
? Templates.parts_order.subject
: Templates.sublet_order.subject,
},
"e",
jobId
);
} else if (sendType === "p") {
GenerateDocument(
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
},
{},
"p"
);
} else if (sendType === "oec") {
//Send to Partner OEC.
try {
const partsOrder = await client.query({
query: QUERY_PARTS_ORDER_OEC,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
let po;
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
if (OEConnection_PriceChange.treatment === "on") {
//Set the flag to include the override.
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
po.parts_order_lines.forEach((pol) => {
pol.priceChange = true;
});
}
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
po || partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
if (oecResponse.data && oecResponse.data.success === false) {
notification.open({
type: "error",
message: t("parts_orders.errors.oec", {
error: oecResponse.data.error,
}),
});
}
} catch (error) {
console.log("Error OEC.", error);
notification["error"]({
message: t("parts_orders.errors.oec", {
error: JSON.stringify(error.message),
}),
});
setSaving(false);
return;
}
}
setSaving(false);
toggleModalVisible();
};
const initialValues = {
jobid: jobId,
return: isReturn,
deliver_by: isReturn ? dayjs(new Date()) : null,
vendorid: vendorId,
returnfrombill: returnFromBill,
parts_order_lines: {
data: linesToOrder
? linesToOrder.reduce((acc, value) => {
acc.push({
line_desc: value.line_desc,
oem_partno: value.oem_partno,
db_price: value.db_price,
act_price: value.act_price,
cost: value.cost,
quantity: value.part_qty,
job_line_id: isReturn ? value.joblineid : value.id,
part_type: value.part_type,
...(isReturn && {cm_received: false}),
});
return acc;
}, [])
: [],
},
};
useEffect(() => {
if (visible && !!linesToOrder) {
form.resetFields();
}
}, [visible, linesToOrder, form]);
return (
<Modal
open={visible}
title={
isReturn
? `${t("parts_orders.labels.returnpartsorder")} ${invoiceNumber}`
: t("parts_orders.labels.newpartsorder")
}
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
okButtonProps={{loading: saving}}
cancelButtonProps={{loading: saving}}
destroyOnClose
width="75%"
forceRender
>
{error ? <AlertComponent message={error.message} type="error"/> : null}
<Form
form={form}
layout="vertical"
autoComplete="no"
onFinish={handleFinish}
initialValues={initialValues}
>
{loading ? (
<LoadingSpinner/>
) : (
<PartsOrderModalComponent
form={form}
vendorList={(data && data.vendors) || []}
sendTypeState={sendTypeState}
isReturn={isReturn}
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
job={job}
/>
)}
</Form>
</Modal>
);
} }
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(PartsOrderModalContainer); )(PartsOrderModalContainer);

View File

@@ -1,10 +1,10 @@
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { Form, Input, Radio, Select } from "antd"; import {Form, Input, Radio, Select} 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";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import DatePickerFormItem from "../form-date-picker/form-date-picker.component"; import DatePickerFormItem from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
@@ -12,150 +12,149 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import PaymentFormTotalPayments from "./payment-form.totalpayments.component"; import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
export function PaymentFormComponent({ export function PaymentFormComponent({form, bodyshop, disabled,}) {
form,
bodyshop,
disabled,
}) {
const { Qb_Multi_Ar } = useTreatments(
["Qb_Multi_Ar"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const {treatments: {Qb_Multi_Ar}} = useSplitTreatments({
attributes: {},
names: ["Qb_Multi_Ar"],
splitKey: bodyshop && bodyshop.imexshopid,
});
return (
<div>
<LayoutFormRow grow>
<Form.Item
name="jobid"
label={t("bills.fields.ro_number")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<JobSearchSelect disabled={disabled} notExported={false} clm_no />
</Form.Item>
<Form.Item
shouldUpdate={(prev, cur) => cur.jobid && prev.jobid !== cur.jobid}
>
{() => {
return (
<PaymentFormTotalPayments jobid={form.getFieldValue("jobid")} />
);
}}
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow> const {t} = useTranslation();
<Form.Item
label={t("payments.fields.amount")}
name="amount"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<CurrencyInput disabled={disabled} />
</Form.Item>
<Form.Item
label={t("payments.fields.transactionid")}
name="transactionid"
>
<Input disabled={disabled} />
</Form.Item>
<Form.Item label={t("payments.fields.memo")} name="memo">
<Input disabled={disabled} />
</Form.Item>
<Form.Item
label={t("payments.fields.date")}
name="date"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<DatePickerFormItem disabled={disabled} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow> return (
<Form.Item <div>
label={t("payments.fields.payer")} <LayoutFormRow grow>
name="payer" <Form.Item
rules={[ name="jobid"
{ label={t("bills.fields.ro_number")}
required: true, rules={[
//message: t("general.validation.required"), {
}, required: true,
]} //message: t("general.validation.required"),
> },
<Select disabled={disabled}> ]}
<Select.Option value={t("payments.labels.customer")}> >
{t("payments.labels.customer")} <JobSearchSelect disabled={disabled} notExported={false} clm_no/>
</Select.Option> </Form.Item>
{Qb_Multi_Ar.treatment === "on" ? ( <Form.Item
<> shouldUpdate={(prev, cur) => cur.jobid && prev.jobid !== cur.jobid}
<Select.OptGroup label={t("payments.labels.external")}> >
{bodyshop.md_ins_cos.map((i, idx) => ( {() => {
<Select.Option key={idx} value={i.name}> return (
{i.name} <PaymentFormTotalPayments jobid={form.getFieldValue("jobid")}/>
</Select.Option> );
))} }}
</Select.OptGroup> </Form.Item>
</> </LayoutFormRow>
) : (
<Select.Option value={t("payments.labels.insurance")}>
{t("payments.labels.insurance")}
</Select.Option>
)}
</Select>
</Form.Item>
<Form.Item <LayoutFormRow grow>
label={t("payments.fields.type")} <Form.Item
name="type" label={t("payments.fields.amount")}
rules={[ name="amount"
{ rules={[
required: true, {
//message: t("general.validation.required"), required: true,
}, //message: t("general.validation.required"),
]} },
> ]}
<Select disabled={disabled}> >
{bodyshop.md_payment_types.map((v, idx) => ( <CurrencyInput disabled={disabled}/>
<Select.Option key={idx} value={v}> </Form.Item>
{v} <Form.Item
</Select.Option> label={t("payments.fields.transactionid")}
))} name="transactionid"
</Select> >
</Form.Item> <Input disabled={disabled}/>
</LayoutFormRow> </Form.Item>
<LayoutFormRow grow> <Form.Item label={t("payments.fields.memo")} name="memo">
<Form.Item <Input disabled={disabled}/>
label={t("general.labels.sendby")} </Form.Item>
name="sendby" <Form.Item
initialValue="none" label={t("payments.fields.date")}
> name="date"
<Radio.Group disabled={disabled}> rules={[
<Radio value="none">{t("general.labels.none")}</Radio> {
<Radio value="email">{t("general.labels.email")}</Radio> required: true,
<Radio value="print">{t("general.labels.print")}</Radio> //message: t("general.validation.required"),
</Radio.Group> },
</Form.Item> ]}
</LayoutFormRow> >
</div> <DatePickerFormItem disabled={disabled}/>
); </Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item
label={t("payments.fields.payer")}
name="payer"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select disabled={disabled}>
<Select.Option value={t("payments.labels.customer")}>
{t("payments.labels.customer")}
</Select.Option>
{Qb_Multi_Ar.treatment === "on" ? (
<>
<Select.OptGroup label={t("payments.labels.external")}>
{bodyshop.md_ins_cos.map((i, idx) => (
<Select.Option key={idx} value={i.name}>
{i.name}
</Select.Option>
))}
</Select.OptGroup>
</>
) : (
<Select.Option value={t("payments.labels.insurance")}>
{t("payments.labels.insurance")}
</Select.Option>
)}
</Select>
</Form.Item>
<Form.Item
label={t("payments.fields.type")}
name="type"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select disabled={disabled}>
{bodyshop.md_payment_types.map((v, idx) => (
<Select.Option key={idx} value={v}>
{v}
</Select.Option>
))}
</Select>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item
label={t("general.labels.sendby")}
name="sendby"
initialValue="none"
>
<Radio.Group disabled={disabled}>
<Radio value="none">{t("general.labels.none")}</Radio>
<Radio value="email">{t("general.labels.email")}</Radio>
<Radio value="print">{t("general.labels.print")}</Radio>
</Radio.Group>
</Form.Item>
</LayoutFormRow>
</div>
);
} }
export default connect(mapStateToProps, null)(PaymentFormComponent); export default connect(mapStateToProps, null)(PaymentFormComponent);

View File

@@ -23,7 +23,7 @@ export function ProductionlistColumnTouchTime({ bodyshop, job }) {
const Difference_In_Days = dayjs().diff( const Difference_In_Days = dayjs().diff(
dayjs(job.actual_in), dayjs(job.actual_in),
"days", "day",
true true
); );

View File

@@ -1,319 +1,306 @@
import { SyncOutlined } from "@ant-design/icons"; import {SyncOutlined} from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { import {Button, Dropdown, Input, Menu, Space, Statistic, Table,} from "antd";
Button,
Dropdown,
Input,
Menu,
Space,
Statistic,
Table,
} from "antd";
import {PageHeader} from "@ant-design/pro-layout"; import {PageHeader} from "@ant-design/pro-layout";
import React, { useMemo, useState } from "react"; import React, {useMemo, useState} from "react";
import ReactDragListView from "react-drag-listview"; import ReactDragListView from "react-drag-listview";
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 { selectTechnician } from "../../redux/tech/tech.selectors"; import {selectTechnician} from "../../redux/tech/tech.selectors";
import { import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component"; import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
import ProductionListColumns from "../production-list-columns/production-list-columns.data"; import ProductionListColumns from "../production-list-columns/production-list-columns.data";
import ProductionListDetail from "../production-list-detail/production-list-detail.component"; import ProductionListDetail from "../production-list-detail/production-list-detail.component";
import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component"; import ProductionListSaveConfigButton
from "../production-list-save-config-button/production-list-save-config-button.component";
import ProductionListPrint from "./production-list-print.component"; import ProductionListPrint from "./production-list-print.component";
import ProductionListTableViewSelect from "./production-list-table-view-select.component"; import ProductionListTableViewSelect from "./production-list-table-view-select.component";
import ResizeableTitle from "./production-list-table.resizeable.component"; import ResizeableTitle from "./production-list-table.resizeable.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician, technician: selectTechnician,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
export function ProductionListTable({ export function ProductionListTable({loading, data, refetch, bodyshop, technician, currentUser}) {
loading,
data,
refetch,
bodyshop,
technician,
currentUser,
}) {
const [searchText, setSearchText] = useState("");
const { Production_List_Status_Colors } = useTreatments(
["Production_List_Status_Colors"],
{},
bodyshop.imexshopid
);
const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email
);
const defaultView = assoc && assoc.default_prod_list_view; const [searchText, setSearchText] = useState("");
const [state, setState] = useState( const { treatments: {Production_List_Status_Colors} } = useSplitTreatments({
(bodyshop.production_config && attributes: {},
bodyshop.production_config.find((p) => p.name === defaultView)?.columns names: ["Production_List_Status_Colors"],
.tableState) || splitKey: bodyshop.imexshopid,
bodyshop.production_config[0]?.columns.tableState || {
sortedInfo: {},
filteredInfo: { text: "" },
}
);
const { t } = useTranslation();
const matchingColumnConfig = useMemo(() => {
return bodyshop.production_config.find((p) => p.name === defaultView);
}, [bodyshop.production_config, defaultView]);
const [columns, setColumns] = useState(
(state &&
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width ?? 100,
};
})) ||
[]
);
const handleTableChange = (pagination, filters, sorter) => {
setState({
...state,
filteredInfo: filters,
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order },
}); });
};
const onDragEnd = (fromIndex, toIndex) => {
const columnsCopy = columns.slice();
const item = columnsCopy.splice(fromIndex, 1)[0];
columnsCopy.splice(toIndex, 0, item);
setColumns(columnsCopy);
};
const removeColumn = (e) => { const assoc = bodyshop.associations.find(
const { key } = e; (a) => a.useremail === currentUser.email
setColumns(columns.filter((i) => i.key !== key)); );
};
const handleResize = const defaultView = assoc && assoc.default_prod_list_view;
(index) =>
(e, { size }) => { const [state, setState] = useState(
const nextColumns = [...columns]; (bodyshop.production_config &&
nextColumns[index] = { bodyshop.production_config.find((p) => p.name === defaultView)?.columns
...nextColumns[index], .tableState) ||
width: size.width, bodyshop.production_config[0]?.columns.tableState || {
}; sortedInfo: {},
setColumns(nextColumns); filteredInfo: {text: ""},
}
);
const {t} = useTranslation();
const matchingColumnConfig = useMemo(() => {
return bodyshop.production_config.find((p) => p.name === defaultView);
}, [bodyshop.production_config, defaultView]);
const [columns, setColumns] = useState(
(state &&
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width ?? 100,
};
})) ||
[]
);
const handleTableChange = (pagination, filters, sorter) => {
setState({
...state,
filteredInfo: filters,
sortedInfo: {columnKey: sorter.columnKey, order: sorter.order},
});
}; };
const headerItem = (col) => ( const onDragEnd = (fromIndex, toIndex) => {
<Dropdown const columnsCopy = columns.slice();
className="prod-header-dropdown" const item = columnsCopy.splice(fromIndex, 1)[0];
overlay={ columnsCopy.splice(toIndex, 0, item);
<Menu onClick={removeColumn}> setColumns(columnsCopy);
<Menu.Item key={col.key}> };
{t("production.actions.removecolumn")}
</Menu.Item>
</Menu>
}
trigger={["contextMenu"]}
>
<span>{col.title}</span>
</Dropdown>
);
const dataSource = const removeColumn = (e) => {
searchText === "" const {key} = e;
? data setColumns(columns.filter((i) => i.key !== key));
: data.filter( };
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ins_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
);
// const handleSelectRecord = (record) => { const handleResize =
// if (selected !== record.id) { (index) =>
// setSelected(record.id); (e, {size}) => {
// } else { const nextColumns = [...columns];
// setSelected(null); nextColumns[index] = {
// } ...nextColumns[index],
// }; width: size.width,
};
if (!!!columns) return <div>No columns found.</div>; setColumns(nextColumns);
const totalHrs = data
.reduce(
(acc, val) =>
acc +
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAB = data
.reduce(
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAR = data
.reduce(
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
return (
<div>
<PageHeader
title={
<Space>
<Statistic
title={t("dashboard.titles.productionhours")}
value={totalHrs}
/>
<Statistic
title={t("dashboard.titles.labhours")}
value={totalLAB}
/>
<Statistic
title={t("dashboard.titles.larhours")}
value={totalLAR}
/>
<Statistic
title={t("appointments.labels.inproduction")}
value={dataSource && dataSource.length}
/>
</Space>
}
extra={
<Space wrap>
<Button onClick={() => refetch && refetch()}>
<SyncOutlined />
</Button>
<ProductionListColumnsAdd
columnState={[columns, setColumns]}
tableState={state}
/>
<ProductionListSaveConfigButton
columns={columns}
tableState={state}
/>
<ProductionListTableViewSelect
state={state}
setState={setState}
setColumns={setColumns}
/>
<Input
onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")}
value={searchText}
/>
<ProductionListPrint />
</Space>
}
/>
<ProductionListDetail jobs={dataSource} />
<ReactDragListView.DragColumn
onDragEnd={onDragEnd}
nodeSelector="th"
handleSelector=".prod-header-dropdown"
>
<Table
sticky
pagination={false}
size="small"
{...(Production_List_Status_Colors.treatment === "on" && {
onRow: (record, index) => {
if (!bodyshop.md_ro_statuses.production_colors) return null;
const color = bodyshop.md_ro_statuses.production_colors.find(
(x) => x.status === record.status
);
if (!color) {
if (index % 2 === 0)
return {
style: {
backgroundColor: `rgb(236, 236, 236)`,
},
};
return null;
}
return {
className: "rowWithColor",
style: {
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
},
};
},
})}
components={{
header: {
cell: ResizeableTitle,
},
}}
columns={columns.map((c, index) => {
return {
...c,
filteredValue: state.filteredInfo[c.key] || null,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),
ellipsis: true,
width: c.width ?? 100,
onHeaderCell: (column) => ({
width: column.width,
onResize: handleResize(index),
}),
}; };
})}
rowKey="id" const headerItem = (col) => (
loading={loading} <Dropdown
dataSource={dataSource} className="prod-header-dropdown"
scroll={{ x: 1000 }} overlay={
onChange={handleTableChange} <Menu onClick={removeColumn}>
/> <Menu.Item key={col.key}>
</ReactDragListView.DragColumn> {t("production.actions.removecolumn")}
</div> </Menu.Item>
); </Menu>
}
trigger={["contextMenu"]}
>
<span>{col.title}</span>
</Dropdown>
);
const dataSource =
searchText === ""
? data
: data.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.ins_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
);
// const handleSelectRecord = (record) => {
// if (selected !== record.id) {
// setSelected(record.id);
// } else {
// setSelected(null);
// }
// };
if (!!!columns) return <div>No columns found.</div>;
const totalHrs = data
.reduce(
(acc, val) =>
acc +
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAB = data
.reduce(
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAR = data
.reduce(
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
return (
<div>
<PageHeader
title={
<Space>
<Statistic
title={t("dashboard.titles.productionhours")}
value={totalHrs}
/>
<Statistic
title={t("dashboard.titles.labhours")}
value={totalLAB}
/>
<Statistic
title={t("dashboard.titles.larhours")}
value={totalLAR}
/>
<Statistic
title={t("appointments.labels.inproduction")}
value={dataSource && dataSource.length}
/>
</Space>
}
extra={
<Space wrap>
<Button onClick={() => refetch && refetch()}>
<SyncOutlined/>
</Button>
<ProductionListColumnsAdd
columnState={[columns, setColumns]}
tableState={state}
/>
<ProductionListSaveConfigButton
columns={columns}
tableState={state}
/>
<ProductionListTableViewSelect
state={state}
setState={setState}
setColumns={setColumns}
/>
<Input
onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")}
value={searchText}
/>
<ProductionListPrint/>
</Space>
}
/>
<ProductionListDetail jobs={dataSource}/>
<ReactDragListView.DragColumn
onDragEnd={onDragEnd}
nodeSelector="th"
handleSelector=".prod-header-dropdown"
>
<Table
sticky
pagination={false}
size="small"
{...(Production_List_Status_Colors.treatment === "on" && {
onRow: (record, index) => {
if (!bodyshop.md_ro_statuses.production_colors) return null;
const color = bodyshop.md_ro_statuses.production_colors.find(
(x) => x.status === record.status
);
if (!color) {
if (index % 2 === 0)
return {
style: {
backgroundColor: `rgb(236, 236, 236)`,
},
};
return null;
}
return {
className: "rowWithColor",
style: {
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
},
};
},
})}
components={{
header: {
cell: ResizeableTitle,
},
}}
columns={columns.map((c, index) => {
return {
...c,
filteredValue: state.filteredInfo[c.key] || null,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),
ellipsis: true,
width: c.width ?? 100,
onHeaderCell: (column) => ({
width: column.width,
onResize: handleResize(index),
}),
};
})}
rowKey="id"
loading={loading}
dataSource={dataSource}
scroll={{x: 1000}}
onChange={handleTableChange}
/>
</ReactDragListView.DragColumn>
</div>
);
} }
export default connect(mapStateToProps, null)(ProductionListTable); export default connect(mapStateToProps, null)(ProductionListTable);

View File

@@ -251,7 +251,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
> >
<DatePicker.RangePicker <DatePicker.RangePicker
format="MM/DD/YYYY" format="MM/DD/YYYY"
ranges={DatePIckerRanges} presets={DatePIckerRanges}
/> />
</Form.Item> </Form.Item>
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}> <Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>

View File

@@ -70,7 +70,7 @@ export function ScheduleJobModalComponent({
form.setFieldsValue({ form.setFieldsValue({
scheduled_completion: dayjs(values.start).businessAdd( scheduled_completion: dayjs(values.start).businessAdd(
totalHours / bodyshop.target_touchtime, totalHours / bodyshop.target_touchtime,
"days" "day"
), ),
}); });
} }
@@ -138,7 +138,7 @@ export function ScheduleJobModalComponent({
if (ssDate.isBefore(dayjs())) { if (ssDate.isBefore(dayjs())) {
form.setFieldsValue({ start: dayjs() }); form.setFieldsValue({ start: dayjs() });
} else { } else {
form.setFieldsValue({ start: dayjs(d).add(8, "hours") }); form.setFieldsValue({ start: dayjs(d).add(8, "hour") });
} }
handleDateBlur(); handleDateBlur();
}} }}

View File

@@ -61,9 +61,9 @@ export const ListOfDaysInCurrentMonth = () => {
const days = []; const days = [];
const dateStart = dayjs().startOf("month"); const dateStart = dayjs().startOf("month");
const dateEnd = dayjs().endOf("month"); const dateEnd = dayjs().endOf("month");
while (dateEnd.diff(dateStart, "days") > 0) { while (dateEnd.diff(dateStart, "day") > 0) {
days.push(dateStart.format("YYYY-MM-DD")); days.push(dateStart.format("YYYY-MM-DD"));
dateStart.add(1, "days"); dateStart.add(1, "day");
} }
days.push(dateEnd.format("YYYY-MM-DD")); days.push(dateEnd.format("YYYY-MM-DD"));
return days; return days;
@@ -73,9 +73,9 @@ export const ListDaysBetween = ({ start, end }) => {
const days = []; const days = [];
const dateStart = dayjs(start); const dateStart = dayjs(start);
const dateEnd = dayjs(end); const dateEnd = dayjs(end);
while (dateEnd.diff(dateStart, "days") > 0) { while (dateEnd.diff(dateStart, "day") > 0) {
days.push(dateStart.format("YYYY-MM-DD")); days.push(dateStart.format("YYYY-MM-DD"));
dateStart.add(1, "days"); dateStart.add(1, "day");
} }
days.push(dateEnd.format("YYYY-MM-DD")); days.push(dateEnd.format("YYYY-MM-DD"));
return days; return days;

View File

@@ -132,7 +132,7 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
dataIndex: "length", dataIndex: "length",
key: "length", key: "length",
render: (text, record) => render: (text, record) =>
dayjs(record.end).diff(dayjs(record.start), "days", true).toFixed(1), dayjs(record.end).diff(dayjs(record.start), "day", true).toFixed(1),
}, },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),

View File

@@ -1,10 +1,10 @@
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { Button, Card, Tabs } from "antd"; import {Button, Card, Tabs} 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";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import ShopInfoGeneral from "./shop-info.general.component"; import ShopInfoGeneral from "./shop-info.general.component";
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component"; import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
import ShopInfoLaborRates from "./shop-info.laborrates.component"; import ShopInfoLaborRates from "./shop-info.laborrates.component";
@@ -15,88 +15,90 @@ import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycen
import ShopInfoROStatusComponent from "./shop-info.rostatus.component"; import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component"; import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
import ShopInfoSpeedPrint from "./shop-info.speedprint.component"; import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
import { useNavigate, useLocation } from "react-router-dom"; import {useLocation, useNavigate} from "react-router-dom";
import queryString from "query-string"; import queryString from "query-string";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent); export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
export function ShopInfoComponent({ bodyshop, form, saveLoading }) { export function ShopInfoComponent({bodyshop, form, saveLoading}) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation();
const history = useNavigate();
const location = useLocation();
const search = queryString.parse(location.search);
return ( const { treatments: {CriticalPartsScanning} } = useSplitTreatments({
<Card attributes: {},
extra={ names: ["CriticalPartsScanning"],
<Button splitKey: bodyshop.imexshopid,
type="primary" });
loading={saveLoading}
onClick={() => form.submit()}
>
{t("general.actions.save")}
</Button>
}
>
<Tabs
defaultActiveKey={search.subtab}
onChange={(key) =>
history({
search: `?tab=${search.tab}&subtab=${key}`,
})
}
>
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
<ShopInfoGeneral form={form} />
</Tabs.TabPane>
<Tabs.TabPane key="speedprint" tab={t("bodyshop.labels.speedprint")}>
<ShopInfoSpeedPrint form={form} />
</Tabs.TabPane>
<Tabs.TabPane key="rbac" tab={t("bodyshop.labels.rbac")}>
<ShopInfoRbacComponent form={form} />
</Tabs.TabPane>
<Tabs.TabPane key="roStatus" tab={t("bodyshop.labels.jobstatuses")}>
<ShopInfoROStatusComponent form={form} />
</Tabs.TabPane>
<Tabs.TabPane key="scheduling" tab={t("bodyshop.labels.scheduling")}>
<ShopInfoSchedulingComponent form={form} />
</Tabs.TabPane>
<Tabs.TabPane
key="orderStatus"
tab={t("bodyshop.labels.orderstatuses")}
>
<ShopInfoOrderStatusComponent form={form} />
</Tabs.TabPane>
<Tabs.TabPane
key="responsibilityCenters"
tab={t("bodyshop.labels.responsibilitycenters.title")}
>
<ShopInfoResponsibilityCenterComponent form={form} />
</Tabs.TabPane>
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}> const {t} = useTranslation();
<ShopInfoIntakeChecklistComponent form={form} /> const history = useNavigate();
</Tabs.TabPane> const location = useLocation();
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}> const search = queryString.parse(location.search);
<ShopInfoLaborRates form={form} />
</Tabs.TabPane> return (
{CriticalPartsScanning.treatment === "on" && ( <Card
<Tabs.TabPane key="partsscan" tab={t("bodyshop.labels.partsscan")}> extra={
<ShopInfoPartsScan form={form} /> <Button
</Tabs.TabPane> type="primary"
)} loading={saveLoading}
</Tabs> onClick={() => form.submit()}
</Card> >
); {t("general.actions.save")}
</Button>
}
>
<Tabs
defaultActiveKey={search.subtab}
onChange={(key) =>
history({
search: `?tab=${search.tab}&subtab=${key}`,
})
}
>
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
<ShopInfoGeneral form={form}/>
</Tabs.TabPane>
<Tabs.TabPane key="speedprint" tab={t("bodyshop.labels.speedprint")}>
<ShopInfoSpeedPrint form={form}/>
</Tabs.TabPane>
<Tabs.TabPane key="rbac" tab={t("bodyshop.labels.rbac")}>
<ShopInfoRbacComponent form={form}/>
</Tabs.TabPane>
<Tabs.TabPane key="roStatus" tab={t("bodyshop.labels.jobstatuses")}>
<ShopInfoROStatusComponent form={form}/>
</Tabs.TabPane>
<Tabs.TabPane key="scheduling" tab={t("bodyshop.labels.scheduling")}>
<ShopInfoSchedulingComponent form={form}/>
</Tabs.TabPane>
<Tabs.TabPane
key="orderStatus"
tab={t("bodyshop.labels.orderstatuses")}
>
<ShopInfoOrderStatusComponent form={form}/>
</Tabs.TabPane>
<Tabs.TabPane
key="responsibilityCenters"
tab={t("bodyshop.labels.responsibilitycenters.title")}
>
<ShopInfoResponsibilityCenterComponent form={form}/>
</Tabs.TabPane>
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}>
<ShopInfoIntakeChecklistComponent form={form}/>
</Tabs.TabPane>
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}>
<ShopInfoLaborRates form={form}/>
</Tabs.TabPane>
{CriticalPartsScanning.treatment === "on" && (
<Tabs.TabPane key="partsscan" tab={t("bodyshop.labels.partsscan")}>
<ShopInfoPartsScan form={form}/>
</Tabs.TabPane>
)}
</Tabs>
</Card>
);
} }

View File

@@ -1,5 +1,5 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { import {
Button, Button,
DatePicker, DatePicker,
@@ -35,13 +35,14 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral); export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export function ShopInfoGeneral({ form, bodyshop }) { export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation();
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation();
const { treatments: {ClosingPeriod} } = useSplitTreatments({
attributes: {},
names: ["ClosingPeriod"],
splitKey: bodyshop && bodyshop.imexshopid,
});
return ( return (
<div> <div>
@@ -413,13 +414,12 @@ export function ShopInfoGeneral({ form, bodyshop }) {
{ClosingPeriod.treatment === "on" && ( {ClosingPeriod.treatment === "on" && (
<> <>
<Form.Item <Form.Item
allowClear
name={["accountingconfig", "ClosingPeriod"]} name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")} label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
> >
<DatePicker.RangePicker <DatePicker.RangePicker
format="MM/DD/YYYY" format="MM/DD/YYYY"
ranges={DatePickerRanges} presets={DatePickerRanges}
/> />
</Form.Item> </Form.Item>
</> </>

View File

@@ -1,94 +1,97 @@
import { Form, Input } from "antd"; import {Form, Input} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(ShopInfoOrderStatusComponent); )(ShopInfoOrderStatusComponent);
export function ShopInfoOrderStatusComponent({ bodyshop, form }) { export function ShopInfoOrderStatusComponent({bodyshop, form}) {
const { t } = useTranslation(); const {t} = useTranslation();
const { OEConnection } = useTreatments(
["OEConnection"],
{},
bodyshop.imexshopid
);
return (
<LayoutFormRow header={t("bodyshop.labels.orderstatuses")}>
<Form.Item
label={t("bodyshop.fields.statuses.default_bo")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_order_statuses", "default_bo"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_ordered")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_order_statuses", "default_ordered"]}
>
<Input />
</Form.Item>
<Form.Item const {treatments: {OEConnection}} = useSplitTreatments({
label={t("bodyshop.fields.statuses.default_received")} attributes: {},
rules={[ names: ["OEConnection"],
{ splitKey: bodyshop.imexshopid,
required: true, });
//message: t("general.validation.required"),
}, return (
]} <LayoutFormRow header={t("bodyshop.labels.orderstatuses")}>
name={["md_order_statuses", "default_received"]} <Form.Item
> label={t("bodyshop.fields.statuses.default_bo")}
<Input /> rules={[
</Form.Item> {
<Form.Item required: true,
label={t("bodyshop.fields.statuses.default_returned")} //message: t("general.validation.required"),
rules={[ },
{ ]}
required: true, name={["md_order_statuses", "default_bo"]}
//message: t("general.validation.required"), >
}, <Input/>
]} </Form.Item>
name={["md_order_statuses", "default_returned"]} <Form.Item
> label={t("bodyshop.fields.statuses.default_ordered")}
<Input /> rules={[
</Form.Item> {
{OEConnection.treatment === "on" && ( required: true,
<Form.Item //message: t("general.validation.required"),
label={t("bodyshop.fields.statuses.default_quote")} },
rules={[ ]}
{ name={["md_order_statuses", "default_ordered"]}
required: true, >
//message: t("general.validation.required"), <Input/>
}, </Form.Item>
]}
name={["md_order_statuses", "default_quote"]} <Form.Item
> label={t("bodyshop.fields.statuses.default_received")}
<Input /> rules={[
</Form.Item> {
)} required: true,
</LayoutFormRow> //message: t("general.validation.required"),
); },
]}
name={["md_order_statuses", "default_received"]}
>
<Input/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_returned")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_order_statuses", "default_returned"]}
>
<Input/>
</Form.Item>
{OEConnection.treatment === "on" && (
<Form.Item
label={t("bodyshop.fields.statuses.default_quote")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_order_statuses", "default_quote"]}
>
<Input/>
</Form.Item>
)}
</LayoutFormRow>
);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,411 +1,415 @@
import { DeleteFilled } from "@ant-design/icons"; import {DeleteFilled} from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import {Button, Form, Select, Space} from "antd";
import { Button, Form, Select, Space } from "antd"; import React, {useState} from "react";
import React, { useState } from "react"; import {ChromePicker} from "react-color";
import { ChromePicker } from "react-color"; import {useTranslation} from "react-i18next";
import { useTranslation } from "react-i18next";
import styled from "styled-components"; import styled from "styled-components";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(ShopInfoROStatusComponent); )(ShopInfoROStatusComponent);
const SelectorDiv = styled.div` const SelectorDiv = styled.div`
.ant-form-item .ant-select { .ant-form-item .ant-select {
width: 200px; width: 200px;
} }
`; `;
export function ShopInfoROStatusComponent({ bodyshop, form }) {
const { t } = useTranslation();
const { Production_List_Status_Colors } = useTreatments(
["Production_List_Status_Colors"],
{},
bodyshop.imexshopid
);
const [options, setOptions] = useState( export function ShopInfoROStatusComponent({bodyshop, form}) {
form.getFieldValue(["md_ro_statuses", "statuses"]) || []
);
const handleBlur = () => { const {t} = useTranslation();
setOptions(form.getFieldValue(["md_ro_statuses", "statuses"]));
};
return ( const { treatments: {Production_List_Status_Colors} } = useSplitTreatments({
<SelectorDiv id="jobstatus"> attributes: {},
<Form.Item names: ["Production_List_Status_Colors"],
name={["md_ro_statuses", "statuses"]} splitKey: bodyshop.imexshopid,
label={t("bodyshop.labels.alljobstatuses")} });
rules={[
{ const [options, setOptions] = useState(
required: true, form.getFieldValue(["md_ro_statuses", "statuses"]) || []
//message: t("general.validation.required"), );
type: "array",
}, const handleBlur = () => {
]} setOptions(form.getFieldValue(["md_ro_statuses", "statuses"]));
> };
<Select mode="tags" onBlur={handleBlur} />
</Form.Item> return (
<Form.Item <SelectorDiv id="jobstatus">
name={["md_ro_statuses", "active_statuses"]} <Form.Item
label={t("bodyshop.fields.statuses.active_statuses")} name={["md_ro_statuses", "statuses"]}
rules={[ label={t("bodyshop.labels.alljobstatuses")}
{ rules={[
required: true, {
//message: t("general.validation.required"), required: true,
type: "array", //message: t("general.validation.required"),
}, type: "array",
]} },
> ]}
<Select mode="multiple"> >
{options.map((item, idx) => ( <Select mode="tags" onBlur={handleBlur}/>
<Select.Option key={idx} value={item}> </Form.Item>
{item} <Form.Item
</Select.Option> name={["md_ro_statuses", "active_statuses"]}
))} label={t("bodyshop.fields.statuses.active_statuses")}
</Select> rules={[
</Form.Item> {
<Form.Item required: true,
name={["md_ro_statuses", "pre_production_statuses"]} //message: t("general.validation.required"),
label={t("bodyshop.fields.statuses.pre_production_statuses")} type: "array",
rules={[ },
{ ]}
required: true, >
//message: t("general.validation.required"), <Select mode="multiple">
type: "array", {options.map((item, idx) => (
}, <Select.Option key={idx} value={item}>
]} {item}
> </Select.Option>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "production_statuses"]}
label={t("bodyshop.fields.statuses.production_statuses")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "post_production_statuses"]}
label={t("bodyshop.fields.statuses.post_production_statuses")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "ready_statuses"]}
label={t("bodyshop.fields.statuses.ready_statuses")}
rules={[
{
//required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "additional_board_statuses"]}
label={t("bodyshop.fields.statuses.additional_board_statuses")}
rules={[
{
//required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<LayoutFormRow noDivider>
<Form.Item
label={t("bodyshop.fields.statuses.default_scheduled")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_scheduled"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_arrived")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_arrived"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_exported")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_exported"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_imported")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_imported"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_invoiced")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_invoiced"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_completed")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_completed"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_delivered")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_delivered"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_void")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_void"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
</LayoutFormRow>
{Production_List_Status_Colors.treatment === "on" && (
<LayoutFormRow
grow
header={t("bodyshop.fields.statuses.production_colors")}
id="production_colors"
>
<Form.List name={["md_ro_statuses", "production_colors"]}>
{(fields, { add, remove, move }) => {
return (
<div>
<LayoutFormRow>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space direction="vertical">
<div style={{ display: "flex" }}>
<Form.Item
style={{ flex: 1 }}
label={t("jobs.fields.status")}
key={`${index}status`}
name={[field.name, "status"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</div>
<Form.Item
label={t("bodyshop.fields.statuses.color")}
key={`${index}color`}
name={[field.name, "color"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<ColorPicker />
</Form.Item>
</Space>
</Form.Item>
))} ))}
</LayoutFormRow> </Select>
<Form.Item> </Form.Item>
<Button <Form.Item
type="dashed" name={["md_ro_statuses", "pre_production_statuses"]}
onClick={() => { label={t("bodyshop.fields.statuses.pre_production_statuses")}
add(); rules={[
}} {
style={{ width: "100%" }} required: true,
> //message: t("general.validation.required"),
{t("general.actions.add")} type: "array",
</Button> },
</Form.Item> ]}
</div> >
); <Select mode="multiple">
}} {options.map((item, idx) => (
</Form.List> <Select.Option key={idx} value={item}>
</LayoutFormRow> {item}
)} </Select.Option>
</SelectorDiv> ))}
); </Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "production_statuses"]}
label={t("bodyshop.fields.statuses.production_statuses")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "post_production_statuses"]}
label={t("bodyshop.fields.statuses.post_production_statuses")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "ready_statuses"]}
label={t("bodyshop.fields.statuses.ready_statuses")}
rules={[
{
//required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={["md_ro_statuses", "additional_board_statuses"]}
label={t("bodyshop.fields.statuses.additional_board_statuses")}
rules={[
{
//required: true,
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<LayoutFormRow noDivider>
<Form.Item
label={t("bodyshop.fields.statuses.default_scheduled")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_scheduled"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_arrived")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_arrived"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_exported")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_exported"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_imported")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_imported"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_invoiced")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_invoiced"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_completed")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_completed"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_delivered")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_delivered"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_void")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_ro_statuses", "default_void"]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
</LayoutFormRow>
{Production_List_Status_Colors.treatment === "on" && (
<LayoutFormRow
grow
header={t("bodyshop.fields.statuses.production_colors")}
id="production_colors"
>
<Form.List name={["md_ro_statuses", "production_colors"]}>
{(fields, {add, remove, move}) => {
return (
<div>
<LayoutFormRow>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space direction="vertical">
<div style={{display: "flex"}}>
<Form.Item
style={{flex: 1}}
label={t("jobs.fields.status")}
key={`${index}status`}
name={[field.name, "status"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select>
{options.map((item, idx) => (
<Select.Option key={idx} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</div>
<Form.Item
label={t("bodyshop.fields.statuses.color")}
key={`${index}color`}
name={[field.name, "color"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<ColorPicker/>
</Form.Item>
</Space>
</Form.Item>
))}
</LayoutFormRow>
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{width: "100%"}}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
)}
</SelectorDiv>
);
} }
export const ColorPicker = ({ value, onChange, style, ...restProps }) => { export const ColorPicker = ({value, onChange, style, ...restProps}) => {
const handleChange = (color) => { const handleChange = (color) => {
if (onChange) onChange(color.rgb); if (onChange) onChange(color.rgb);
}; };
return ( return (
<ChromePicker <ChromePicker
{...restProps} {...restProps}
color={value} color={value}
onChangeComplete={handleChange} onChangeComplete={handleChange}
/> />
); );
}; };

View File

@@ -95,7 +95,7 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
]} ]}
> >
<DatePicker.RangePicker <DatePicker.RangePicker
ranges={DatePIckerRanges} presets={DatePIckerRanges}
format={"MM/DD/YYYY"} format={"MM/DD/YYYY"}
/> />
</Form.Item> </Form.Item>

View File

@@ -41,7 +41,7 @@ export default function TimeTicketsDatesSelector() {
end ? dayjs(end) : dayjs().endOf("week"), end ? dayjs(end) : dayjs().endOf("week"),
]} ]}
format="MM/DD/YYYY" format="MM/DD/YYYY"
ranges={DatePickerRanges} presets={DatePickerRanges}
onCalendarChange={handleChange} onCalendarChange={handleChange}
/> />
); );

View File

@@ -187,7 +187,7 @@ export function TimeTicketList({
return ( return (
<div> <div>
{dayjs(record.clockoff) {dayjs(record.clockoff)
.diff(dayjs(record.clockon), "hours", true) .diff(dayjs(record.clockon), "hour", true)
.toFixed(2)} .toFixed(2)}
</div> </div>
); );

View File

@@ -124,7 +124,7 @@ const JobRelatedTicketsTable = ({
if (!!val.clockoff && !!val.clockon) if (!!val.clockoff && !!val.clockon)
return ( return (
acc + acc +
dayjs(val.clockoff).diff(dayjs(val.clockon), "hours", true) dayjs(val.clockoff).diff(dayjs(val.clockon), "hour", true)
); );
return acc; return acc;
}, 0); }, 0);
@@ -251,7 +251,7 @@ const ShiftRelatedTicketsTable = ({
const clockHrs = item.tickets.reduce((acc, val) => { const clockHrs = item.tickets.reduce((acc, val) => {
if (!!val.clockoff && !!val.clockon) if (!!val.clockoff && !!val.clockon)
return ( return (
acc + dayjs(val.clockoff).diff(dayjs(val.clockon), "hours", true) acc + dayjs(val.clockoff).diff(dayjs(val.clockon), "hour", true)
); );
return acc; return acc;
}, 0); }, 0);

View File

@@ -1,262 +1,249 @@
import { DeleteFilled } from "@ant-design/icons"; import {DeleteFilled} from "@ant-design/icons";
import { useApolloClient } from "@apollo/client"; import {useApolloClient} from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
import { import {Button, Divider, Form, Input, InputNumber, Space, Switch,} from "antd";
Button,
Divider,
Form,
Input,
InputNumber,
Space,
Switch,
} from "antd";
import {PageHeader} from "@ant-design/pro-layout"; import {PageHeader} from "@ant-design/pro-layout";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { CHECK_VENDOR_NAME } from "../../graphql/vendors.queries"; import {CHECK_VENDOR_NAME} from "../../graphql/vendors.queries";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { import PhoneFormItem, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component";
PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorsPhonebookAdd from "../vendors-phonebook-add/vendors-phonebook-add.component"; import VendorsPhonebookAdd from "../vendors-phonebook-add/vendors-phonebook-add.component";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(VendorsFormComponent); )(VendorsFormComponent);
export function VendorsFormComponent({ export function VendorsFormComponent({bodyshop, form, formLoading, handleDelete, responsibilityCenters, selectedvendor}) {
bodyshop, const {t} = useTranslation();
form, const client = useApolloClient();
formLoading,
handleDelete,
responsibilityCenters,
selectedvendor,
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { DmsAp } = useTreatments(
["DmsAp"],
{},
bodyshop && bodyshop.imexshopid
);
const { getFieldValue } = form;
return (
<div>
<PageHeader
title={
<Form.Item shouldUpdate>{() => form.getFieldValue("name")}</Form.Item>
}
extra={
<Space>
<Form.Item
label={t("vendors.fields.active")}
name="active"
initialValue={true}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Button
onClick={() => form.submit()}
type="primary"
loading={formLoading}
>
{t("general.actions.save")}
</Button>
<Button
type="danger"
disabled={selectedvendor === "new"}
onClick={handleDelete}
loading={formLoading}
>
{t("general.actions.delete")}
</Button>
<VendorsPhonebookAdd const {treatments: {DmsAp}} = useSplitTreatments({
form={form} attributes: {},
disabled={form.isFieldsTouched()} names: ["DmsAp"],
/> splitKey: bodyshop && bodyshop.imexshopid
</Space> });
}
/>
<FormFieldsChanged form={form} />
<LayoutFormRow grow>
<Form.Item
label={t("vendors.fields.name")}
name="name"
validateTrigger="onBlur"
hasFeedback
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
async validator(rule, value) {
if (value) {
const response = await client.query({
query: CHECK_VENDOR_NAME,
variables: {
name: value,
},
});
if (response.data.vendors_aggregate.aggregate.count === 0) {
return Promise.resolve(); const {getFieldValue} = form;
} else if ( return (
response.data.vendors_aggregate.nodes.length === 1 && <div>
response.data.vendors_aggregate.nodes[0].id === <PageHeader
form.getFieldValue("id") title={
) { <Form.Item shouldUpdate>{() => form.getFieldValue("name")}</Form.Item>
return Promise.resolve();
}
return Promise.reject(
t("vendors.validation.unique_vendor_name")
);
} else {
return Promise.resolve();
} }
}, extra={
}), <Space>
]} <Form.Item
> label={t("vendors.fields.active")}
<Input /> name="active"
</Form.Item> initialValue={true}
valuePropName="checked"
>
<Switch/>
</Form.Item>
<Button
onClick={() => form.submit()}
type="primary"
loading={formLoading}
>
{t("general.actions.save")}
</Button>
<Button
type="danger"
disabled={selectedvendor === "new"}
onClick={handleDelete}
loading={formLoading}
>
{t("general.actions.delete")}
</Button>
<Form.Item <VendorsPhonebookAdd
label={t("vendors.fields.email")} form={form}
rules={ disabled={form.isFieldsTouched()}
[ />
// { </Space>
// type: "email", }
// message: t("general.validation.invalidemail"), />
// }, <FormFieldsChanged form={form}/>
] <LayoutFormRow grow>
} <Form.Item
name="email" label={t("vendors.fields.name")}
> name="name"
<FormItemEmail email={getFieldValue("email")} /> validateTrigger="onBlur"
</Form.Item> hasFeedback
<Form.Item rules={[
label={t("vendors.fields.phone")}
name="phone"
rules={[
({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "phone"),
]}
>
<PhoneFormItem />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.street1")} name="street1">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.street2")} name="street2">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.city")} name="city">
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.state")} name="state">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.zip")} name="zip">
<Input />
</Form.Item>
<Form.Item label={t("vendors.fields.country")} name="country">
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.discount")} name="discount">
<InputNumber min={0} max={1} precision={2} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber min={0} />
</Form.Item>
{
// <Form.Item
// label={t("vendors.fields.cost_center")}
// rules={[
// { required: true, message: t("general.validation.required") },
// ]}
// name="cost_center"
// >
// <Select style={{ width: "150px" }}>
// {responsibilityCenters.costs.map((item) => (
// <Select.Option key={item.name}>{item.name}</Select.Option>
// ))}
// </Select>
// </Form.Item>
}
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<Form.Item label={t("vendors.fields.dmsid")} name="dmsid">
<Input />
</Form.Item>
)}
<Divider align="left">{t("vendors.labels.preferredmakes")}</Divider>
<Form.List name="favorite">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space wrap>
<Form.Item
label={t("vendors.fields.make")}
key={`${index}make`}
name={[field.name]}
rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ({getFieldValue}) => ({
> async validator(rule, value) {
<Input /> if (value) {
</Form.Item> const response = await client.query({
query: CHECK_VENDOR_NAME,
variables: {
name: value,
},
});
<DeleteFilled if (response.data.vendors_aggregate.aggregate.count === 0) {
onClick={() => { return Promise.resolve();
remove(field.name); } else if (
}} response.data.vendors_aggregate.nodes.length === 1 &&
/> response.data.vendors_aggregate.nodes[0].id ===
</Space> form.getFieldValue("id")
</Form.Item> ) {
))} return Promise.resolve();
<Form.Item> }
<Button return Promise.reject(
type="dashed" t("vendors.validation.unique_vendor_name")
onClick={() => { );
add(); } else {
}} return Promise.resolve();
style={{ width: "100%" }} }
},
}),
]}
> >
{t("vendors.actions.newpreferredmake")} <Input/>
</Button> </Form.Item>
</Form.Item>
</div> <Form.Item
); label={t("vendors.fields.email")}
}} rules={
</Form.List> [
</div> // {
); // type: "email",
// message: t("general.validation.invalidemail"),
// },
]
}
name="email"
>
<FormItemEmail email={getFieldValue("email")}/>
</Form.Item>
<Form.Item
label={t("vendors.fields.phone")}
name="phone"
rules={[
({getFieldValue}) =>
PhoneItemFormatterValidation(getFieldValue, "phone"),
]}
>
<PhoneFormItem/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.street1")} name="street1">
<Input/>
</Form.Item>
<Form.Item label={t("vendors.fields.street2")} name="street2">
<Input/>
</Form.Item>
<Form.Item label={t("vendors.fields.city")} name="city">
<Input/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.state")} name="state">
<Input/>
</Form.Item>
<Form.Item label={t("vendors.fields.zip")} name="zip">
<Input/>
</Form.Item>
<Form.Item label={t("vendors.fields.country")} name="country">
<Input/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("vendors.fields.discount")} name="discount">
<InputNumber min={0} max={1} precision={2} step={0.01}/>
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber min={0}/>
</Form.Item>
{
// <Form.Item
// label={t("vendors.fields.cost_center")}
// rules={[
// { required: true, message: t("general.validation.required") },
// ]}
// name="cost_center"
// >
// <Select style={{ width: "150px" }}>
// {responsibilityCenters.costs.map((item) => (
// <Select.Option key={item.name}>{item.name}</Select.Option>
// ))}
// </Select>
// </Form.Item>
}
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<Form.Item label={t("vendors.fields.dmsid")} name="dmsid">
<Input/>
</Form.Item>
)}
<Divider align="left">{t("vendors.labels.preferredmakes")}</Divider>
<Form.List name="favorite">
{(fields, {add, remove}) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space wrap>
<Form.Item
label={t("vendors.fields.make")}
key={`${index}make`}
name={[field.name]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input/>
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</Space>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{width: "100%"}}
>
{t("vendors.actions.newpreferredmake")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</div>
);
} }

View File

@@ -23,7 +23,7 @@ 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 { useNavigate } from 'react-router-dom'; // import { useNavigate } from 'react-router-dom';
import { useTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -51,16 +51,12 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
// const history = useHistory(); // const history = useHistory();
const [closeJob] = useMutation(UPDATE_JOB); const [closeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { Qb_Multi_Ar } = useTreatments(
["Qb_Multi_Ar"], const { treatments: {Qb_Multi_Ar,ClosingPeriod} } = useSplitTreatments({
{}, attributes: {},
bodyshop && bodyshop.imexshopid names: ["Qb_Multi_Ar", "ClosingPeriod"],
); splitKey: bodyshop && bodyshop.imexshopid,
const { ClosingPeriod } = useTreatments( });
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
const handleFinish = async ({ removefromproduction, ...values }) => { const handleFinish = async ({ removefromproduction, ...values }) => {
setLoading(true); setLoading(true);

View File

@@ -1,4 +1,4 @@
import {BackTop, Layout} from "antd"; import {FloatButton, Layout} from "antd";
import preval from "preval.macro"; import preval from "preval.macro";
import React, {lazy, Suspense, useEffect} from "react"; import React, {lazy, Suspense, useEffect} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
@@ -371,7 +371,7 @@ export function Manage({conflict, bodyshop}) {
{PageContent} {PageContent}
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
<BackTop/> <FloatButton.BackTop/>
<Footer> <Footer>
<div <div
style={{ style={{

View File

@@ -1,4 +1,4 @@
import { BackTop, Layout } from "antd"; import {FloatButton, Layout} from "antd";
import React, { Suspense, lazy, useEffect } from "react"; import React, { Suspense, lazy, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -90,7 +90,7 @@ export function TechPage({ technician }) {
</FeatureWrapper> </FeatureWrapper>
</Suspense> </Suspense>
</ErrorBoundary> </ErrorBoundary>
<BackTop /> <FloatButton.BackTop />
</Content> </Content>
</Layout> </Layout>
</Layout> </Layout>

View File

@@ -1,27 +1,62 @@
import dayjs from "./day"; import dayjs from "./day";
const range = {
Today: [dayjs(), dayjs()], const range = [
"Last 14 days": [dayjs().subtract(14, "day"), dayjs()], {
"Last 7 days": [dayjs().subtract(7, "day"), dayjs()], label: 'Today',
"Next 7 days": [dayjs(), dayjs().add(7, "day")], value: [dayjs(), dayjs()]
"Next 14 days": [dayjs(), dayjs().add(14, "day")], },
"Last Month": [ {
dayjs().startOf("month").subtract(1, "month"), label: 'Last 14 days',
dayjs().startOf("month").subtract(1, "month").endOf("month"), value: [dayjs().subtract(14, "day"), dayjs()]
], },
"This Month": [dayjs().startOf("month"), dayjs().endOf("month")], {
"Next Month": [ label: 'Last 7 days',
dayjs().startOf("month").add(1, "month"), value: [dayjs().subtract(7, "day"), dayjs()]
dayjs().startOf("month").add(1, "month").endOf("month"), },
], {
"Last Quarter": [ label: 'Next 7 days',
dayjs().startOf("quarter").subtract(1, "quarter"), value: [dayjs(), dayjs().add(7, "day")]
dayjs().startOf("quarter").subtract(1, "day"), },
], {
"This Quarter": [ label: 'Next 14 days',
dayjs().startOf("quarter"), value: [dayjs(), dayjs().add(14, "day")],
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day"), },
], {
"Last 90 Days": [dayjs().add(-90, "day"), dayjs()], label: 'Last Month',
}; value: [
dayjs().startOf("month").subtract(1, "month"),
dayjs().startOf("month").subtract(1, "month").endOf("month"),
]
},
{
label: 'This Month',
value: [dayjs().startOf("month"), dayjs().endOf("month")]
},
{
label: 'Next Month',
value: [
dayjs().startOf("month").add(1, "month"),
dayjs().startOf("month").add(1, "month").endOf("month"),
]
},
{
label: 'Last Quarter',
value: [
dayjs().startOf("quarter").subtract(1, "quarter"),
dayjs().startOf("quarter").subtract(1, "day"),
]
},
{
label: 'This Quarter',
value: [
dayjs().startOf("quarter"),
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day"),
]
},
{
label: 'Last 90 Days',
value: [dayjs().add(-90, "day"), dayjs()],
}
]
export default range; export default range;

View File

@@ -54,27 +54,27 @@ function functionMapper(f, timezone) {
case "date.yesterday": case "date.yesterday":
return moment().tz(timezone).subtract(1, "day").format(isoFormat); return moment().tz(timezone).subtract(1, "day").format(isoFormat);
case "date.3daysago": case "date.3daysago":
return moment().tz(timezone).subtract(3, "days").format(isoFormat); return moment().tz(timezone).subtract(3, "day").format(isoFormat);
case "date.7daysago": case "date.7daysago":
return moment().tz(timezone).subtract(7, "days").format(isoFormat); return moment().tz(timezone).subtract(7, "day").format(isoFormat);
case "date.tomorrow": case "date.tomorrow":
return moment().tz(timezone).add(1, "day").format(isoFormat); return moment().tz(timezone).add(1, "day").format(isoFormat);
case "date.3daysfromnow": case "date.3daysfromnow":
return moment().tz(timezone).add(3, "days").format(isoFormat); return moment().tz(timezone).add(3, "day").format(isoFormat);
case "date.7daysfromnow": case "date.7daysfromnow":
return moment().tz(timezone).add(7, "days").format(isoFormat); return moment().tz(timezone).add(7, "day").format(isoFormat);
case "date.yesterdaytz": case "date.yesterdaytz":
return moment().tz(timezone).subtract(1, "day"); return moment().tz(timezone).subtract(1, "day");
case "date.3daysagotz": case "date.3daysagotz":
return moment().tz(timezone).subtract(3, "days"); return moment().tz(timezone).subtract(3, "day");
case "date.7daysagotz": case "date.7daysagotz":
return moment().tz(timezone).subtract(7, "days"); return moment().tz(timezone).subtract(7, "day");
case "date.tomorrowtz": case "date.tomorrowtz":
return moment().tz(timezone).add(1, "day"); return moment().tz(timezone).add(1, "day");
case "date.3daysfromnowtz": case "date.3daysfromnowtz":
return moment().tz(timezone).add(3, "days"); return moment().tz(timezone).add(3, "day");
case "date.7daysfromnowtz": case "date.7daysfromnowtz":
return moment().tz(timezone).add(7, "days"); return moment().tz(timezone).add(7, "day");
case "date.now": case "date.now":
return moment().tz(timezone); return moment().tz(timezone);