Merge branch 'master-AIO' into feature/IO-2825-Node-20-Update
This commit is contained in:
@@ -14,7 +14,6 @@ import dayjs from "../../utils/day";
|
|||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
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 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";
|
||||||
@@ -22,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -276,7 +276,7 @@ export function BillFormComponent({
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker disabled={disabled} />
|
<DateTimePicker isDateOnly disabled={disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bills.fields.is_credit_memo")}
|
label={t("bills.fields.is_credit_memo")}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
|||||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
||||||
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
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 InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import InputPhone, { 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";
|
||||||
@@ -196,7 +196,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{dlExpiresBeforeReturn && (
|
{dlExpiresBeforeReturn && (
|
||||||
<Space style={{ color: "tomato" }}>
|
<Space style={{ color: "tomato" }}>
|
||||||
@@ -274,7 +274,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
|||||||
<InputPhone />
|
<InputPhone />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
|
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<ContractsRatesChangeButton form={form} />
|
<ContractsRatesChangeButton form={form} />
|
||||||
|
|||||||
@@ -10,16 +10,12 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
|||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput 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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function CourtesyCarCreateFormComponent({
|
export default function CourtesyCarCreateFormComponent({ form, saveLoading, newCC }) {
|
||||||
form,
|
|
||||||
saveLoading,
|
|
||||||
newCC,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
@@ -161,16 +157,16 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
|
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
|
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
|
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
|
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
@@ -228,7 +224,7 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
|
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
|
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -260,7 +256,7 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
|
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
|
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -293,7 +289,7 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
|
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
|
||||||
{() => {
|
{() => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Form, InputNumber } from "antd";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function CourtesyCarReturnModalComponent() {
|
export default function CourtesyCarReturnModalComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("contracts.fields.kmend")}
|
label={t("contracts.fields.kmend")}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import i18n from "../../translations/i18n";
|
|||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -164,7 +164,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
|||||||
<Input disabled />
|
<Input disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
|
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import Markdown from "react-markdown";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component";
|
|
||||||
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { acceptEula } from "../../redux/user/user.actions";
|
import { acceptEula } from "../../redux/user/user.actions";
|
||||||
@@ -12,6 +11,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import day from "../../utils/day";
|
import day from "../../utils/day";
|
||||||
|
|
||||||
import "./eula.styles.scss";
|
import "./eula.styles.scss";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||||
const [formReady, setFormReady] = useState(false);
|
const [formReady, setFormReady] = useState(false);
|
||||||
@@ -216,7 +216,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
<DateTimePicker isDateOnly onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import { DatePicker } from "antd";
|
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useRef } from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
|
|
||||||
|
|
||||||
const dateFormat = "MM/DD/YYYY";
|
|
||||||
|
|
||||||
export function FormDatePicker({
|
|
||||||
bodyshop,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onBlur,
|
|
||||||
onlyFuture,
|
|
||||||
onlyToday,
|
|
||||||
isDateOnly = true,
|
|
||||||
...restProps
|
|
||||||
}) {
|
|
||||||
const ref = useRef();
|
|
||||||
|
|
||||||
const handleChange = (newDate) => {
|
|
||||||
if (value !== newDate && onChange) {
|
|
||||||
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
|
||||||
if (e.key.toLowerCase() === "t") {
|
|
||||||
if (onChange) {
|
|
||||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
|
||||||
}
|
|
||||||
} else if (e.key.toLowerCase() === "enter") {
|
|
||||||
if (ref.current && ref.current.blur) ref.current.blur();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = (e) => {
|
|
||||||
const v = e.target.value;
|
|
||||||
if (!v) return;
|
|
||||||
|
|
||||||
const formats = [
|
|
||||||
"MMDDYY",
|
|
||||||
"MMDDYYYY",
|
|
||||||
"MM/DD/YY",
|
|
||||||
"MM/DD/YYYY",
|
|
||||||
"M/DD/YY",
|
|
||||||
"M/DD/YYYY",
|
|
||||||
"MM/D/YY",
|
|
||||||
"MM/D/YYYY",
|
|
||||||
"M/D/YY",
|
|
||||||
"M/D/YYYY",
|
|
||||||
"D/MM/YY",
|
|
||||||
"D/MM/YYYY",
|
|
||||||
"DD/M/YY",
|
|
||||||
"DD/M/YYYY",
|
|
||||||
"D/M/YY",
|
|
||||||
"D/M/YYYY"
|
|
||||||
];
|
|
||||||
|
|
||||||
let _a;
|
|
||||||
|
|
||||||
// Iterate through formats to find the correct one
|
|
||||||
for (let format of formats) {
|
|
||||||
_a = dayjs(v, format);
|
|
||||||
if (v === _a.format(format)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_a.isValid() && value && value.isValid && value.isValid()) {
|
|
||||||
_a.set({
|
|
||||||
hours: value.hours(),
|
|
||||||
minutes: value.minutes(),
|
|
||||||
seconds: value.seconds(),
|
|
||||||
milliseconds: value.milliseconds()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_a.isValid() && onChange) {
|
|
||||||
if (onlyFuture) {
|
|
||||||
if (dayjs().subtract(1, "day").isBefore(_a)) {
|
|
||||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
|
||||||
} else {
|
|
||||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div onKeyDown={handleKeyDown}>
|
|
||||||
<DatePicker
|
|
||||||
ref={ref}
|
|
||||||
value={value ? dayjs(value) : null}
|
|
||||||
onChange={handleChange}
|
|
||||||
format={dateFormat}
|
|
||||||
onBlur={onBlur || handleBlur}
|
|
||||||
showToday={false}
|
|
||||||
disabledTime
|
|
||||||
disabledDate={(d) => {
|
|
||||||
if (onlyToday) {
|
|
||||||
return !dayjs().isSame(d, "day");
|
|
||||||
} else if (onlyFuture) {
|
|
||||||
return dayjs().subtract(1, "day").isAfter(d);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { DatePicker } from "antd";
|
|
||||||
import dayjs from "../../utils/day.js";
|
|
||||||
import React, { useRef } from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced);
|
|
||||||
|
|
||||||
const dateFormat = "MM/DD/YYYY h:mm a";
|
|
||||||
|
|
||||||
export function FormDateTimePickerEnhanced({
|
|
||||||
bodyshop,
|
|
||||||
value,
|
|
||||||
onBlur,
|
|
||||||
onlyFuture,
|
|
||||||
onlyToday,
|
|
||||||
isDateOnly = true,
|
|
||||||
...restProps
|
|
||||||
}) {
|
|
||||||
const ref = useRef();
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<DatePicker
|
|
||||||
ref={ref}
|
|
||||||
value={value ? dayjs(value) : null}
|
|
||||||
format={dateFormat}
|
|
||||||
onBlur={onBlur}
|
|
||||||
showToday={false}
|
|
||||||
disabledDate={(d) => {
|
|
||||||
if (onlyToday) {
|
|
||||||
return !dayjs().isSame(d, "day");
|
|
||||||
} else if (onlyFuture) {
|
|
||||||
return dayjs().subtract(1, "day").isAfter(d);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,105 @@
|
|||||||
import React, { forwardRef } from "react";
|
import { DatePicker } from "antd";
|
||||||
//import DatePicker from "react-datepicker";
|
import PropTypes from "prop-types";
|
||||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
import React, { useCallback, useState } from "react";
|
||||||
import { Space, TimePicker } from "antd";
|
import { useTranslation } from "react-i18next";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import { fuzzyMatchDate } from "./formats.js";
|
||||||
//To be used as a form element only.
|
|
||||||
|
|
||||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => {
|
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||||
// const handleChange = (newDate) => {
|
const [isManualInput, setIsManualInput] = useState(false);
|
||||||
// if (value !== newDate && onChange) {
|
const { t } = useTranslation();
|
||||||
// onChange(newDate);
|
|
||||||
// }
|
const handleChange = useCallback(
|
||||||
// };
|
(newDate) => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(newDate || null);
|
||||||
|
}
|
||||||
|
setIsManualInput(false);
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBlur = useCallback(
|
||||||
|
(e) => {
|
||||||
|
// Bail if this is not a manual input
|
||||||
|
if (!isManualInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reset manual input flag
|
||||||
|
setIsManualInput(false);
|
||||||
|
|
||||||
|
const v = e?.target?.value;
|
||||||
|
|
||||||
|
if (!v) return;
|
||||||
|
|
||||||
|
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
|
||||||
|
|
||||||
|
if (parsedDate && onChange) {
|
||||||
|
onChange(parsedDate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isManualInput, isDateOnly, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e) => {
|
||||||
|
setIsManualInput(true);
|
||||||
|
|
||||||
|
if (e.key.toLowerCase() === "t" && onChange) {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsManualInput(false);
|
||||||
|
onChange(dayjs());
|
||||||
|
} else if (e.key.toLowerCase() === "enter") {
|
||||||
|
handleBlur(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChange, handleBlur]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisabledDate = useCallback(
|
||||||
|
(current) => {
|
||||||
|
if (onlyToday) {
|
||||||
|
return !dayjs().isSame(current, "day");
|
||||||
|
} else if (onlyFuture) {
|
||||||
|
return dayjs().subtract(1, "day").isAfter(current);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[onlyToday, onlyFuture]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ width: "100%" }} id={id}>
|
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
|
||||||
<FormDatePicker
|
<DatePicker
|
||||||
{...restProps}
|
showTime={
|
||||||
{...(onlyFuture && {
|
isDateOnly
|
||||||
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d)
|
? false
|
||||||
})}
|
: {
|
||||||
value={value}
|
format: "hh:mm a",
|
||||||
onBlur={onBlur}
|
minuteStep: 15,
|
||||||
onChange={onChange}
|
defaultValue: dayjs(dayjs(), "HH:mm:ss")
|
||||||
onlyFuture={onlyFuture}
|
}
|
||||||
isDateOnly={false}
|
}
|
||||||
/>
|
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
|
||||||
|
|
||||||
<TimePicker
|
|
||||||
value={value ? dayjs(value) : null}
|
value={value ? dayjs(value) : null}
|
||||||
{...(onlyFuture && {
|
onChange={handleChange}
|
||||||
disabledDate: (d) => dayjs().isAfter(d)
|
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||||
})}
|
onBlur={onBlur || handleBlur}
|
||||||
onChange={onChange}
|
disabledDate={handleDisabledDate}
|
||||||
disableSeconds={true}
|
|
||||||
minuteStep={15}
|
|
||||||
onBlur={onBlur}
|
|
||||||
format="hh:mm a"
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default forwardRef(DateTimePicker);
|
DateTimePicker.propTypes = {
|
||||||
|
value: PropTypes.any,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onBlur: PropTypes.func,
|
||||||
|
id: PropTypes.string,
|
||||||
|
onlyFuture: PropTypes.bool,
|
||||||
|
onlyToday: PropTypes.bool,
|
||||||
|
isDateOnly: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(DateTimePicker);
|
||||||
|
|||||||
63
client/src/components/form-date-time-picker/formats.js
Normal file
63
client/src/components/form-date-time-picker/formats.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import dayjs from "../../utils/day";
|
||||||
|
|
||||||
|
const dateFormats = [
|
||||||
|
"MMDDYYYY",
|
||||||
|
"MMDDYY",
|
||||||
|
"M/D/YYYY",
|
||||||
|
"MM/D/YYYY",
|
||||||
|
"M/DD/YYYY",
|
||||||
|
"MM/DD/YYYY",
|
||||||
|
"M/D/YY",
|
||||||
|
"MM/D/YY",
|
||||||
|
"M/DD/YY",
|
||||||
|
"MM/DD/YY"
|
||||||
|
];
|
||||||
|
|
||||||
|
const timeFormats = ["h:mm A", "h:mmA", "h A", "hA", "hh:mm A", "hh:mm:ss A"];
|
||||||
|
|
||||||
|
const dateTimeFormats = [
|
||||||
|
...["M/D/YYYY", "MM/D/YYYY", "M/DD/YYYY", "MM/DD/YYYY", "M/D/YY", "MM/D/YY", "M/DD/YY", "MM/DD/YY"].flatMap(
|
||||||
|
(dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)
|
||||||
|
),
|
||||||
|
|
||||||
|
...["MMDDYYYY", "MMDDYY"].flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)),
|
||||||
|
|
||||||
|
"M/D/YYYY",
|
||||||
|
"MM/D/YYYY",
|
||||||
|
"M/DD/YYYY",
|
||||||
|
"MM/DD/YYYY",
|
||||||
|
"M/D/YY",
|
||||||
|
"MM/D/YY",
|
||||||
|
"M/DD/YY",
|
||||||
|
"MM/DD/YY",
|
||||||
|
"MMDDYYYY",
|
||||||
|
"MMDDYY"
|
||||||
|
];
|
||||||
|
|
||||||
|
const sanitizeInput = (input) =>
|
||||||
|
input
|
||||||
|
.trim()
|
||||||
|
.toUpperCase()
|
||||||
|
.replace(/\s*(am|pm)\s*/i, " $1")
|
||||||
|
.replaceAll(".", "/")
|
||||||
|
.replaceAll("-", "/");
|
||||||
|
|
||||||
|
export const fuzzyMatchDate = (dateString) => {
|
||||||
|
const sanitizedInput = sanitizeInput(dateString);
|
||||||
|
|
||||||
|
for (const format of dateFormats) {
|
||||||
|
const parsedDate = dayjs(sanitizedInput, format, true);
|
||||||
|
if (parsedDate.isValid()) {
|
||||||
|
return parsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const format of dateTimeFormats) {
|
||||||
|
const parsedDateTime = dayjs(sanitizedInput, format, true);
|
||||||
|
if (parsedDateTime.isValid()) {
|
||||||
|
return parsedDateTime; // Return the dayjs object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // If no matching format is found
|
||||||
|
};
|
||||||
@@ -10,8 +10,8 @@ import {
|
|||||||
QUERY_SCOREBOARD_ENTRY,
|
QUERY_SCOREBOARD_ENTRY,
|
||||||
UPDATE_SCOREBOARD_ENTRY
|
UPDATE_SCOREBOARD_ENTRY
|
||||||
} from "../../graphql/scoreboard.queries";
|
} from "../../graphql/scoreboard.queries";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
|
export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -86,7 +86,7 @@ export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps })
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("scoreboard.fields.bodyhrs")}
|
label={t("scoreboard.fields.bodyhrs")}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
@@ -20,7 +19,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
|
||||||
@@ -87,7 +93,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
||||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||||
<FormDatePicker format="MM/DD/YYYY" />
|
<DateTimePicker format="MM/DD/YYYY" isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
|
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
|
||||||
<DateTimePicker />
|
<DateTimePicker />
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
import {
|
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
Collapse,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
} 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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
@@ -29,6 +20,7 @@ import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.c
|
|||||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -61,10 +53,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
|
||||||
label={t("jobs.fields.regie_number")}
|
|
||||||
name="regie_number"
|
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
@@ -116,7 +105,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<FormItemEmail email={getFieldValue("ins_ea")} />
|
<FormItemEmail email={getFieldValue("ins_ea")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Form, Input } from "antd";
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
||||||
const [state] = useContext(JobCreateContext);
|
const [state] = useContext(JobCreateContext);
|
||||||
@@ -113,7 +113,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
|||||||
<Input disabled={!state.vehicle.new} />
|
<Input disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}>
|
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}>
|
||||||
<FormDatePicker disabled={!state.vehicle.new} />
|
<DateTimePicker isDateOnly disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<div>
|
<div>
|
||||||
<FormRow header={t("jobs.forms.estdates")}>
|
<FormRow header={t("jobs.forms.estdates")}>
|
||||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||||
<FormDatePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
||||||
<DateTimePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} />
|
||||||
@@ -45,7 +44,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
|
|
||||||
<FormRow header={t("jobs.forms.scheddates")}>
|
<FormRow header={t("jobs.forms.scheddates")}>
|
||||||
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
||||||
<FormDatePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Tooltip title={t("jobs.labels.scheduledinchange")}>
|
<Tooltip title={t("jobs.labels.scheduledinchange")}>
|
||||||
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
|
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
|
||||||
@@ -85,7 +84,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: jobInPostProduction
|
required: jobInPostProduction
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
@@ -13,6 +12,7 @@ import Car from "../job-damage-visual/job-damage-visual.component";
|
|||||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||||
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -152,7 +152,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||||
<FormDatePicker disabled={jobRO} />
|
<DateTimePicker isDateOnly disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function PartnerPingComponent({ bodyshop }) {
|
|||||||
// Execute the created function directly
|
// Execute the created function directly
|
||||||
checkPartnerStatus(bodyshop);
|
checkPartnerStatus(bodyshop);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [bodyshop]);
|
}, [bodyshop?.id]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
|
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import { CalendarFilled } from "@ant-design/icons";
|
import { CalendarFilled } from "@ant-design/icons";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -62,7 +62,7 @@ export function PartsOrderBackorderEta({
|
|||||||
<div>
|
<div>
|
||||||
<Form form={form} onFinish={handleFinish}>
|
<Form form={form} onFinish={handleFinish}>
|
||||||
<Form.Item name="eta">
|
<Form.Item name="eta">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" onClick={() => form.submit()}>
|
<Button type="primary" onClick={() => form.submit()}>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
|
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -71,7 +71,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
|
|||||||
<div>
|
<div>
|
||||||
<Form form={form} onFinish={handleFinish}>
|
<Form form={form} onFinish={handleFinish}>
|
||||||
<Form.Item name="eta">
|
<Form.Item name="eta">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" onClick={() => form.submit()}>
|
<Button type="primary" onClick={() => form.submit()}>
|
||||||
{t("parts_orders.actions.backordered")}
|
{t("parts_orders.actions.backordered")}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { DeleteFilled, EyeFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd";
|
import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd";
|
||||||
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -83,47 +82,34 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
sortedInfo: {}
|
sortedInfo: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [returnfrombill, setReturnFromBill] = useState();
|
const [billData, setBillData] = useState(null);
|
||||||
const [billData, setBillData] = useState();
|
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const selectedpartsorder = search.partsorderid;
|
const selectedpartsorder = search.partsorderid;
|
||||||
|
|
||||||
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
|
||||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||||
const { refetch } = billsQuery;
|
const { refetch } = billsQuery;
|
||||||
|
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
||||||
|
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (returnfrombill === null) {
|
const fetchData = async () => {
|
||||||
setBillData(null);
|
if (selectedPartsOrderRecord?.returnfrombill) {
|
||||||
} else {
|
try {
|
||||||
const fetchData = async () => {
|
const { data } = await billQuery({
|
||||||
const result = await billQuery({
|
variables: { billid: selectedPartsOrderRecord.returnfrombill }
|
||||||
variables: { billid: returnfrombill }
|
});
|
||||||
});
|
setBillData(data);
|
||||||
setBillData(result.data);
|
} catch (error) {
|
||||||
};
|
console.error("Error fetching bill data:", error);
|
||||||
fetchData();
|
}
|
||||||
}
|
} else setBillData(null);
|
||||||
}, [returnfrombill, billQuery]);
|
};
|
||||||
|
fetchData();
|
||||||
|
}, [selectedPartsOrderRecord, billQuery]);
|
||||||
|
|
||||||
const recordActions = (record, showView = false) => (
|
const recordActions = (record) => (
|
||||||
<Space direction="horizontal" wrap>
|
<Space direction="horizontal" wrap>
|
||||||
{showView && (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (record.returnfrombill) {
|
|
||||||
setReturnFromBill(record.returnfrombill);
|
|
||||||
} else {
|
|
||||||
setReturnFromBill(null);
|
|
||||||
}
|
|
||||||
handleOnRowClick(record);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EyeFilled />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -133,16 +119,14 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
context: {
|
context: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
job: job,
|
job: job,
|
||||||
partsorderlines: record.parts_order_lines.map((pol) => {
|
partsorderlines: record.parts_order_lines.map((pol) => ({
|
||||||
return {
|
joblineid: pol.job_line_id,
|
||||||
joblineid: pol.job_line_id,
|
id: pol.id,
|
||||||
id: pol.id,
|
line_desc: pol.line_desc,
|
||||||
line_desc: pol.line_desc,
|
quantity: pol.quantity,
|
||||||
quantity: pol.quantity,
|
act_price: pol.act_price,
|
||||||
act_price: pol.act_price,
|
oem_partno: pol.oem_partno
|
||||||
oem_partno: pol.oem_partno
|
}))
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -167,7 +151,6 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
//Delete the parts return.!
|
//Delete the parts return.!
|
||||||
|
|
||||||
await deletePartsOrder({
|
await deletePartsOrder({
|
||||||
variables: { partsOrderId: record.id },
|
variables: { partsOrderId: record.id },
|
||||||
update(cache) {
|
update(cache) {
|
||||||
@@ -191,7 +174,6 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid}
|
disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
logImEXEvent("parts_order_receive_bill");
|
logImEXEvent("parts_order_receive_bill");
|
||||||
|
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
@@ -199,24 +181,20 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
bill: {
|
bill: {
|
||||||
vendorid: record.vendor.id,
|
vendorid: record.vendor.id,
|
||||||
is_credit_memo: record.return,
|
is_credit_memo: record.return,
|
||||||
billlines: record.parts_order_lines.map((pol) => {
|
billlines: record.parts_order_lines.map((pol) => ({
|
||||||
return {
|
joblineid: pol.job_line_id || "noline",
|
||||||
joblineid: pol.job_line_id || "noline",
|
line_desc: pol.line_desc,
|
||||||
line_desc: pol.line_desc,
|
quantity: pol.quantity,
|
||||||
quantity: pol.quantity,
|
actual_price: pol.act_price,
|
||||||
|
cost_center: pol.jobline?.part_type
|
||||||
actual_price: pol.act_price,
|
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||||
|
? pol.jobline.part_type !== "PAE"
|
||||||
cost_center: pol.jobline?.part_type
|
? pol.jobline.part_type
|
||||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
: null
|
||||||
? pol.jobline.part_type !== "PAE"
|
: responsibilityCenters.defaults &&
|
||||||
? pol.jobline.part_type
|
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||||
: null
|
: null
|
||||||
: responsibilityCenters.defaults &&
|
}))
|
||||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
|
||||||
: null
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -243,8 +221,6 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
|
||||||
|
|
||||||
const rowExpander = (record) => {
|
const rowExpander = (record) => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ 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 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";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -74,7 +74,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
|||||||
]}
|
]}
|
||||||
label={t("parts_orders.fields.deliver_by")}
|
label={t("parts_orders.fields.deliver_by")}
|
||||||
>
|
>
|
||||||
<FormDatePicker onlyFuture />
|
<DateTimePicker isDateOnly onlyFuture />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{job && job.special_coverage_policy && (
|
{job && job.special_coverage_policy && (
|
||||||
<Tag color="tomato">
|
<Tag color="tomato">
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ 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 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";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
|
import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -77,7 +77,7 @@ export function PaymentFormComponent({ form, bodyshop, disabled }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<DatePickerFormItem disabled={disabled} />
|
<DateTimePicker isDateOnly disabled={disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ
|
|||||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||||
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||||
|
|
||||||
|
const getEmployeeName = (employeeId, employees) => {
|
||||||
|
const employee = employees.find((e) => e.id === employeeId);
|
||||||
|
return employee ? `${employee.first_name} ${employee.last_name}` : "";
|
||||||
|
};
|
||||||
|
|
||||||
const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
|
const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
|
||||||
const { Enhanced_Payroll } = treatments;
|
const { Enhanced_Payroll } = treatments;
|
||||||
return [
|
return [
|
||||||
@@ -426,8 +431,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
|
getEmployeeName(a.employee_body, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
|
getEmployeeName(b.employee_body, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
|
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
|
||||||
@@ -440,8 +445,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
|
getEmployeeName(a.employee_prep, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
|
getEmployeeName(b.employee_prep, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
|
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
|
||||||
@@ -460,8 +465,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
|
getEmployeeName(a.employee_csr, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
|
getEmployeeName(b.employee_csr, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
|
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
|
||||||
@@ -474,8 +479,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
|
getEmployeeName(a.employee_refinish, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
|
getEmployeeName(b.employee_refinish, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />
|
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Dropdown, Space, TimePicker } from "antd";
|
import { Button, Card, Dropdown, Space } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ProductionListDate({ record, field, time, pastIndicator }) {
|
export default function ProductionListDate({ record, field, time, pastIndicator }) {
|
||||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||||
@@ -57,22 +57,14 @@ export default function ProductionListDate({ record, field, time, pastIndicator
|
|||||||
label: (
|
label: (
|
||||||
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
|
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
|
||||||
<Space direction={"vertical"}>
|
<Space direction={"vertical"}>
|
||||||
<FormDatePicker
|
<DateTimePicker
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
value={(record[field] && dayjs(record[field])) || null}
|
value={(record[field] && dayjs(record[field])) || null}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
format="MM/DD/YYYY"
|
format={time ? "MM/DD/YYYY hh:mm a" : "MM/DD/YYYY"}
|
||||||
isDateOnly={!time}
|
isDateOnly={!time}
|
||||||
|
showTime={time ? { format: "hh:mm a", minuteStep: 15 } : false}
|
||||||
/>
|
/>
|
||||||
{time && (
|
|
||||||
<TimePicker
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
value={(record[field] && dayjs(record[field])) || null}
|
|
||||||
onChange={handleChange}
|
|
||||||
minuteStep={15}
|
|
||||||
format="hh:mm a"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { Button, Form, Input, notification, Popover, Space } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import { isFunction } from "lodash";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ProductionListSaveConfigButton({ columns, bodyshop, tableState, onSave }) {
|
|
||||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const handleSaveConfig = async (values) => {
|
|
||||||
logImEXEvent("production_save_config");
|
|
||||||
setLoading(true);
|
|
||||||
const result = await updateShop({
|
|
||||||
variables: {
|
|
||||||
id: bodyshop.id,
|
|
||||||
shop: {
|
|
||||||
production_config: [
|
|
||||||
...bodyshop.production_config.filter((b) => b.name !== values.name),
|
|
||||||
//Assign it to the name
|
|
||||||
{
|
|
||||||
name: values.name,
|
|
||||||
columns: {
|
|
||||||
columnKeys: columns.map((i) => {
|
|
||||||
return { key: i.key, width: i.width };
|
|
||||||
}),
|
|
||||||
tableState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!!!result.errors) {
|
|
||||||
notification["success"]({ message: t("bodyshop.successes.save") });
|
|
||||||
if (onSave && isFunction(onSave)) {
|
|
||||||
onSave();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bodyshop.errors.saving", {
|
|
||||||
error: JSON.stringify(result.errors)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
form.resetFields();
|
|
||||||
setOpen(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
const popMenu = (
|
|
||||||
<div>
|
|
||||||
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
|
||||||
<Form.Item label={t("production.labels.viewname")} name="name" rules={[{ required: true }]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Space wrap>
|
|
||||||
<Button type="primary" danger onClick={() => form.submit()} loading={loading}>
|
|
||||||
{t("general.actions.save")}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
|
||||||
</Space>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} content={popMenu}>
|
|
||||||
<Button loading={loading} onClick={() => setOpen(true)}>
|
|
||||||
{t("production.actions.saveconfig")}
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListSaveConfigButton);
|
|
||||||
@@ -0,0 +1,505 @@
|
|||||||
|
import { DeleteOutlined, ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Button, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries";
|
||||||
|
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||||
|
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import { isFunction } from "lodash";
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
|
export function ProductionListConfigManager({
|
||||||
|
refetch,
|
||||||
|
bodyshop,
|
||||||
|
technician,
|
||||||
|
currentUser,
|
||||||
|
state,
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
setColumns,
|
||||||
|
setState,
|
||||||
|
onSave,
|
||||||
|
hasUnsavedChanges,
|
||||||
|
setHasUnsavedChanges
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
||||||
|
const [updateShop] = useMutation(UPDATE_SHOP);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [isAddingNewProfile, setIsAddingNewProfile] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const [activeView, setActiveView] = useState(() => {
|
||||||
|
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||||
|
return assoc && assoc.default_prod_list_view;
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
sortedInfo: {
|
||||||
|
columnKey: "ro_number",
|
||||||
|
order: null
|
||||||
|
},
|
||||||
|
filteredInfo: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureDefaultState = (state) => {
|
||||||
|
return {
|
||||||
|
sortedInfo: state?.sortedInfo || defaultState.sortedInfo,
|
||||||
|
filteredInfo: state?.filteredInfo || defaultState.filteredInfo,
|
||||||
|
...state
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDefaultView = async () => {
|
||||||
|
const defaultConfig = {
|
||||||
|
name: t("production.constants.main_profile"),
|
||||||
|
columns: {
|
||||||
|
columnKeys: [
|
||||||
|
{ key: "ro_number", width: 100 },
|
||||||
|
{ key: "ownr", width: 100 },
|
||||||
|
{ key: "vehicle", width: 100 },
|
||||||
|
{ key: "ins_co_nm", width: 100 },
|
||||||
|
{ key: "actual_in", width: 100 },
|
||||||
|
{ key: "scheduled_completion", width: 100 },
|
||||||
|
{ key: "labhrs", width: 100 },
|
||||||
|
{ key: "employee_body", width: 100 },
|
||||||
|
{ key: "larhrs", width: 100 },
|
||||||
|
{ key: "employee_refinish", width: 100 },
|
||||||
|
{ key: "tt", width: 100 },
|
||||||
|
{ key: "status", width: 100 },
|
||||||
|
{ key: "sublets", width: 100 },
|
||||||
|
{ key: "viewdetail", width: 100 }
|
||||||
|
],
|
||||||
|
tableState: ensureDefaultState(state)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await updateShop({
|
||||||
|
variables: {
|
||||||
|
id: bodyshop.id,
|
||||||
|
shop: {
|
||||||
|
production_config: [defaultConfig]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
await updateActiveProdView(t("production.constants.main_profile"));
|
||||||
|
window.location.reload(); // Reload the page
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: t("bodyshop.errors.creatingdefaultview", {
|
||||||
|
error: JSON.stringify(result.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
} = useSplitTreatments({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Enhanced_Payroll"],
|
||||||
|
splitKey: bodyshop.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateActiveProdView = async (viewName) => {
|
||||||
|
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||||
|
if (assoc) {
|
||||||
|
await updateDefaultProdView({
|
||||||
|
variables: { assocId: assoc.id, view: viewName },
|
||||||
|
update(cache) {
|
||||||
|
cache.modify({
|
||||||
|
id: cache.identify(bodyshop),
|
||||||
|
fields: {
|
||||||
|
associations(existingAssociations) {
|
||||||
|
return existingAssociations.map((a) => {
|
||||||
|
if (a.useremail !== currentUser.email) return a;
|
||||||
|
return { ...a, default_prod_list_view: viewName };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setActiveView(viewName);
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = async (value) => {
|
||||||
|
if (hasUnsavedChanges) {
|
||||||
|
confirm({
|
||||||
|
title: t("general.labels.unsavedchanges"),
|
||||||
|
icon: <ExclamationCircleOutlined />,
|
||||||
|
content: t("general.messages.unsavedchangespopup"),
|
||||||
|
onOk: () => proceedWithSelect(value),
|
||||||
|
onCancel() {
|
||||||
|
// Do nothing if canceled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await proceedWithSelect(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const proceedWithSelect = async (value) => {
|
||||||
|
if (value === "add_new") {
|
||||||
|
setIsAddingNewProfile(true);
|
||||||
|
setOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedConfig = bodyshop.production_config.find((pc) => pc.name === value);
|
||||||
|
|
||||||
|
// If the selected profile doesn't exist, revert to the main profile
|
||||||
|
if (!selectedConfig) {
|
||||||
|
const mainProfileConfig = bodyshop.production_config.find(
|
||||||
|
(pc) => pc.name === t("production.constants.main_profile")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mainProfileConfig) {
|
||||||
|
await updateActiveProdView(t("production.constants.main_profile"));
|
||||||
|
setColumns(
|
||||||
|
mainProfileConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const newState = ensureDefaultState(mainProfileConfig.columns.tableState);
|
||||||
|
setState(newState);
|
||||||
|
|
||||||
|
if (onSave && isFunction(onSave)) {
|
||||||
|
onSave();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the selected profile exists, proceed as normal
|
||||||
|
if (selectedConfig) {
|
||||||
|
const newColumns = selectedConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setColumns(newColumns);
|
||||||
|
const newState = ensureDefaultState(selectedConfig.columns.tableState);
|
||||||
|
setState(newState);
|
||||||
|
|
||||||
|
await updateActiveProdView(value);
|
||||||
|
if (onSave && isFunction(onSave)) {
|
||||||
|
onSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTrash = async (name) => {
|
||||||
|
if (name === t("production.constants.main_profile")) return;
|
||||||
|
|
||||||
|
const remainingConfigs = bodyshop.production_config.filter((b) => b.name !== name);
|
||||||
|
|
||||||
|
await updateShop({
|
||||||
|
variables: {
|
||||||
|
id: bodyshop.id,
|
||||||
|
shop: {
|
||||||
|
production_config: remainingConfigs
|
||||||
|
}
|
||||||
|
},
|
||||||
|
awaitRefetchQueries: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (name === activeView) {
|
||||||
|
// Only switch profiles if the deleted profile was the active profile
|
||||||
|
if (remainingConfigs.length > 0) {
|
||||||
|
const nextConfig = remainingConfigs[0];
|
||||||
|
await updateActiveProdView(nextConfig.name);
|
||||||
|
setColumns(
|
||||||
|
nextConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
refetch,
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setState(ensureDefaultState(nextConfig.columns.tableState));
|
||||||
|
} else {
|
||||||
|
await updateActiveProdView(null);
|
||||||
|
setColumns([]);
|
||||||
|
setState(defaultState);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Revert back to the active view and load its columns and state
|
||||||
|
const activeConfig = bodyshop.production_config.find((pc) => pc.name === activeView);
|
||||||
|
if (activeConfig) {
|
||||||
|
await updateActiveProdView(activeView);
|
||||||
|
setColumns(
|
||||||
|
activeConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
refetch,
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setState(ensureDefaultState(activeConfig.columns.tableState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveConfig = async (values) => {
|
||||||
|
logImEXEvent("production_save_config");
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const profileName = isAddingNewProfile ? values.name : activeView;
|
||||||
|
|
||||||
|
const result = await updateShop({
|
||||||
|
variables: {
|
||||||
|
id: bodyshop.id,
|
||||||
|
shop: {
|
||||||
|
production_config: [
|
||||||
|
...bodyshop.production_config.filter((b) => b.name !== profileName),
|
||||||
|
{
|
||||||
|
name: profileName,
|
||||||
|
columns: {
|
||||||
|
columnKeys: columns.map((i) => ({ key: i.key, width: i.width })),
|
||||||
|
tableState: ensureDefaultState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
notification.success({ message: t("bodyshop.successes.save") });
|
||||||
|
if (isAddingNewProfile) {
|
||||||
|
await updateActiveProdView(profileName);
|
||||||
|
}
|
||||||
|
if (onSave && isFunction(onSave)) {
|
||||||
|
onSave();
|
||||||
|
}
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: t("bodyshop.errors.saving", {
|
||||||
|
error: JSON.stringify(result.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.resetFields();
|
||||||
|
setOpen(false);
|
||||||
|
setLoading(false);
|
||||||
|
setIsAddingNewProfile(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const validateAndSetDefaultView = () => {
|
||||||
|
const configExists = bodyshop.production_config.some((pc) => pc.name === activeView);
|
||||||
|
|
||||||
|
if (!configExists) {
|
||||||
|
// If the default view doesn't exist, revert to the main profile
|
||||||
|
const mainProfileConfig = bodyshop.production_config.find(
|
||||||
|
(pc) => pc.name === t("production.constants.main_profile")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mainProfileConfig) {
|
||||||
|
setActiveView(t("production.constants.main_profile"));
|
||||||
|
|
||||||
|
setColumns(
|
||||||
|
mainProfileConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setState(ensureDefaultState(mainProfileConfig.columns.tableState));
|
||||||
|
|
||||||
|
updateActiveProdView(t("production.constants.main_profile"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the default view exists, set it as active
|
||||||
|
setActiveView(activeView);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!bodyshop.production_config || bodyshop.production_config.length === 0) {
|
||||||
|
createDefaultView().catch((e) => {
|
||||||
|
console.error("Something went wrong saving the production list view Config.");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
validateAndSetDefaultView();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [activeView, bodyshop.production_config]);
|
||||||
|
|
||||||
|
const popMenu = (
|
||||||
|
<div>
|
||||||
|
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
||||||
|
{isAddingNewProfile && (
|
||||||
|
<Form.Item
|
||||||
|
label={t("production.labels.viewname")}
|
||||||
|
name="name"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t("production.errors.name_required") },
|
||||||
|
{
|
||||||
|
validator: (_, value) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const nameExists = bodyshop.production_config.some((pc) => pc.name === value);
|
||||||
|
if (nameExists) {
|
||||||
|
return Promise.reject(new Error(t("production.errors.name_exists")));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Space wrap>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={() => form.submit()}
|
||||||
|
loading={loading}
|
||||||
|
disabled={form.getFieldsError().some(({ errors }) => errors.length)}
|
||||||
|
>
|
||||||
|
{t("general.actions.save")}
|
||||||
|
</Button>
|
||||||
|
{!isAddingNewProfile && (
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
onClick={() => {
|
||||||
|
setIsAddingNewProfile(true);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.saveas")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsAddingNewProfile(false);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Button loading={loading} onClick={() => setOpen(true)} disabled={isAddingNewProfile || !hasUnsavedChanges}>
|
||||||
|
{t("production.actions.saveconfig")}
|
||||||
|
</Button>
|
||||||
|
<Popover open={open} content={popMenu} placement="bottom">
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
minWidth: "150px"
|
||||||
|
}}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
placeholder={t("production.labels.selectview")}
|
||||||
|
optionLabelProp="label"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
value={activeView}
|
||||||
|
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
||||||
|
>
|
||||||
|
{bodyshop.production_config
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) =>
|
||||||
|
a.name === t("production.constants.main_profile")
|
||||||
|
? -1
|
||||||
|
: b.name === t("production.constants.main_profile")
|
||||||
|
? 1
|
||||||
|
: 0
|
||||||
|
) //
|
||||||
|
.map((config) => (
|
||||||
|
<Select.Option key={config.name} label={config.name}>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: "80%",
|
||||||
|
marginRight: "1rem",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{config.name}
|
||||||
|
</span>
|
||||||
|
{config.name !== t("production.constants.main_profile") && (
|
||||||
|
<Popconfirm
|
||||||
|
placement="right"
|
||||||
|
title={t("general.labels.areyousure")}
|
||||||
|
onConfirm={() => handleTrash(config.name)}
|
||||||
|
onCancel={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<DeleteOutlined onClick={(e) => e.stopPropagation()} />
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
<Select.Option key="add_new" label={t("production.labels.addnewprofile")}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<PlusOutlined style={{ marginRight: "0.5rem" }} />
|
||||||
|
{t("production.labels.addnewprofile")}
|
||||||
|
</div>
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Popover>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
import { DeleteOutlined } from "@ant-design/icons";
|
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Popconfirm, Select } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries";
|
|
||||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
|
||||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { isFunction } from "lodash";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
technician: selectTechnician,
|
|
||||||
currentUser: selectCurrentUser
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ProductionListTable({
|
|
||||||
refetch,
|
|
||||||
bodyshop,
|
|
||||||
technician,
|
|
||||||
currentUser,
|
|
||||||
state,
|
|
||||||
data,
|
|
||||||
setColumns,
|
|
||||||
setState,
|
|
||||||
onProfileChange
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
|
||||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
|
||||||
|
|
||||||
const {
|
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
} = useSplitTreatments({
|
|
||||||
attributes: {},
|
|
||||||
names: ["Enhanced_Payroll"],
|
|
||||||
splitKey: bodyshop.imexshopid
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSelect = async (value, option) => {
|
|
||||||
const newColumns = bodyshop.production_config
|
|
||||||
.filter((pc) => pc.name === value)[0]
|
|
||||||
.columns.columnKeys.map((k) => {
|
|
||||||
return {
|
|
||||||
...ProductionListColumns({
|
|
||||||
bodyshop,
|
|
||||||
refetch,
|
|
||||||
technician,
|
|
||||||
state,
|
|
||||||
data: data,
|
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
}).find((e) => e.key === k.key),
|
|
||||||
width: k.width
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setColumns(newColumns);
|
|
||||||
const newState = bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState;
|
|
||||||
setState(newState);
|
|
||||||
|
|
||||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
|
||||||
|
|
||||||
if (assoc) {
|
|
||||||
await updateDefaultProdView({
|
|
||||||
variables: { assocId: assoc.id, view: value },
|
|
||||||
update(cache) {
|
|
||||||
cache.modify({
|
|
||||||
id: cache.identify(bodyshop),
|
|
||||||
fields: {
|
|
||||||
associations(existingAssociations, { readField }) {
|
|
||||||
return existingAssociations.map((a) => {
|
|
||||||
if (a.useremail !== currentUser.email) return a;
|
|
||||||
return { ...a, default_prod_list_view: value };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onProfileChange && isFunction(onProfileChange)) {
|
|
||||||
onProfileChange({ value, option, newColumns, newState, assoc });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTrash = async (name) => {
|
|
||||||
await updateShop({
|
|
||||||
variables: {
|
|
||||||
id: bodyshop.id,
|
|
||||||
shop: {
|
|
||||||
production_config: bodyshop.production_config.filter((b) => b.name !== name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
awaitRefetchQueries: true
|
|
||||||
});
|
|
||||||
|
|
||||||
setColumns(
|
|
||||||
bodyshop.production_config[0].columns.columnKeys.map((k) => {
|
|
||||||
return {
|
|
||||||
...ProductionListColumns({
|
|
||||||
technician,
|
|
||||||
state,
|
|
||||||
refetch,
|
|
||||||
data: data,
|
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
}).find((e) => e.key === k.key),
|
|
||||||
width: k.width
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(bodyshop.production_config[0].columns.tableState);
|
|
||||||
};
|
|
||||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
|
||||||
|
|
||||||
const defaultView = assoc && assoc.default_prod_list_view;
|
|
||||||
return (
|
|
||||||
<div style={{ width: "10rem" }}>
|
|
||||||
<Select
|
|
||||||
onSelect={handleSelect}
|
|
||||||
placeholder={t("production.labels.selectview")}
|
|
||||||
optionLabelProp="label"
|
|
||||||
popupMatchSelectWidth={false}
|
|
||||||
defaultValue={defaultView}
|
|
||||||
>
|
|
||||||
{bodyshop.production_config.map((config) => (
|
|
||||||
<Select.Option key={config.name} label={config.name}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
maxWidth: "80%",
|
|
||||||
marginRight: "1rem",
|
|
||||||
textOverflow: "ellipsis"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{config.name}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<Popconfirm
|
|
||||||
placement="right"
|
|
||||||
title={t("general.labels.areyousure")}
|
|
||||||
onConfirm={() => handleTrash(config.name)}
|
|
||||||
>
|
|
||||||
<DeleteOutlined
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(ProductionListTable);
|
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||||
|
import _ from "lodash";
|
||||||
|
import React, { useEffect, useMemo, useRef, 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 { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
import Prompt from "../../utils/prompt.js";
|
||||||
|
import AlertComponent from "../alert/alert.component.jsx";
|
||||||
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 { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||||
import ProductionListPrint from "./production-list-print.component";
|
import ProductionListPrint from "./production-list-print.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";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { SyncOutlined } from "@ant-design/icons";
|
|
||||||
import Prompt from "../../utils/prompt.js";
|
|
||||||
import _ from "lodash";
|
|
||||||
import AlertComponent from "../alert/alert.component.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -44,7 +43,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
const initialStateRef = useRef(
|
const initialStateRef = useRef(
|
||||||
(bodyshop.production_config &&
|
(bodyshop.production_config &&
|
||||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||||
bodyshop.production_config[0]?.columns.tableState || {
|
(bodyshop.production_config && bodyshop.production_config[0]?.columns.tableState) || {
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" }
|
filteredInfo: { text: "" }
|
||||||
}
|
}
|
||||||
@@ -270,23 +269,23 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
data={data}
|
data={data}
|
||||||
onColumnAdd={addColumn}
|
onColumnAdd={addColumn}
|
||||||
/>
|
/>
|
||||||
<ProductionListSaveConfigButton
|
|
||||||
|
<ProductionListConfigManager
|
||||||
columns={columns}
|
columns={columns}
|
||||||
tableState={state}
|
setColumns={setColumns}
|
||||||
onSave={() => {
|
|
||||||
setHasUnsavedChanges(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ProductionListTableViewSelect
|
|
||||||
state={state}
|
state={state}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
setColumns={setColumns}
|
|
||||||
onProfileChange={() => {
|
|
||||||
initialStateRef.current = state;
|
|
||||||
setHasUnsavedChanges(false);
|
|
||||||
}}
|
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
data={data}
|
data={data}
|
||||||
|
bodyshop={bodyshop}
|
||||||
|
technician={technician}
|
||||||
|
currentUser={currentUser}
|
||||||
|
setHasUnsavedChanges={setHasUnsavedChanges}
|
||||||
|
hasUnsavedChanges={hasUnsavedChanges}
|
||||||
|
onSave={() => {
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
initialStateRef.current = state;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier";
|
import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { generateInternalReflections } from "./report-center-modal-utils";
|
import { generateInternalReflections } from "./report-center-modal-utils";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
|
export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
|
||||||
return (
|
return (
|
||||||
@@ -196,7 +196,8 @@ function FiltersSection({ filters, form, bodyshop }) {
|
|||||||
// We have a type of date, so we will use a date picker
|
// We have a type of date, so we will use a date picker
|
||||||
if (type === "date") {
|
if (type === "date") {
|
||||||
return (
|
return (
|
||||||
<FormDatePicker
|
<DateTimePicker
|
||||||
|
isDateOnly
|
||||||
disabled={!operator}
|
disabled={!operator}
|
||||||
onChange={(date) => form.setFieldValue(fieldPath, date)}
|
onChange={(date) => form.setFieldValue(fieldPath, date)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import dayjs from "../../utils/day";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ScoreboardEntryEdit({ entry }) {
|
export default function ScoreboardEntryEdit({ entry }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -52,7 +52,7 @@ export default function ScoreboardEntryEdit({ entry }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("scoreboard.fields.bodyhrs")}
|
label={t("scoreboard.fields.bodyhrs")}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { INSERT_VACATION } from "../../graphql/employees.queries";
|
import { INSERT_VACATION } from "../../graphql/employees.queries";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ShopEmployeeAddVacation({ employee }) {
|
export default function ShopEmployeeAddVacation({ employee }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -64,7 +64,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("employees.fields.vacation.end")}
|
label={t("employees.fields.vacation.end")}
|
||||||
@@ -90,7 +90,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import CiecaSelect from "../../utils/Ciecaselect";
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.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";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -266,10 +266,10 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
|
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("employees.fields.user_email")}
|
label={t("employees.fields.user_email")}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
||||||
import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -246,7 +245,8 @@ export function TaskUpsertModalComponent({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
||||||
<FormDatePicker
|
<DateTimePicker
|
||||||
|
isDateOnly
|
||||||
onlyFuture
|
onlyFuture
|
||||||
format="MM/DD/YYYY"
|
format="MM/DD/YYYY"
|
||||||
presets={generatePresets(selectedJobDetails)}
|
presets={generatePresets(selectedJobDetails)}
|
||||||
@@ -278,12 +278,7 @@ export function TaskUpsertModalComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDateTimePickerEnhanced
|
<DateTimePicker onlyFuture presets={generatePresets(selectedJobDetails)} />
|
||||||
onlyFuture
|
|
||||||
showTime
|
|
||||||
minuteStep={15}
|
|
||||||
presets={generatePresets(selectedJobDetails)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
||||||
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";
|
||||||
@@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectTechnician,
|
||||||
technician: selectTechnician
|
technician: selectTechnician
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
||||||
|
|
||||||
export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
|||||||
subject:
|
subject:
|
||||||
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
||||||
},
|
},
|
||||||
values.sendby // === "email" ? "e" : "p"
|
values.sendby,
|
||||||
|
bodyshop
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -71,7 +71,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -10,10 +10,10 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -165,7 +165,7 @@ export function TimeTicketList({
|
|||||||
key: "memo",
|
key: "memo",
|
||||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||||
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||||
render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo)
|
render: (text, record) => (record.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo)
|
||||||
},
|
},
|
||||||
...(Enhanced_Payroll.treatment === "on"
|
...(Enhanced_Payroll.treatment === "on"
|
||||||
? [
|
? [
|
||||||
@@ -206,76 +206,98 @@ export function TimeTicketList({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
title: t("timetickets.fields.created_by"),
|
title: t("timetickets.fields.created_by"),
|
||||||
dataIndex: "created_by",
|
dataIndex: "created_by",
|
||||||
key: "created_by",
|
key: "created_by",
|
||||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||||
render: (text, record) => record.created_by
|
render: (text, record) => record.created_by
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: "Pay",
|
// title: "Pay",
|
||||||
// dataIndex: "pay",
|
// dataIndex: "pay",
|
||||||
// key: "pay",
|
// key: "pay",
|
||||||
// render: (text, record) =>
|
// render: (text, record) =>
|
||||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||||
// .toFormat("$0.00"),
|
// .toFormat("$0.00"),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{techConsole && (
|
{techConsole && (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{ id: record.id, timeticket: record }}
|
context={{ id: record.id, timeticket: record }}
|
||||||
disabled={!record.job || disabled}
|
disabled={!record.job || disabled}
|
||||||
>
|
>
|
||||||
<EditFilled />
|
<EditFilled />
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
)}
|
)}
|
||||||
{!techConsole && (
|
{!techConsole && (
|
||||||
<RbacWrapper
|
<RbacWrapper
|
||||||
action="timetickets:edit"
|
action="timetickets:edit"
|
||||||
noauth={() => {
|
noauth={() => {
|
||||||
return <div />;
|
return <div />;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{
|
context={{
|
||||||
id: record.id,
|
id: record.id,
|
||||||
timeticket: record
|
timeticket: record
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
HasRbacAccess({
|
record.ciecacode
|
||||||
bodyshop,
|
? record.committed_at
|
||||||
authLevel: authLevel,
|
? HasRbacAccess({
|
||||||
action: "timetickets:editcommitted"
|
bodyshop,
|
||||||
}) &&
|
authLevel: authLevel,
|
||||||
HasRbacAccess({
|
action: "timetickets:editcommitted"
|
||||||
bodyshop,
|
}) &&
|
||||||
authLevel: authLevel,
|
HasRbacAccess({
|
||||||
action: "timetickets:shiftedit"
|
bodyshop,
|
||||||
})
|
authLevel: authLevel,
|
||||||
|
action: "timetickets:edit"
|
||||||
|
})
|
||||||
|
: HasRbacAccess({
|
||||||
|
bodyshop,
|
||||||
|
authLevel: authLevel,
|
||||||
|
action: "timetickets:edit"
|
||||||
|
})
|
||||||
|
: record.committed_at
|
||||||
|
? HasRbacAccess({
|
||||||
|
bodyshop,
|
||||||
|
authLevel: authLevel,
|
||||||
|
action: "timetickets:editcommitted"
|
||||||
|
}) &&
|
||||||
|
HasRbacAccess({
|
||||||
|
bodyshop,
|
||||||
|
authLevel: authLevel,
|
||||||
|
action: "timetickets:shiftedit"
|
||||||
|
})
|
||||||
|
: HasRbacAccess({
|
||||||
|
bodyshop,
|
||||||
|
authLevel: authLevel,
|
||||||
|
action: "timetickets:shiftedit"
|
||||||
|
})
|
||||||
? disabled
|
? disabled
|
||||||
: !record.jobid
|
: !record.jobid
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<EditFilled />
|
<EditFilled />
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
</RbacWrapper>
|
</RbacWrapper>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -7,8 +8,10 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import {
|
||||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
default as DateTimePicker,
|
||||||
|
default as FormDateTimePicker
|
||||||
|
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||||
@@ -16,7 +19,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -60,8 +62,8 @@ export function TimeTicketModalComponent({
|
|||||||
{item.cost_center === "timetickets.labels.shift"
|
{item.cost_center === "timetickets.labels.shift"
|
||||||
? t(item.cost_center)
|
? t(item.cost_center)
|
||||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
||||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||||
: item.cost_center}
|
: item.cost_center}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -69,13 +71,7 @@ export function TimeTicketModalComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MemoInput = ({ value, ...props }) => {
|
const MemoInput = ({ value, ...props }) => {
|
||||||
return (
|
return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />;
|
||||||
<Input
|
|
||||||
value={value?.startsWith("timetickets.") ? t(value) : value}
|
|
||||||
{...props}
|
|
||||||
disabled={value?.startsWith("timetickets.") || disabled}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +107,7 @@ export function TimeTicketModalComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="employeeid"
|
name="employeeid"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
|
|||||||
renderItem={(ticket) => (
|
renderItem={(ticket) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Card
|
<Card
|
||||||
title={t(ticket.memo)}
|
title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo}
|
||||||
actions={[
|
actions={[
|
||||||
<TechClockOffButton
|
<TechClockOffButton
|
||||||
jobId={ticket.jobid}
|
jobId={ticket.jobid}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
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 FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function VehicleDetailFormComponent({ form, loading }) {
|
export default function VehicleDetailFormComponent({ form, loading }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -102,7 +102,7 @@ export default function VehicleDetailFormComponent({ form, loading }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt">
|
<Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}>
|
<Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}>
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ export function Manage({ conflict, bodyshop }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
|
||||||
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
||||||
<UpdateAlert />
|
<UpdateAlert />
|
||||||
<HeaderContainer />
|
<HeaderContainer />
|
||||||
|
|||||||
@@ -271,7 +271,8 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"loading": "Unable to load shop details. Please call technical support.",
|
"loading": "Unable to load shop details. Please call technical support.",
|
||||||
"saving": "Error encountered while saving. {{message}}"
|
"saving": "Error encountered while saving. {{message}}",
|
||||||
|
"creatingdefaultview": "Error creating default view."
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||||
@@ -699,7 +700,10 @@
|
|||||||
"workingdays": "Working Days"
|
"workingdays": "Working Days"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"save": "Shop configuration saved successfully. "
|
"save": "Shop configuration saved successfully. ",
|
||||||
|
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?",
|
||||||
|
"areyousure": "Are you sure you want to continue?",
|
||||||
|
"defaultviewcreated": "Default view created successfully."
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"centermustexist": "The chosen responsibility center does not exist.",
|
"centermustexist": "The chosen responsibility center does not exist.",
|
||||||
@@ -1161,7 +1165,8 @@
|
|||||||
"tryagain": "Try Again",
|
"tryagain": "Try Again",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"viewreleasenotes": "See What's Changed",
|
"viewreleasenotes": "See What's Changed",
|
||||||
"remove_alert": "Are you sure you want to dismiss the alert?"
|
"remove_alert": "Are you sure you want to dismiss the alert?",
|
||||||
|
"saveas": "Save As"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||||
@@ -1176,6 +1181,7 @@
|
|||||||
"vehicle": "Vehicle"
|
"vehicle": "Vehicle"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"unsavedchanges": "Unsaved changes.",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"areyousure": "Are you sure?",
|
"areyousure": "Are you sure?",
|
||||||
"barcode": "Barcode",
|
"barcode": "Barcode",
|
||||||
@@ -1183,6 +1189,8 @@
|
|||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"confirmpassword": "Confirm Password",
|
"confirmpassword": "Confirm Password",
|
||||||
"created_at": "Created At",
|
"created_at": "Created At",
|
||||||
|
"date": "Select Date",
|
||||||
|
"datetime": "Select Date & Time",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"errors": "Errors",
|
"errors": "Errors",
|
||||||
"excel": "Excel",
|
"excel": "Excel",
|
||||||
@@ -2731,6 +2739,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
|
"constants": {
|
||||||
|
"main_profile": "Default"
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"small": "Small",
|
"small": "Small",
|
||||||
"medium": "Medium",
|
"medium": "Medium",
|
||||||
@@ -2780,7 +2791,9 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"boardupdate": "Error encountered updating Job. {{message}}",
|
"boardupdate": "Error encountered updating Job. {{message}}",
|
||||||
"removing": "Error removing from production board. {{error}}",
|
"removing": "Error removing from production board. {{error}}",
|
||||||
"settings": "Error saving board settings: {{error}}"
|
"settings": "Error saving board settings: {{error}}",
|
||||||
|
"name_exists": "A Profile with this name already exists. Please choose a different name.",
|
||||||
|
"name_required": "Profile name is required."
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"kiosk_mode": "Kiosk Mode",
|
"kiosk_mode": "Kiosk Mode",
|
||||||
@@ -2834,7 +2847,8 @@
|
|||||||
"totalhours": "Total Hrs ",
|
"totalhours": "Total Hrs ",
|
||||||
"touchtime": "T/T",
|
"touchtime": "T/T",
|
||||||
"viewname": "View Name",
|
"viewname": "View Name",
|
||||||
"alerts": "Alerts"
|
"alerts": "Alerts",
|
||||||
|
"addnewprofile": "Add New Profile"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"removed": "Job removed from production."
|
"removed": "Job removed from production."
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -271,7 +271,8 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
||||||
"saving": ""
|
"saving": "",
|
||||||
|
"creatingdefaultview": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "",
|
"ReceivableCustomField": "",
|
||||||
@@ -699,7 +700,10 @@
|
|||||||
"workingdays": ""
|
"workingdays": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"save": ""
|
"save": "",
|
||||||
|
"unsavedchanges": "",
|
||||||
|
"areyousure": "",
|
||||||
|
"defaultviewcreated": ""
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"centermustexist": "",
|
"centermustexist": "",
|
||||||
@@ -1161,7 +1165,8 @@
|
|||||||
"tryagain": "",
|
"tryagain": "",
|
||||||
"view": "",
|
"view": "",
|
||||||
"viewreleasenotes": "",
|
"viewreleasenotes": "",
|
||||||
"remove_alert": ""
|
"remove_alert": "",
|
||||||
|
"saveas": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
@@ -1176,6 +1181,7 @@
|
|||||||
"vehicle": ""
|
"vehicle": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"unsavedchanges": "",
|
||||||
"actions": "actes",
|
"actions": "actes",
|
||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "code à barre",
|
"barcode": "code à barre",
|
||||||
@@ -1183,6 +1189,8 @@
|
|||||||
"clear": "",
|
"clear": "",
|
||||||
"confirmpassword": "",
|
"confirmpassword": "",
|
||||||
"created_at": "",
|
"created_at": "",
|
||||||
|
"date": "",
|
||||||
|
"datetime": "",
|
||||||
"email": "",
|
"email": "",
|
||||||
"errors": "",
|
"errors": "",
|
||||||
"excel": "",
|
"excel": "",
|
||||||
@@ -2731,6 +2739,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
|
"constants": {
|
||||||
|
"main_profile": ""
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"small": "",
|
"small": "",
|
||||||
"medium": "",
|
"medium": "",
|
||||||
@@ -2780,7 +2791,9 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"boardupdate": "",
|
"boardupdate": "",
|
||||||
"removing": "",
|
"removing": "",
|
||||||
"settings": ""
|
"settings": "",
|
||||||
|
"name_exists": "",
|
||||||
|
"name_required": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"kiosk_mode": "",
|
"kiosk_mode": "",
|
||||||
@@ -2834,7 +2847,8 @@
|
|||||||
"totalhours": "",
|
"totalhours": "",
|
||||||
"touchtime": "",
|
"touchtime": "",
|
||||||
"viewname": "",
|
"viewname": "",
|
||||||
"alerts": ""
|
"alerts": "",
|
||||||
|
"addnewprofile": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"removed": ""
|
"removed": ""
|
||||||
|
|||||||
@@ -1,3 +1,27 @@
|
|||||||
|
- name: AutoHouse Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/ah'
|
||||||
|
schedule: 0 6 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
- name: Claimscorp Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/cc'
|
||||||
|
schedule: 30 6 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
- name: Kaizen Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/kaizen'
|
||||||
|
schedule: 30 5 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
- name: Task Reminders
|
- name: Task Reminders
|
||||||
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
||||||
schedule: '*/15 * * * *'
|
schedule: '*/15 * * * *'
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
res.sendStatus(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Query for the List of Bodyshop Clients.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
res.sendStatus(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Query for the List of Bodyshop Clients.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
||||||
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
res.sendStatus(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Query for the List of Bodyshop Clients.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
||||||
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
||||||
@@ -56,8 +62,8 @@ exports.default = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"),
|
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||||
...(end && { end: moment(end).endOf("hours") })
|
...(end && { end: moment(end).endOf("day") })
|
||||||
});
|
});
|
||||||
|
|
||||||
const kaizenObject = {
|
const kaizenObject = {
|
||||||
@@ -176,24 +182,19 @@ exports.default = async (req, res) => {
|
|||||||
} finally {
|
} finally {
|
||||||
sftp.end();
|
sftp.end();
|
||||||
}
|
}
|
||||||
// sendServerEmail({
|
sendServerEmail({
|
||||||
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
// Uploaded: ${JSON.stringify(
|
Uploaded: ${JSON.stringify(
|
||||||
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
// null,
|
null,
|
||||||
// 2
|
2
|
||||||
// )}
|
)}
|
||||||
// `,
|
`
|
||||||
// });
|
});
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json(error);
|
res.status(200).json(error);
|
||||||
sendServerEmail({
|
|
||||||
subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
|
|
||||||
text: `Errors: JSON.stringify(error)}
|
|
||||||
All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -965,22 +965,17 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (job.adjustment_bottom_line) {
|
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
|
||||||
const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) }));
|
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
||||||
const percent_of_adjustment =
|
if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) {
|
||||||
Math.round(
|
//This amount is taxable for this type.
|
||||||
subtotal_before_adjustment.toUnit() /
|
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
|
||||||
(job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1)
|
Dinero({
|
||||||
) / 100;
|
amount: Math.round(job.adjustment_bottom_line * 100)
|
||||||
|
})
|
||||||
Object.keys(taxableAmountsByTier).forEach((taxTierKey) => {
|
);
|
||||||
taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment);
|
|
||||||
if (job.adjustment_bottom_line > 0) {
|
|
||||||
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment);
|
|
||||||
} else {
|
|
||||||
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].subtract(taxable_adjustment);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainingTaxableAmounts = taxableAmountsByTier;
|
const remainingTaxableAmounts = taxableAmountsByTier;
|
||||||
|
|||||||
Reference in New Issue
Block a user