Allow for Component Token Overrides.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -15,7 +15,7 @@ const BillLineSearchSelect = (
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
dropdownMatchSelectWidth={false}
|
||||
popupMatchSelectWidth={false}
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
return (
|
||||
|
||||
@@ -1,55 +1,64 @@
|
||||
import { HomeFilled } from "@ant-design/icons";
|
||||
import { Breadcrumb, Row, Col } from "antd";
|
||||
import {HomeFilled} from "@ant-design/icons";
|
||||
import {Breadcrumb, Col, Row} from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {connect} from "react-redux";
|
||||
import {Link} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBreadcrumbs} from "../../redux/application/application.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||
import "./breadcrumbs.styles.scss";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
bodyshop: selectBodyshop,
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
export function BreadCrumbs({breadcrumbs, bodyshop}) {
|
||||
|
||||
const { treatments: {OpenSearch} } = useSplitTreatments({
|
||||
const {treatments: {OpenSearch}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["OpenSearch"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
// TODO - Client Update - Technically key is not doing anything here
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
<Breadcrumb separator=">">
|
||||
<Breadcrumb.Item>
|
||||
<Link to={`/manage/`}>
|
||||
<HomeFilled />{" "}
|
||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
{breadcrumbs.map((item) =>
|
||||
item.link ? (
|
||||
<Breadcrumb.Item key={item.label}>
|
||||
<Link to={item.link}>{item.label} </Link>
|
||||
</Breadcrumb.Item>
|
||||
) : (
|
||||
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item>
|
||||
)
|
||||
)}
|
||||
</Breadcrumb>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
<Breadcrumb
|
||||
separator=">"
|
||||
items={[
|
||||
{
|
||||
key: "home",
|
||||
title: (
|
||||
<Link to={`/manage/`}>
|
||||
<HomeFilled/>{" "}
|
||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
...breadcrumbs.map((item) =>
|
||||
item.link
|
||||
? {
|
||||
key: item.label,
|
||||
title: <Link to={item.link}>{item.label}</Link>,
|
||||
}
|
||||
: {
|
||||
key: item.label,
|
||||
title: item.label,
|
||||
}
|
||||
),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs/> : <GlobalSearch/>}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(BreadCrumbs);
|
||||
|
||||
@@ -16,19 +16,19 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
|
||||
const menuItems = bodyshop.md_messaging_presets.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: i.label,
|
||||
onClick: () => setMessage(i.text),
|
||||
}));
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
{bodyshop.md_messaging_presets.map((i, idx) => (
|
||||
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
|
||||
{i.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu items={menuItems} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Dropdown trigger={["click"]} overlay={menu}>
|
||||
<Dropdown trigger={["click"]} menu={menu}>
|
||||
<PlusCircleOutlined />
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function ChatTagRoComponent({
|
||||
<Select
|
||||
showSearch
|
||||
autoFocus
|
||||
dropdownMatchSelectWidth
|
||||
popupMatchSelectWidth
|
||||
placeholder={t("general.labels.search")}
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
|
||||
@@ -18,20 +18,20 @@ export function ContractsRatesChangeButton({ disabled, form, bodyshop }) {
|
||||
form.setFieldsValue(rate);
|
||||
};
|
||||
|
||||
const menuItems = bodyshop.md_ccc_rates.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: i.label,
|
||||
value: i,
|
||||
}));
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_ccc_rates.map((rate, idx) => (
|
||||
<Menu.Item value={rate} key={idx}>
|
||||
{rate.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu onClick={handleClick} items={menuItems} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<Dropdown menu={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -178,6 +178,25 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: courtesycars;
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
key: "courtesycar_inventory",
|
||||
label: t("printcenter.courtesycarcontract.courtesy_car_inventory"),
|
||||
onClick: () =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycar").courtesy_car_inventory.key,
|
||||
variables: {
|
||||
//id: contract.id
|
||||
},
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("menus.header.courtesycars")}
|
||||
@@ -188,26 +207,8 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycar").courtesy_car_inventory
|
||||
.key,
|
||||
variables: {
|
||||
//id: contract.id
|
||||
},
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("printcenter.courtesycarcontract.courtesy_car_inventory")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu items={menuItems} />
|
||||
}
|
||||
>
|
||||
<Button>{t("general.labels.print")}</Button>
|
||||
|
||||
@@ -123,18 +123,16 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
[data]
|
||||
);
|
||||
const existingLayoutKeys = state.items.map((i) => i.i);
|
||||
|
||||
const menuItems = Object.keys(componentList).map((key) => ({
|
||||
key: key,
|
||||
label: componentList[key].label,
|
||||
value: key,
|
||||
disabled: existingLayoutKeys.includes(key),
|
||||
}));
|
||||
|
||||
const addComponentOverlay = (
|
||||
<Menu onClick={handleAddComponent}>
|
||||
{Object.keys(componentList).map((key) => (
|
||||
<Menu.Item
|
||||
key={key}
|
||||
value={key}
|
||||
disabled={existingLayoutKeys.includes(key)}
|
||||
>
|
||||
{componentList[key].label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu onClick={handleAddComponent} items={menuItems} />
|
||||
);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
@@ -147,7 +145,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
||||
<Dropdown menu={addComponentOverlay} trigger={["click"]}>
|
||||
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
|
||||
@@ -22,20 +22,22 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
|
||||
|
||||
export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
return (
|
||||
<Timeline pending
|
||||
reverse={true}
|
||||
>
|
||||
{logs.map((log, idx) => (
|
||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
||||
<Timeline
|
||||
pending
|
||||
reverse={true}
|
||||
items={logs.map((log, idx) => ({
|
||||
key: idx,
|
||||
color: LogLevelHierarchy(log.level),
|
||||
label: (
|
||||
<Space wrap align="start" style={{}}>
|
||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||
<span>{dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span>
|
||||
<Divider type="vertical" />
|
||||
<span>{log.message}</span>
|
||||
</Space>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { DeleteFilled, DownOutlined } from "@ant-design/icons";
|
||||
import {DeleteFilled, DownOutlined} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Menu,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Menu,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { determineDmsType } from "../../pages/dms/dms.container";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {determineDmsType} from "../../pages/dms/dms.container";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import i18n from "../../translations/i18n";
|
||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||
@@ -30,440 +30,427 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
|
||||
|
||||
export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
export function DmsPostForm({bodyshop, socket, job, logsRef}) {
|
||||
const [form] = Form.useForm();
|
||||
const {t} = useTranslation();
|
||||
|
||||
const handlePayerSelect = (value, index) => {
|
||||
form.setFieldsValue({
|
||||
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
|
||||
if (index !== mapIndex) return payer;
|
||||
const cdkPayer =
|
||||
bodyshop.cdk_configuration.payers &&
|
||||
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
|
||||
const handlePayerSelect = (value, index) => {
|
||||
form.setFieldsValue({
|
||||
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
|
||||
if (index !== mapIndex) return payer;
|
||||
const cdkPayer =
|
||||
bodyshop.cdk_configuration.payers &&
|
||||
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
|
||||
|
||||
if (!cdkPayer) return payer;
|
||||
if (!cdkPayer) return payer;
|
||||
|
||||
return {
|
||||
...cdkPayer,
|
||||
dms_acctnumber: cdkPayer.dms_acctnumber,
|
||||
controlnumber: job && job[cdkPayer.control_type],
|
||||
};
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const handleFinish = (values) => {
|
||||
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
||||
jobid: job.id,
|
||||
txEnvelope: values,
|
||||
});
|
||||
console.log(logsRef);
|
||||
if (logsRef) {
|
||||
console.log("executing", logsRef);
|
||||
logsRef.curent &&
|
||||
logsRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
return {
|
||||
...cdkPayer,
|
||||
dms_acctnumber: cdkPayer.dms_acctnumber,
|
||||
controlnumber: job && job[cdkPayer.control_type],
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={t("jobs.labels.dms.postingform")}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{
|
||||
story: `${t("jobs.labels.dms.defaultstory", {
|
||||
ro_number: job.ro_number,
|
||||
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||
job.ownr_co_nm || ""
|
||||
}`.trim(),
|
||||
ins_co_nm: job.ins_co_nm || "N/A",
|
||||
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
|
||||
job.po_number || ""
|
||||
}`,
|
||||
}).trim()}.${
|
||||
job.area_of_damage && job.area_of_damage.impact1
|
||||
? " " +
|
||||
t("jobs.labels.dms.damageto", {
|
||||
area_of_damage:
|
||||
(job.area_of_damage && job.area_of_damage.impact1) ||
|
||||
"UNKNOWN",
|
||||
})
|
||||
: ""
|
||||
}`.slice(0, 239),
|
||||
inservicedate: dayjs("2019-01-01"),
|
||||
}}
|
||||
>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
name="journal"
|
||||
label={t("jobs.fields.dms.journal")}
|
||||
initialValue={
|
||||
bodyshop.cdk_configuration &&
|
||||
bodyshop.cdk_configuration.default_journal
|
||||
}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="kmin"
|
||||
label={t("jobs.fields.kmin")}
|
||||
initialValue={job && job.kmin}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="kmout"
|
||||
label={t("jobs.fields.kmout")}
|
||||
initialValue={job && job.kmout}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber disabled />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
const handleFinish = (values) => {
|
||||
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
||||
jobid: job.id,
|
||||
txEnvelope: values,
|
||||
});
|
||||
console.log(logsRef);
|
||||
if (logsRef) {
|
||||
console.log("executing", logsRef);
|
||||
logsRef.curent &&
|
||||
logsRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
{bodyshop.cdk_dealerid && (
|
||||
<div>
|
||||
<LayoutFormRow style={{ justifyContent: "center" }} grow>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model"
|
||||
label={t("jobs.fields.dms.dms_model")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="inservicedate"
|
||||
label={t("jobs.fields.dms.inservicedate")}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item
|
||||
name="dms_unsold"
|
||||
label={t("jobs.fields.dms.dms_unsold")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model_override"
|
||||
label={t("jobs.fields.dms.dms_model_override")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
<Form.Item
|
||||
name="story"
|
||||
label={t("jobs.fields.dms.story")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea maxLength={240} />
|
||||
</Form.Item>
|
||||
|
||||
<Divider />
|
||||
<Space size="large" wrap align="center">
|
||||
<Statistic
|
||||
title={t("jobs.fields.ded_amt")}
|
||||
value={Dinero(
|
||||
job.job_totals.totals.custPayable.deductible
|
||||
).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("jobs.labels.total_cust_payable")}
|
||||
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("jobs.labels.net_repairs")}
|
||||
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
|
||||
/>
|
||||
</Space>
|
||||
<Form.List name={["payers"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Space wrap>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
style={{ minWidth: "15rem" }}
|
||||
onSelect={(value) => handlePayerSelect(value, index)}
|
||||
>
|
||||
{bodyshop.cdk_configuration &&
|
||||
bodyshop.cdk_configuration.payers &&
|
||||
bodyshop.cdk_configuration.payers.map((payer) => (
|
||||
<Select.Option key={payer.name}>
|
||||
{payer.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.dms_acctnumber")}
|
||||
key={`${index}dms_acctnumber`}
|
||||
name={[field.name, "dms_acctnumber"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.amount")}
|
||||
key={`${index}amount`}
|
||||
name={[field.name, "amount"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<div>
|
||||
{t("jobs.fields.dms.payer.controlnumber")}{" "}
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
{bodyshop.cdk_configuration.controllist &&
|
||||
bodyshop.cdk_configuration.controllist.map(
|
||||
(key, idx) => (
|
||||
<Menu.Item
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
form.setFieldsValue({
|
||||
payers: form
|
||||
.getFieldValue("payers")
|
||||
.map((row, mapIndex) => {
|
||||
if (index !== mapIndex)
|
||||
return row;
|
||||
|
||||
return {
|
||||
...row,
|
||||
controlnumber:
|
||||
key.controlnumber,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{key.name}
|
||||
</Menu.Item>
|
||||
)
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
return (
|
||||
<Card title={t("jobs.labels.dms.postingform")}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{
|
||||
story: `${t("jobs.labels.dms.defaultstory", {
|
||||
ro_number: job.ro_number,
|
||||
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||
job.ownr_co_nm || ""
|
||||
}`.trim(),
|
||||
ins_co_nm: job.ins_co_nm || "N/A",
|
||||
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
|
||||
job.po_number || ""
|
||||
}`,
|
||||
}).trim()}.${
|
||||
job.area_of_damage && job.area_of_damage.impact1
|
||||
? " " +
|
||||
t("jobs.labels.dms.damageto", {
|
||||
area_of_damage:
|
||||
(job.area_of_damage && job.area_of_damage.impact1) ||
|
||||
"UNKNOWN",
|
||||
})
|
||||
: ""
|
||||
}`.slice(0, 239),
|
||||
inservicedate: dayjs("2019-01-01"),
|
||||
}}
|
||||
>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
name="journal"
|
||||
label={t("jobs.fields.dms.journal")}
|
||||
initialValue={
|
||||
bodyshop.cdk_configuration &&
|
||||
bodyshop.cdk_configuration.default_journal
|
||||
}
|
||||
key={`${index}controlnumber`}
|
||||
name={[field.name, "controlnumber"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="kmin"
|
||||
label={t("jobs.fields.kmin")}
|
||||
initialValue={job && job.kmin}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber disabled/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="kmout"
|
||||
label={t("jobs.fields.kmout")}
|
||||
initialValue={job && job.kmout}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber disabled/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
const payers = form.getFieldValue("payers");
|
||||
|
||||
const row = payers && payers[index];
|
||||
|
||||
const cdkPayer =
|
||||
bodyshop.cdk_configuration.payers &&
|
||||
bodyshop.cdk_configuration.payers.find(
|
||||
(i) => i && row && i.name === row.name
|
||||
);
|
||||
if (
|
||||
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
{cdkPayer &&
|
||||
t(`jobs.fields.${cdkPayer?.control_type}`)}
|
||||
</div>
|
||||
);
|
||||
else if (
|
||||
i18n.exists(
|
||||
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
||||
)
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
{cdkPayer &&
|
||||
t(
|
||||
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
disabled={!(fields.length < 3)}
|
||||
onClick={() => {
|
||||
if (fields.length < 3) add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("jobs.actions.dms.addpayer")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
//Perform Calculation to determine discrepancy.
|
||||
let totalAllocated = Dinero();
|
||||
|
||||
const payers = form.getFieldValue("payers");
|
||||
payers &&
|
||||
payers.forEach((payer) => {
|
||||
totalAllocated = totalAllocated.add(
|
||||
Dinero({ amount: Math.round((payer?.amount || 0) * 100) })
|
||||
);
|
||||
});
|
||||
|
||||
const totals =
|
||||
socket.allocationsSummary &&
|
||||
socket.allocationsSummary.reduce(
|
||||
(acc, val) => {
|
||||
return {
|
||||
totalSale: acc.totalSale.add(Dinero(val.sale)),
|
||||
totalCost: acc.totalCost.add(Dinero(val.cost)),
|
||||
};
|
||||
},
|
||||
{
|
||||
totalSale: Dinero(),
|
||||
totalCost: Dinero(),
|
||||
}
|
||||
);
|
||||
const discrep = totals
|
||||
? totals.totalSale.subtract(totalAllocated)
|
||||
: Dinero();
|
||||
return (
|
||||
<Space size="large" wrap align="center">
|
||||
<Statistic
|
||||
title={t("jobs.labels.subtotal")}
|
||||
value={(totals ? totals.totalSale : Dinero()).toFormat()}
|
||||
/>
|
||||
<Typography.Title>-</Typography.Title>
|
||||
<Statistic
|
||||
title={t("jobs.labels.dms.totalallocated")}
|
||||
value={totalAllocated.toFormat()}
|
||||
/>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Statistic
|
||||
title={t("jobs.labels.dms.notallocated")}
|
||||
valueStyle={{
|
||||
color: discrep.getAmount() === 0 ? "green" : "red",
|
||||
}}
|
||||
value={discrep.toFormat()}
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
!socket.allocationsSummary || discrep.getAmount() !== 0
|
||||
}
|
||||
htmlType="submit"
|
||||
{bodyshop.cdk_dealerid && (
|
||||
<div>
|
||||
<LayoutFormRow style={{justifyContent: "center"}} grow>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model"
|
||||
label={t("jobs.fields.dms.dms_model")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="inservicedate"
|
||||
label={t("jobs.fields.dms.inservicedate")}
|
||||
>
|
||||
<FormDatePicker/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job}/>
|
||||
<DmsCdkMakesRefetch/>
|
||||
<Form.Item
|
||||
name="dms_unsold"
|
||||
label={t("jobs.fields.dms.dms_unsold")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model_override"
|
||||
label={t("jobs.fields.dms.dms_model_override")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
<Form.Item
|
||||
name="story"
|
||||
label={t("jobs.fields.dms.story")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{t("jobs.actions.dms.post")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
<Input.TextArea maxLength={240}/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider/>
|
||||
<Space size="large" wrap align="center">
|
||||
<Statistic
|
||||
title={t("jobs.fields.ded_amt")}
|
||||
value={Dinero(
|
||||
job.job_totals.totals.custPayable.deductible
|
||||
).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("jobs.labels.total_cust_payable")}
|
||||
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("jobs.labels.net_repairs")}
|
||||
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
|
||||
/>
|
||||
</Space>
|
||||
<Form.List name={["payers"]}>
|
||||
{(fields, {add, remove}) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Space wrap>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
style={{minWidth: "15rem"}}
|
||||
onSelect={(value) => handlePayerSelect(value, index)}
|
||||
>
|
||||
{bodyshop.cdk_configuration &&
|
||||
bodyshop.cdk_configuration.payers &&
|
||||
bodyshop.cdk_configuration.payers.map((payer) => (
|
||||
<Select.Option key={payer.name}>
|
||||
{payer.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.dms_acctnumber")}
|
||||
key={`${index}dms_acctnumber`}
|
||||
name={[field.name, "dms_acctnumber"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.amount")}
|
||||
key={`${index}amount`}
|
||||
name={[field.name, "amount"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput min={0}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<div>
|
||||
{t("jobs.fields.dms.payer.controlnumber")}{" "}
|
||||
<Dropdown
|
||||
menu={<Menu
|
||||
items={bodyshop.cdk_configuration.controllist?.map((key, idx) => ({
|
||||
key: idx,
|
||||
label: key.name,
|
||||
onClick: () => {
|
||||
form.setFieldsValue({
|
||||
payers: form.getFieldValue("payers").map((row, mapIndex) => {
|
||||
if (index !== mapIndex) return row;
|
||||
return {
|
||||
...row,
|
||||
controlnumber: key.controlnumber,
|
||||
};
|
||||
}),
|
||||
});
|
||||
},
|
||||
}))}
|
||||
/>}
|
||||
>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
}
|
||||
key={`${index}controlnumber`}
|
||||
name={[field.name, "controlnumber"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
const payers = form.getFieldValue("payers");
|
||||
|
||||
const row = payers && payers[index];
|
||||
|
||||
const cdkPayer =
|
||||
bodyshop.cdk_configuration.payers &&
|
||||
bodyshop.cdk_configuration.payers.find(
|
||||
(i) => i && row && i.name === row.name
|
||||
);
|
||||
if (
|
||||
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
{cdkPayer &&
|
||||
t(`jobs.fields.${cdkPayer?.control_type}`)}
|
||||
</div>
|
||||
);
|
||||
else if (
|
||||
i18n.exists(
|
||||
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
||||
)
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
{cdkPayer &&
|
||||
t(
|
||||
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
disabled={!(fields.length < 3)}
|
||||
onClick={() => {
|
||||
if (fields.length < 3) add();
|
||||
}}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
{t("jobs.actions.dms.addpayer")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
//Perform Calculation to determine discrepancy.
|
||||
let totalAllocated = Dinero();
|
||||
|
||||
const payers = form.getFieldValue("payers");
|
||||
payers &&
|
||||
payers.forEach((payer) => {
|
||||
totalAllocated = totalAllocated.add(
|
||||
Dinero({amount: Math.round((payer?.amount || 0) * 100)})
|
||||
);
|
||||
});
|
||||
|
||||
const totals =
|
||||
socket.allocationsSummary &&
|
||||
socket.allocationsSummary.reduce(
|
||||
(acc, val) => {
|
||||
return {
|
||||
totalSale: acc.totalSale.add(Dinero(val.sale)),
|
||||
totalCost: acc.totalCost.add(Dinero(val.cost)),
|
||||
};
|
||||
},
|
||||
{
|
||||
totalSale: Dinero(),
|
||||
totalCost: Dinero(),
|
||||
}
|
||||
);
|
||||
const discrep = totals
|
||||
? totals.totalSale.subtract(totalAllocated)
|
||||
: Dinero();
|
||||
return (
|
||||
<Space size="large" wrap align="center">
|
||||
<Statistic
|
||||
title={t("jobs.labels.subtotal")}
|
||||
value={(totals ? totals.totalSale : Dinero()).toFormat()}
|
||||
/>
|
||||
<Typography.Title>-</Typography.Title>
|
||||
<Statistic
|
||||
title={t("jobs.labels.dms.totalallocated")}
|
||||
value={totalAllocated.toFormat()}
|
||||
/>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Statistic
|
||||
title={t("jobs.labels.dms.notallocated")}
|
||||
valueStyle={{
|
||||
color: discrep.getAmount() === 0 ? "green" : "red",
|
||||
}}
|
||||
value={discrep.toFormat()}
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
!socket.allocationsSummary || discrep.getAmount() !== 0
|
||||
}
|
||||
htmlType="submit"
|
||||
>
|
||||
{t("jobs.actions.dms.post")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,267 +1,269 @@
|
||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
Select,
|
||||
Space,
|
||||
Tabs,
|
||||
Upload,
|
||||
} from "antd";
|
||||
import {UploadOutlined, UserAddOutlined} from "@ant-design/icons";
|
||||
import {Button, Divider, Dropdown, Form, Input, Menu, Select, Space, Tabs, Upload,} from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectEmailConfig} from "../../redux/email/email.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import {CreateExplorerLinkForJob} from "../../utils/localmedia";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
emailConfig: selectEmailConfig,
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
emailConfig: selectEmailConfig,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(EmailOverlayComponent);
|
||||
|
||||
export function EmailOverlayComponent({
|
||||
emailConfig,
|
||||
form,
|
||||
selectedMediaState,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
to: _.uniq([
|
||||
...form.getFieldValue("to"),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
const handle_CC_Click = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
cc: _.uniq([
|
||||
...(form.getFieldValue("cc") || ""),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
emailConfig,
|
||||
form,
|
||||
selectedMediaState,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const handleClick = ({item, key, keyPath}) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
to: _.uniq([
|
||||
...form.getFieldValue("to"),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
const handle_CC_Click = ({item, key, keyPath}) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
cc: _.uniq([
|
||||
...(form.getFieldValue("cc") || ""),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.md_to_emails.map((e, idx) => (
|
||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
||||
{e.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
const menuCC = (
|
||||
<div>
|
||||
<Menu onClick={handle_CC_Click}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.md_to_emails.map((e, idx) => (
|
||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
||||
{e.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("emails.fields.from")}
|
||||
name="from"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option key={currentUser.email}>
|
||||
{currentUser.email}
|
||||
</Select.Option>
|
||||
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
||||
{bodyshop.md_from_emails &&
|
||||
bodyshop.md_from_emails.map((e) => (
|
||||
<Select.Option key={e}>{e}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.to")}
|
||||
<Dropdown overlay={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="to"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.cc")}
|
||||
<Dropdown overlay={menuCC}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="cc"
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("emails.fields.subject")}
|
||||
name="subject"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t("emails.labels.preview")}</Divider>
|
||||
{bodyshop.attach_pdf_to_email && (
|
||||
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
|
||||
)}
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
|
||||
backgroundColor: "lightgray",
|
||||
borderLeft: "6px solid #2196F3",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }}
|
||||
const menu = (
|
||||
<Menu
|
||||
onClick={handleClick}
|
||||
items={[
|
||||
...bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => ({
|
||||
key: idx,
|
||||
label: `${e.first_name} ${e.last_name}`,
|
||||
value: e.user_email,
|
||||
})),
|
||||
...bodyshop.md_to_emails.map((e, idx) => ({
|
||||
key: idx + "group",
|
||||
label: e.label,
|
||||
value: e.emails,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
<Tabs>
|
||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
||||
<EmailDocumentsComponent
|
||||
selectedMediaState={selectedMediaState}
|
||||
form={form}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
const menuCC = (
|
||||
<div>
|
||||
<Menu
|
||||
onClick={handle_CC_Click}
|
||||
items={[
|
||||
...bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => ({
|
||||
key: idx,
|
||||
label: `${e.first_name} ${e.last_name}`,
|
||||
value: e.user_email,
|
||||
})),
|
||||
...bodyshop.md_to_emails.map((e, idx) => ({
|
||||
key: idx + "group",
|
||||
label: e.label,
|
||||
value: e.emails,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
|
||||
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
||||
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>
|
||||
<Button>{t("documents.labels.openinexplorer")}</Button>
|
||||
</a>
|
||||
)}
|
||||
<Form.Item
|
||||
name="fileList"
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={(e) => {
|
||||
if (Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return e && e.fileList;
|
||||
}}
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
const totalSize = value.reduce(
|
||||
(acc, val) => (acc = acc + val.size),
|
||||
0
|
||||
);
|
||||
|
||||
const limit =
|
||||
10485760 - new Blob([form.getFieldValue("html")]).size;
|
||||
|
||||
if (totalSize > limit) {
|
||||
return Promise.reject(t("general.errors.sizelimit"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Upload.Dragger
|
||||
beforeUpload={Upload.LIST_IGNORE}
|
||||
multiple
|
||||
listType="picture-card"
|
||||
return (
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("emails.fields.from")}
|
||||
name="from"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
Click or drag files to this area to upload.
|
||||
</p>
|
||||
</>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
<Select>
|
||||
<Select.Option key={currentUser.email}>
|
||||
{currentUser.email}
|
||||
</Select.Option>
|
||||
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
||||
{bodyshop.md_from_emails &&
|
||||
bodyshop.md_from_emails.map((e) => (
|
||||
<Select.Option key={e}>{e}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.to")}
|
||||
<Dropdown menu={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="to"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.cc")}
|
||||
<Dropdown menu={menuCC}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="cc"
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("emails.fields.subject")}
|
||||
name="subject"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t("emails.labels.preview")}</Divider>
|
||||
{bodyshop.attach_pdf_to_email && (
|
||||
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
|
||||
)}
|
||||
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
|
||||
backgroundColor: "lightgray",
|
||||
borderLeft: "6px solid #2196F3",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{__html: form.getFieldValue("html")}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="documents"
|
||||
items={[
|
||||
{
|
||||
key: "documents",
|
||||
tab: t("emails.labels.documents"),
|
||||
children: (
|
||||
<EmailDocumentsComponent
|
||||
selectedMediaState={selectedMediaState}
|
||||
form={form}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "attachments",
|
||||
tab: t("emails.labels.attachments"),
|
||||
children: (
|
||||
<>
|
||||
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
||||
<a href={CreateExplorerLinkForJob({jobid: emailConfig.jobid})}>
|
||||
<Button>{t("documents.labels.openinexplorer")}</Button>
|
||||
</a>
|
||||
)}
|
||||
<Form.Item
|
||||
name="fileList"
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={(e) => {
|
||||
if (Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return e && e.fileList;
|
||||
}}
|
||||
rules={[
|
||||
({getFieldValue}) => ({
|
||||
validator(rule, value) {
|
||||
const totalSize = value.reduce(
|
||||
(acc, val) => (acc = acc + val.size),
|
||||
0
|
||||
);
|
||||
|
||||
const limit =
|
||||
10485760 - new Blob([form.getFieldValue("html")]).size;
|
||||
|
||||
if (totalSize > limit) {
|
||||
return Promise.reject(t("general.errors.sizelimit"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Upload.Dragger
|
||||
beforeUpload={Upload.LIST_IGNORE}
|
||||
multiple
|
||||
listType="picture-card"
|
||||
>
|
||||
<>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined/>
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
Click or drag files to this area to upload.
|
||||
</p>
|
||||
</>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,17 +38,20 @@ export function JobAltTransportChange({ bodyshop, job }) {
|
||||
}
|
||||
};
|
||||
const menu = (
|
||||
<Menu selectedKeys={[job && job.alt_transport]} onClick={onClick}>
|
||||
{bodyshop.appt_alt_transport &&
|
||||
bodyshop.appt_alt_transport.map((alt) => (
|
||||
<Menu.Item key={alt}>{alt}</Menu.Item>
|
||||
))}
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
<Menu
|
||||
selectedKeys={[job && job.alt_transport]}
|
||||
onClick={onClick}
|
||||
items={[
|
||||
...(bodyshop.appt_alt_transport || []).map((alt) => ({
|
||||
key: alt,
|
||||
label: alt,
|
||||
})),
|
||||
{ key: "null", label: t("general.actions.clear") },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined />
|
||||
</a>
|
||||
|
||||
@@ -45,20 +45,23 @@ export function ScheduleEventColor({ bodyshop, event }) {
|
||||
?.label;
|
||||
|
||||
const menu = (
|
||||
<Menu selectedKeys={[event.color]} onClick={onClick}>
|
||||
{bodyshop.appt_colors &&
|
||||
bodyshop.appt_colors.map((color) => (
|
||||
<Menu.Item style={{ color: color.color.hex }} key={color.color.hex}>
|
||||
{color.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
<Menu
|
||||
selectedKeys={[event.color]}
|
||||
onClick={onClick}
|
||||
items={[
|
||||
...(bodyshop.appt_colors || []).map((color) => ({
|
||||
key: color.color.hex,
|
||||
label: color.label,
|
||||
style: { color: color.color.hex },
|
||||
})),
|
||||
{ key: "divider", label: <hr />, disabled: true },
|
||||
{ key: "null", label: t("general.actions.clear") },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{selectedColor}
|
||||
<DownOutlined />
|
||||
|
||||
@@ -185,55 +185,57 @@ export function ScheduleEventComponent({
|
||||
) : null}
|
||||
{event.job ? (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
const Template = TemplateList("job").appointment_reminder;
|
||||
GenerateDocument(
|
||||
{
|
||||
name: Template.key,
|
||||
variables: { id: event.job.id },
|
||||
},
|
||||
{
|
||||
to: event.job && event.job.ownr_ea,
|
||||
subject: Template.subject,
|
||||
},
|
||||
"e",
|
||||
event.job && event.job.id
|
||||
);
|
||||
}}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("general.labels.email")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: event.job.id,
|
||||
});
|
||||
setMessage(
|
||||
t("appointments.labels.reminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
date: dayjs(event.start).format("MM/DD/YYYY"),
|
||||
time: dayjs(event.start).format("HH:mm a"),
|
||||
})
|
||||
);
|
||||
setVisible(false);
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("messaging.error.invalidphone"),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={event.arrived || !bodyshop.messagingservicesid}
|
||||
>
|
||||
{t("general.labels.sms")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
key: "email",
|
||||
label: t("general.labels.email"),
|
||||
disabled: event.arrived,
|
||||
onClick: () => {
|
||||
const Template = TemplateList("job").appointment_reminder;
|
||||
GenerateDocument(
|
||||
{
|
||||
name: Template.key,
|
||||
variables: { id: event.job.id },
|
||||
},
|
||||
{
|
||||
to: event.job && event.job.ownr_ea,
|
||||
subject: Template.subject,
|
||||
},
|
||||
"e",
|
||||
event.job && event.job.id
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "sms",
|
||||
label: t("general.labels.sms"),
|
||||
disabled: event.arrived || !bodyshop.messagingservicesid,
|
||||
onClick: () => {
|
||||
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: event.job.id,
|
||||
});
|
||||
setMessage(
|
||||
t("appointments.labels.reminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
date: dayjs(event.start).format("MM/DD/YYYY"),
|
||||
time: dayjs(event.start).format("HH:mm a"),
|
||||
})
|
||||
);
|
||||
setVisible(false);
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("messaging.error.invalidphone"),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button>{t("appointments.actions.sendreminder")}</Button>
|
||||
|
||||
@@ -1,119 +1,188 @@
|
||||
import { Timeline } from "antd";
|
||||
import {Timeline} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
export default function JobDetailCardsDatesComponent({loading, data}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}>
|
||||
{data ? (
|
||||
<Timeline>
|
||||
{!(
|
||||
data.actual_in ||
|
||||
data.scheduled_completion ||
|
||||
data.scheduled_in ||
|
||||
data.actual_completion ||
|
||||
data.scheduled_delivery ||
|
||||
data.actual_delivery ||
|
||||
data.date_estimated ||
|
||||
data.date_open ||
|
||||
data.date_scheduled ||
|
||||
data.date_invoiced ||
|
||||
data.date_exported
|
||||
) ? (
|
||||
<div>{t("jobs.errors.nodates")}</div>
|
||||
) : null}
|
||||
{data.date_last_contacted ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_last_contacted")}: </label>
|
||||
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.date_open ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_open")}: </label>
|
||||
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_estimated ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_estimated")}: </label>
|
||||
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_scheduled ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_scheduled")}: </label>
|
||||
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_in")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.actual_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_in")}: </label>
|
||||
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.date_repairstarted ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_repairstarted")}: </label>
|
||||
<DateTimeFormatter>{data.date_repairstarted}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.scheduled_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_completion")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_completion")}: </label>
|
||||
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_invoiced ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_invoiced")}: </label>
|
||||
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_exported ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_exported")}: </label>
|
||||
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
</Timeline>
|
||||
) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}>
|
||||
{data ? (
|
||||
<Timeline
|
||||
items={[
|
||||
...(data.date_last_contacted
|
||||
? [
|
||||
{
|
||||
key: "date_last_contacted",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_last_contacted")}: </label>
|
||||
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_open
|
||||
? [
|
||||
{
|
||||
key: "date_open",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_open")}: </label>
|
||||
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_estimated
|
||||
? [
|
||||
{
|
||||
key: "date_estimated",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_estimated")}: </label>
|
||||
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_scheduled
|
||||
? [
|
||||
{
|
||||
key: "date_scheduled",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_scheduled")}: </label>
|
||||
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.scheduled_in
|
||||
? [
|
||||
{
|
||||
key: "scheduled_in",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.scheduled_in")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.actual_in
|
||||
? [
|
||||
{
|
||||
key: "actual_in",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.actual_in")}: </label>
|
||||
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_repairstarted
|
||||
? [
|
||||
{
|
||||
key: "date_repairstarted",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_repairstarted")}: </label>
|
||||
<DateTimeFormatter>{data.date_repairstarted}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.scheduled_completion
|
||||
? [
|
||||
{
|
||||
key: "scheduled_completion",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.scheduled_completion")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.actual_completion
|
||||
? [
|
||||
{
|
||||
key: "actual_completion",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.actual_completion")}: </label>
|
||||
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.scheduled_delivery
|
||||
? [
|
||||
{
|
||||
key: "scheduled_delivery",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.scheduled_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.actual_delivery
|
||||
? [
|
||||
{
|
||||
key: "actual_delivery",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.actual_delivery")}: </label>
|
||||
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_invoiced
|
||||
? [
|
||||
{
|
||||
key: "date_invoiced",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_invoiced")}: </label>
|
||||
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.date_exported
|
||||
? [
|
||||
{
|
||||
key: "date_exported",
|
||||
label: (
|
||||
<>
|
||||
<label>{t("jobs.fields.date_exported")}: </label>
|
||||
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>) : null}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,94 +1,95 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Col, Divider, Row, Skeleton, Space, Timeline, Typography} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Link} from "react-router-dom";
|
||||
import {GET_JOB_LINE_ORDERS} from "../../graphql/jobs.queries";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import {DateFormatter} from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
export default function JobLinesExpander({ jobline, jobid }) {
|
||||
const { t } = useTranslation();
|
||||
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
joblineid: jobline.id,
|
||||
},
|
||||
});
|
||||
export default function JobLinesExpander({jobline, jobid}) {
|
||||
const {t} = useTranslation();
|
||||
const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
joblineid: jobline.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (loading) return <Skeleton />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (loading) return <Skeleton/>;
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col md={24} lg={12}>
|
||||
<Typography.Title level={4}>
|
||||
{t("parts_orders.labels.parts_orders")}
|
||||
</Typography.Title>
|
||||
<Timeline>
|
||||
{data.parts_order_lines.length > 0 ? (
|
||||
data.parts_order_lines.map((line) => (
|
||||
<Timeline.Item key={line.id}>
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link
|
||||
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
|
||||
>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
{line.parts_order.vendor.name}
|
||||
</Space>
|
||||
</Timeline.Item>
|
||||
))
|
||||
) : (
|
||||
<Timeline.Item>
|
||||
{t("parts_orders.labels.notyetordered")}
|
||||
</Timeline.Item>
|
||||
)}
|
||||
</Timeline>
|
||||
</Col>
|
||||
<Col md={24} lg={12}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<Timeline>
|
||||
{data.billlines.length > 0 ? (
|
||||
data.billlines.map((line) => (
|
||||
<Timeline.Item key={line.id}>
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link
|
||||
to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}
|
||||
>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_price")}: `}
|
||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_cost")}: `}
|
||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
</Timeline.Item>
|
||||
))
|
||||
) : (
|
||||
<Timeline.Item>
|
||||
{t("parts_orders.labels.notyetordered")}
|
||||
</Timeline.Item>
|
||||
)}
|
||||
</Timeline>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
return (
|
||||
<Row>
|
||||
<Col md={24} lg={12}>
|
||||
<Typography.Title level={4}>
|
||||
{t("parts_orders.labels.parts_orders")}
|
||||
</Typography.Title>
|
||||
<Timeline
|
||||
items={
|
||||
data.parts_order_lines.length > 0
|
||||
? data.parts_order_lines.map((line) => ({
|
||||
key: line.id,
|
||||
label: (
|
||||
<Space split={<Divider type="vertical"/>} wrap>
|
||||
<Link
|
||||
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
|
||||
>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
{line.parts_order.vendor.name}
|
||||
</Space>
|
||||
),
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
label: t("parts_orders.labels.notyetordered"),
|
||||
},
|
||||
]
|
||||
}
|
||||
/> </Col>
|
||||
<Col md={24} lg={12}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<Timeline
|
||||
items={
|
||||
data.billlines.length > 0
|
||||
? data.billlines.map((line) => ({
|
||||
key: line.id,
|
||||
label: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link
|
||||
to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}
|
||||
>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>{`${t("billlines.fields.actual_price")}: `}<CurrencyFormatter>{line.actual_price}</CurrencyFormatter></span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>{`${t("billlines.fields.actual_cost")}: `}<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter></span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
),
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
label: t("parts_orders.labels.notyetordered"),
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -411,14 +411,17 @@ export function JobLinesComponent({
|
||||
};
|
||||
|
||||
const markMenu = (
|
||||
<Menu onClick={handleMark}>
|
||||
<Menu.Item key="PAA">{t("joblines.fields.part_types.PAA")}</Menu.Item>
|
||||
<Menu.Item key="PAN">{t("joblines.fields.part_types.PAN")}</Menu.Item>
|
||||
<Menu.Item key="PAL">{t("joblines.fields.part_types.PAL")}</Menu.Item>
|
||||
<Menu.Item key="PAS">{t("joblines.fields.part_types.PAS")}</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item key="clear">{t("general.labels.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
<Menu
|
||||
onClick={handleMark}
|
||||
items={[
|
||||
{ key: "PAA", label: t("joblines.fields.part_types.PAA") },
|
||||
{ key: "PAN", label: t("joblines.fields.part_types.PAN") },
|
||||
{ key: "PAL", label: t("joblines.fields.part_types.PAL") },
|
||||
{ key: "PAS", label: t("joblines.fields.part_types.PAS") },
|
||||
{ key: "divider", label: <hr />, disabled: true },
|
||||
{ key: "clear", label: t("general.labels.clear") },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -549,7 +552,7 @@ export function JobLinesComponent({
|
||||
>
|
||||
<FilterFilled /> {t("jobs.actions.filterpartsonly")}
|
||||
</Button>
|
||||
<Dropdown overlay={markMenu} trigger={["click"]}>
|
||||
<Dropdown menu={markMenu} trigger={["click"]}>
|
||||
<Button>{t("jobs.actions.mark")}</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
|
||||
@@ -57,7 +57,7 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
|
||||
<Select
|
||||
autoFocus
|
||||
allowClear
|
||||
dropdownMatchSelectWidth={100}
|
||||
popupMatchSelectWidth={100}
|
||||
value={location}
|
||||
onClear={() => setLocation(null)}
|
||||
onSelect={handleChange}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
|
||||
<Select
|
||||
autoFocus
|
||||
allowClear
|
||||
dropdownMatchSelectWidth={100}
|
||||
popupMatchSelectWidth={100}
|
||||
value={status}
|
||||
onSelect={handleChange}
|
||||
onBlur={handleSave}
|
||||
|
||||
@@ -22,29 +22,25 @@ export function JoblinePresetButton({ bodyshop, form }) {
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<Menu
|
||||
style={{
|
||||
columnCount: Math.max(
|
||||
Math.floor(bodyshop.md_jobline_presets.length / 15),
|
||||
1
|
||||
),
|
||||
}}
|
||||
>
|
||||
{bodyshop.md_jobline_presets.map((i, idx) => (
|
||||
<Menu.Item
|
||||
onClick={() => handleSelect(i)}
|
||||
key={idx}
|
||||
style={{ breakInside: "avoid" }}
|
||||
>
|
||||
{i.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
style={{
|
||||
columnCount: Math.max(
|
||||
Math.floor(bodyshop.md_jobline_presets.length / 15),
|
||||
1
|
||||
),
|
||||
}}
|
||||
items={bodyshop.md_jobline_presets.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: i.label,
|
||||
style: { breakInside: "avoid" },
|
||||
onClick: () => handleSelect(i),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown trigger={["click"]} overlay={menu}>
|
||||
<Dropdown trigger={["click"]} menu={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href="# "
|
||||
|
||||
@@ -45,15 +45,15 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
||||
onClick={(e) => {
|
||||
updateJobStatus(e.key);
|
||||
}}
|
||||
>
|
||||
{bodyshop.md_ro_statuses.statuses.map((item) => (
|
||||
<Menu.Item key={item}>{item}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
items={bodyshop.md_ro_statuses.statuses.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
|
||||
<Dropdown menu={statusmenu} trigger={["click"]} key="changestatus">
|
||||
<Button shape="round">
|
||||
<span>{job.status}</span>
|
||||
|
||||
|
||||
@@ -86,24 +86,27 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
onClick={(e) => {
|
||||
updateJobStatus(e.key);
|
||||
}}
|
||||
>
|
||||
{availableStatuses.map((item) => (
|
||||
<Menu.Item key={item}>{item}</Menu.Item>
|
||||
))}
|
||||
{job.converted && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
{otherStages.map((item, idx) => (
|
||||
<Menu.Item key={item}>{item}</Menu.Item>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
items={[
|
||||
...availableStatuses.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
})),
|
||||
...(job.converted
|
||||
? [
|
||||
{ key: "divider", label: <hr />, disabled: true },
|
||||
...otherStages.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
})),
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={statusmenu}
|
||||
menu={statusmenu}
|
||||
trigger={["click"]}
|
||||
key="changestatus"
|
||||
disabled={jobRO || !job.converted}
|
||||
|
||||
@@ -63,17 +63,18 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
};
|
||||
|
||||
const overlay = (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
||||
<Menu onClick={handleMenuClick}>
|
||||
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
|
||||
<Menu.Item disabled={disabled} key={mapping.name}>
|
||||
{mapping.name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
onClick={handleMenuClick}
|
||||
items={bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ({
|
||||
key: mapping.name,
|
||||
label: mapping.name,
|
||||
disabled: disabled,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? (
|
||||
<Dropdown overlay={overlay}>
|
||||
<Dropdown menu={overlay}>
|
||||
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
|
||||
</Dropdown>
|
||||
) : (
|
||||
|
||||
@@ -16,19 +16,18 @@ export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) {
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_estimators.map((est, idx) => (
|
||||
<Menu.Item value={est} key={idx}>
|
||||
{`${est.est_ct_fn} ${est.est_ct_ln}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
<Menu
|
||||
onClick={handleClick}
|
||||
items={bodyshop.md_estimators.map((est, idx) => ({
|
||||
key: idx,
|
||||
label: `${est.est_ct_fn} ${est.est_ct_ln}`,
|
||||
value: est,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<Dropdown menu={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -21,17 +21,17 @@ export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
|
||||
style={{
|
||||
columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1,
|
||||
}}
|
||||
>
|
||||
{bodyshop.md_filehandlers.map((est, idx) => (
|
||||
<Menu.Item value={est} key={idx} style={{ breakInside: "avoid" }}>
|
||||
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
items={bodyshop.md_filehandlers.map((est, idx) => ({
|
||||
key: idx,
|
||||
label: `${est.ins_ct_fn} ${est.ins_ct_ln}`,
|
||||
value: est,
|
||||
style: { breakInside: "avoid" },
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<Dropdown menu={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -560,7 +560,7 @@ export function JobsDetailHeaderActions({
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
|
||||
<Dropdown menu={statusmenu} trigger={["click"]} key="changestatus">
|
||||
<Button>
|
||||
<span>{t("general.labels.actions")}</span>
|
||||
|
||||
|
||||
@@ -19,19 +19,18 @@ export function JobsDetailRatesChangeButton({ disabled, form, bodyshop }) {
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_labor_rates.map((rate, idx) => (
|
||||
<Menu.Item value={rate} key={idx}>
|
||||
{rate.rate_label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
<Menu
|
||||
onClick={handleClick}
|
||||
items={bodyshop.md_labor_rates.map((rate, idx) => ({
|
||||
key: idx,
|
||||
label: rate.rate_label,
|
||||
value: rate,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<Dropdown menu={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -26,22 +26,18 @@ export function NotesPresetButton({ bodyshop, form }) {
|
||||
style={{
|
||||
columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1,
|
||||
}}
|
||||
>
|
||||
{bodyshop.md_notes_presets.map((i, idx) => (
|
||||
<Menu.Item
|
||||
onClick={() => handleSelect(i)}
|
||||
key={idx}
|
||||
style={{ breakInside: "avoid" }}
|
||||
>
|
||||
{i.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
items={bodyshop.md_notes_presets.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: i.label,
|
||||
style: { breakInside: "avoid" },
|
||||
onClick: () => handleSelect(i),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown trigger={["click"]} overlay={menu}>
|
||||
<Dropdown trigger={["click"]} menu={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href="# "
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function PartsOrderModalPriceChange({ form, field }) {
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Dropdown overlay={menu} trigger="click">
|
||||
<Dropdown menu={menu} trigger="click">
|
||||
<Space>
|
||||
%
|
||||
<DownOutlined />
|
||||
|
||||
@@ -40,15 +40,14 @@ export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, i
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_parts_order_comment.map((comment, idx) => (
|
||||
<Menu.Item value={comment.comment} key={idx}>
|
||||
{comment.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
<Menu
|
||||
onClick={handleClick}
|
||||
items={bodyshop.md_parts_order_comment.map((comment, idx) => ({
|
||||
key: idx,
|
||||
label: comment.label,
|
||||
value: comment.comment,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -286,7 +285,7 @@ export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, i
|
||||
label={
|
||||
<Space>
|
||||
{t("parts_orders.fields.comments")}
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -53,19 +53,18 @@ export function ProductionColumnsComponent({
|
||||
style={{
|
||||
columnCount: Math.max(Math.floor(cols.length / 10), 1),
|
||||
}}
|
||||
>
|
||||
{cols
|
||||
items={cols
|
||||
.filter((i) => !columnKeys.includes(i.key))
|
||||
.map((item) => (
|
||||
<Menu.Item key={item.key} style={{ breakInside: "avoid" }}>
|
||||
{item.title}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.title,
|
||||
style: { breakInside: "avoid" },
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<Button>{t("production.actions.addcolumns")}</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -53,14 +53,18 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item key="toggleAlert" onClick={handleAlertToggle}>
|
||||
{record.production_vars && record.production_vars.alert
|
||||
? t("production.labels.alertoff")
|
||||
: t("production.labels.alerton")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
key: "toggleAlert",
|
||||
label: record.production_vars && record.production_vars.alert
|
||||
? t("production.labels.alertoff")
|
||||
: t("production.labels.alerton"),
|
||||
onClick: handleAlertToggle,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={["contextMenu"]}
|
||||
>
|
||||
|
||||
@@ -31,24 +31,28 @@ export default function ProductionListColumnBodyPriority({ record }) {
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu onClick={handleSetBodyPriority}>
|
||||
<Menu.Item key="clearBodyPriority">
|
||||
{t("production.actions.bodypriority-clear")}
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
key="set"
|
||||
title={t("production.actions.bodypriority-set")}
|
||||
>
|
||||
{new Array(15).fill().map((value, index) => (
|
||||
<Menu.Item key={index + 1}>
|
||||
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
|
||||
{index + 1}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
onClick={handleSetBodyPriority}
|
||||
items={[
|
||||
{
|
||||
key: "clearBodyPriority",
|
||||
label: t("production.actions.bodypriority-clear"),
|
||||
},
|
||||
{
|
||||
key: "set",
|
||||
label: t("production.actions.bodypriority-set"),
|
||||
children: new Array(15).fill().map((value, index) => ({
|
||||
key: index + 1,
|
||||
label: (
|
||||
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
|
||||
{index + 1}
|
||||
</div>
|
||||
),
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function ProductionListDate({
|
||||
(dayjs().add(1, "day").isSame(dayjs(record[field]), "day") &&
|
||||
"production-completion-soon"));
|
||||
}
|
||||
|
||||
// TODO - Client Update = Why is the overlay a card?
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
|
||||
@@ -31,24 +31,28 @@ export default function ProductionListColumnDetailPriority({ record }) {
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu onClick={handleSetDetailPriority}>
|
||||
<Menu.Item key="clearDetailPriority">
|
||||
{t("production.actions.detailpriority-clear")}
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
key="set"
|
||||
title={t("production.actions.detailpriority-set")}
|
||||
>
|
||||
{new Array(15).fill().map((value, index) => (
|
||||
<Menu.Item key={index + 1}>
|
||||
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
|
||||
{index + 1}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
onClick={handleSetDetailPriority}
|
||||
items={[
|
||||
{
|
||||
key: "clearDetailPriority",
|
||||
label: t("production.actions.detailpriority-clear"),
|
||||
},
|
||||
{
|
||||
key: "set",
|
||||
label: t("production.actions.detailpriority-set"),
|
||||
children: new Array(15).fill().map((value, index) => ({
|
||||
key: index + 1,
|
||||
label: (
|
||||
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
|
||||
{index + 1}
|
||||
</div>
|
||||
),
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
>
|
||||
|
||||
@@ -89,6 +89,7 @@ export function ProductionLastContacted({ currentUser, record }) {
|
||||
}
|
||||
}, [visible, form, record.date_last_contacted]);
|
||||
|
||||
// TODO - Client Update - Why is this a card?
|
||||
return (
|
||||
<div>
|
||||
<Dropdown
|
||||
|
||||
@@ -31,24 +31,28 @@ export default function ProductionListColumnPaintPriority({ record }) {
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu onClick={handleSetPaintPriority}>
|
||||
<Menu.Item key="clearPaintPriority">
|
||||
{t("production.actions.paintpriority-clear")}
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
key="set"
|
||||
title={t("production.actions.paintpriority-set")}
|
||||
>
|
||||
{new Array(15).fill().map((value, index) => (
|
||||
<Menu.Item key={index + 1}>
|
||||
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
|
||||
{index + 1}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
onClick={handleSetPaintPriority}
|
||||
items={[
|
||||
{
|
||||
key: "clearPaintPriority",
|
||||
label: t("production.actions.paintpriority-clear"),
|
||||
},
|
||||
{
|
||||
key: "set",
|
||||
label: t("production.actions.paintpriority-set"),
|
||||
children: new Array(15).fill().map((value, index) => ({
|
||||
key: index + 1,
|
||||
label: (
|
||||
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
|
||||
{index + 1}
|
||||
</div>
|
||||
),
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
>
|
||||
|
||||
@@ -38,15 +38,15 @@ export function ProductionListColumnCategory({ record, bodyshop }) {
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
menu={
|
||||
<Menu
|
||||
style={{ maxHeight: "200px", overflowY: "auto" }}
|
||||
onClick={handleSetStatus}
|
||||
>
|
||||
{bodyshop.md_categories.map((item) => (
|
||||
<Menu.Item key={item}>{item}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
items={bodyshop.md_categories.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
}))}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
>
|
||||
|
||||
@@ -47,15 +47,15 @@ export function ProductionListColumnStatus({
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
menu={
|
||||
<Menu
|
||||
style={{ maxHeight: "200px", overflowY: "auto" }}
|
||||
onClick={handleSetStatus}
|
||||
>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
||||
<Menu.Item key={item}>{item}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
items={bodyshop.md_ro_statuses.production_statuses.map((item) => ({
|
||||
key: item,
|
||||
label: item,
|
||||
}))}
|
||||
/>
|
||||
}
|
||||
trigger={["click"]}
|
||||
>
|
||||
|
||||
@@ -31,99 +31,53 @@ export function ProductionListPrint({ bodyshop }) {
|
||||
return (
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
overlay={
|
||||
<Menu>
|
||||
{Object.keys(ProdTemplates).map((key) => (
|
||||
<Menu.Item
|
||||
key={key}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
menu={
|
||||
<Menu
|
||||
onClick={async (e) => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: ProdTemplates[key].key,
|
||||
name: ProdTemplates[e.key].key,
|
||||
// variables: { id: contract.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{ProdTemplates[key].title}
|
||||
</Menu.Item>
|
||||
))}
|
||||
<Menu.SubMenu
|
||||
title={t("reportcenter.templates.production_by_technician_one")}
|
||||
>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.active)
|
||||
.map((e) => (
|
||||
<Menu.Item
|
||||
key={e.id}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: production_by_technician_one.key,
|
||||
variables: { id: e.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{e.first_name} {e.last_name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
title={t("reportcenter.templates.production_by_category_one")}
|
||||
>
|
||||
{bodyshop.md_categories.map((e) => (
|
||||
<Menu.Item
|
||||
key={e}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: production_by_category_one.key,
|
||||
variables: { category: e },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{e}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
title={t("reportcenter.templates.production_by_repair_status_one")}
|
||||
>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((e) => (
|
||||
<Menu.Item
|
||||
key={e}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: production_by_repair_status_one.key,
|
||||
variables: { status: e },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{e}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
items={[
|
||||
...Object.keys(ProdTemplates).map((key) => ({
|
||||
key: key,
|
||||
label: ProdTemplates[key].title,
|
||||
})),
|
||||
{
|
||||
key: "production_by_technician_one",
|
||||
label: t("reportcenter.templates.production_by_technician_one"),
|
||||
children: bodyshop.employees
|
||||
.filter((e) => e.active)
|
||||
.map((e) => ({
|
||||
key: e.id,
|
||||
label: `${e.first_name} ${e.last_name}`,
|
||||
})),
|
||||
},
|
||||
{
|
||||
key: "production_by_category_one",
|
||||
label: t("reportcenter.templates.production_by_category_one"),
|
||||
children: bodyshop.md_categories.map((e) => ({
|
||||
key: e,
|
||||
label: e,
|
||||
})),
|
||||
},
|
||||
{
|
||||
key: "production_by_repair_status_one",
|
||||
label: t("reportcenter.templates.production_by_repair_status_one"),
|
||||
children: bodyshop.md_ro_statuses.production_statuses.map((e) => ({
|
||||
key: e,
|
||||
label: e,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button loading={loading}>{t("general.labels.print")}</Button>
|
||||
|
||||
@@ -115,7 +115,7 @@ export function ProductionListTable({
|
||||
onSelect={handleSelect}
|
||||
placeholder={t("production.labels.selectview")}
|
||||
optionLabelProp="label"
|
||||
dropdownMatchSelectWidth={false}
|
||||
popupMatchSelectWidth={false}
|
||||
defaultValue={defaultView}
|
||||
>
|
||||
{bodyshop.production_config.map((config) => (
|
||||
|
||||
@@ -108,12 +108,16 @@ export function ProductionListTable({loading, data, refetch, bodyshop, technicia
|
||||
const headerItem = (col) => (
|
||||
<Dropdown
|
||||
className="prod-header-dropdown"
|
||||
overlay={
|
||||
<Menu onClick={removeColumn}>
|
||||
<Menu.Item key={col.key}>
|
||||
{t("production.actions.removecolumn")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
onClick={removeColumn}
|
||||
items={[
|
||||
{
|
||||
key: col.key,
|
||||
label: t("production.actions.removecolumn"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={["contextMenu"]}
|
||||
>
|
||||
|
||||
@@ -56,14 +56,20 @@ export function ScheduleBlockDay({
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<Menu onClick={handleMenu}>
|
||||
<Menu.Item key="block">{t("appointments.actions.block")}</Menu.Item>
|
||||
</Menu>
|
||||
<Menu
|
||||
onClick={handleMenu}
|
||||
items={[
|
||||
{
|
||||
key: "block",
|
||||
label: t("appointments.actions.block"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={menu}
|
||||
menu={menu}
|
||||
disabled={alreadyBlocked}
|
||||
trigger={["contextMenu"]}
|
||||
>
|
||||
|
||||
@@ -1,48 +1,45 @@
|
||||
import React from "react";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { Timeline } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import {Timeline} from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||
|
||||
export default function ScheduleExistingAppointmentsList({
|
||||
existingAppointments,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (existingAppointments.loading) return <LoadingSpinner />;
|
||||
if (existingAppointments.error)
|
||||
existingAppointments,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
if (existingAppointments.loading) return <LoadingSpinner/>;
|
||||
if (existingAppointments.error)
|
||||
return (
|
||||
<AlertComponent
|
||||
message={existingAppointments.error.message}
|
||||
type="error"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<AlertComponent
|
||||
message={existingAppointments.error.message}
|
||||
type="error"
|
||||
/>
|
||||
<div>
|
||||
{t("appointments.labels.priorappointments")}
|
||||
<Timeline
|
||||
items={
|
||||
existingAppointments.data
|
||||
? existingAppointments.data.appointments.map((item) => ({
|
||||
key: item.id,
|
||||
color: item.canceled ? "red" : item.arrived ? "green" : "blue",
|
||||
label: (
|
||||
<>
|
||||
{item.canceled
|
||||
? t("appointments.labels.cancelledappointment")
|
||||
: item.arrived
|
||||
? t("appointments.labels.arrivedon")
|
||||
: t("appointments.labels.scheduledfor")}
|
||||
<DateTimeFormatter>{item.start}</DateTimeFormatter>
|
||||
</>
|
||||
),
|
||||
}))
|
||||
: []
|
||||
}
|
||||
/></div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{t("appointments.labels.priorappointments")}
|
||||
<Timeline>
|
||||
{existingAppointments.data
|
||||
? existingAppointments.data.appointments.map((item) => {
|
||||
return (
|
||||
<Timeline.Item
|
||||
key={item.id}
|
||||
color={
|
||||
item.canceled ? "red" : item.arrived ? "green" : "blue"
|
||||
}
|
||||
>
|
||||
{item.canceled
|
||||
? t("appointments.labels.cancelledappointment")
|
||||
: item.arrived
|
||||
? t("appointments.labels.arrivedon")
|
||||
: t("appointments.labels.scheduledfor")}
|
||||
|
||||
<DateTimeFormatter>{item.start}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Timeline>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ export default function ScoreboardEntryEdit({ entry }) {
|
||||
</Card>
|
||||
);
|
||||
|
||||
// TODO Client Update, why is this a card
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown open={visible} overlay={popContent}>
|
||||
|
||||
@@ -28,13 +28,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
|
||||
|
||||
export function ShopInfoComponent({bodyshop, form, saveLoading}) {
|
||||
|
||||
const { treatments: {CriticalPartsScanning} } = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["CriticalPartsScanning"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
const {treatments: {CriticalPartsScanning}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["CriticalPartsScanning"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const {t} = useTranslation();
|
||||
const {t} = useTranslation();
|
||||
const history = useNavigate();
|
||||
const location = useLocation();
|
||||
const search = queryString.parse(location.search);
|
||||
@@ -58,47 +58,63 @@ export function ShopInfoComponent({bodyshop, form, saveLoading}) {
|
||||
search: `?tab=${search.tab}&subtab=${key}`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
|
||||
<ShopInfoGeneral form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="speedprint" tab={t("bodyshop.labels.speedprint")}>
|
||||
<ShopInfoSpeedPrint form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="rbac" tab={t("bodyshop.labels.rbac")}>
|
||||
<ShopInfoRbacComponent form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="roStatus" tab={t("bodyshop.labels.jobstatuses")}>
|
||||
<ShopInfoROStatusComponent form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="scheduling" tab={t("bodyshop.labels.scheduling")}>
|
||||
<ShopInfoSchedulingComponent form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
key="orderStatus"
|
||||
tab={t("bodyshop.labels.orderstatuses")}
|
||||
>
|
||||
<ShopInfoOrderStatusComponent form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
key="responsibilityCenters"
|
||||
tab={t("bodyshop.labels.responsibilitycenters.title")}
|
||||
>
|
||||
<ShopInfoResponsibilityCenterComponent form={form}/>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}>
|
||||
<ShopInfoIntakeChecklistComponent form={form}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}>
|
||||
<ShopInfoLaborRates form={form}/>
|
||||
</Tabs.TabPane>
|
||||
{CriticalPartsScanning.treatment === "on" && (
|
||||
<Tabs.TabPane key="partsscan" tab={t("bodyshop.labels.partsscan")}>
|
||||
<ShopInfoPartsScan form={form}/>
|
||||
</Tabs.TabPane>
|
||||
)}
|
||||
</Tabs>
|
||||
items={[
|
||||
{
|
||||
key: "general",
|
||||
tab: t("bodyshop.labels.shopinfo"),
|
||||
children: <ShopInfoGeneral form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "speedprint",
|
||||
tab: t("bodyshop.labels.speedprint"),
|
||||
children: <ShopInfoSpeedPrint form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "rbac",
|
||||
tab: t("bodyshop.labels.rbac"),
|
||||
children: <ShopInfoRbacComponent form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "roStatus",
|
||||
tab: t("bodyshop.labels.jobstatuses"),
|
||||
children: <ShopInfoROStatusComponent form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "scheduling",
|
||||
tab: t("bodyshop.labels.scheduling"),
|
||||
children: <ShopInfoSchedulingComponent form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "orderStatus",
|
||||
tab: t("bodyshop.labels.orderstatuses"),
|
||||
children: <ShopInfoOrderStatusComponent form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "responsibilityCenters",
|
||||
tab: t("bodyshop.labels.responsibilitycenters.title"),
|
||||
children: <ShopInfoResponsibilityCenterComponent form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "checklists",
|
||||
tab: t("bodyshop.labels.checklists"),
|
||||
children: <ShopInfoIntakeChecklistComponent form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "laborrates",
|
||||
tab: t("bodyshop.labels.laborrates"),
|
||||
children: <ShopInfoLaborRates form={form}/>,
|
||||
},
|
||||
...(CriticalPartsScanning.treatment === "on"
|
||||
? [
|
||||
{
|
||||
key: "partsscan",
|
||||
tab: t("bodyshop.labels.partsscan"),
|
||||
children: <ShopInfoPartsScan form={form}/>,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,19 +69,27 @@ export function ShopTemplateAddComponent({
|
||||
};
|
||||
const TemplateListGenerated = TemplateList();
|
||||
const menu = (
|
||||
<Menu onClick={handleAdd}>
|
||||
{availableTemplateKeys.length > 0 ? (
|
||||
availableTemplateKeys.map((tkey) => (
|
||||
<Menu.Item key={tkey}>{TemplateListGenerated[tkey].title}</Menu.Item>
|
||||
))
|
||||
) : (
|
||||
<div>{t("bodyshop.labels.notemplatesavailable")}</div>
|
||||
)}
|
||||
</Menu>
|
||||
<Menu
|
||||
onClick={handleAdd}
|
||||
items={
|
||||
availableTemplateKeys.length > 0
|
||||
? availableTemplateKeys.map((tkey) => ({
|
||||
key: tkey,
|
||||
label: TemplateListGenerated[tkey].title,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-templates",
|
||||
label: t("bodyshop.labels.notemplatesavailable"),
|
||||
disabled: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<span>
|
||||
{t("bodyshop.actions.addtemplate")} <DownOutlined />
|
||||
</span>
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function ShopTemplatesListContainer({ visibleState }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
(<Drawer
|
||||
placement="left"
|
||||
width="25%"
|
||||
open={visible}
|
||||
@@ -64,7 +64,7 @@ export default function ShopTemplatesListContainer({ visibleState }) {
|
||||
]}
|
||||
>
|
||||
<Skeleton title={false} loading={item.loading} active>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div rootStyle={{ display: "flex", flexDirection: "column" }}>
|
||||
<div>{TemplateList()[item.name].title}</div>
|
||||
<div>{TemplateList()[item.name].description}</div>
|
||||
<div>{TemplateList()[item.name].drivingid}</div>
|
||||
@@ -74,6 +74,6 @@ export default function ShopTemplatesListContainer({ visibleState }) {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</Drawer>)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { PrinterFilled } from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Divider, Drawer, Grid, Tabs } from "antd";
|
||||
import {PrinterFilled} from "@ant-design/icons";
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Button, Divider, Drawer, Grid, Tabs} from "antd";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {GET_JOB_BY_PK} from "../../graphql/jobs.queries";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobLinesContainer from "../job-detail-lines/job-lines.container";
|
||||
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component";
|
||||
@@ -19,11 +19,11 @@ import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-docu
|
||||
import JobNotesContainer from "../jobs-notes/jobs-notes.container";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop });
|
||||
const mapStateToProps = createStructuredSelector({bodyshop: selectBodyshop});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "printCenter"})),
|
||||
});
|
||||
|
||||
// const colBreakPoints = {
|
||||
@@ -35,105 +35,118 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
// },
|
||||
// };
|
||||
|
||||
export function TechLookupJobsDrawer({ bodyshop, setPrintCenterContext }) {
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
export function TechLookupJobsDrawer({bodyshop, setPrintCenterContext}) {
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
|
||||
const bpoints = {
|
||||
xs: "100%",
|
||||
sm: "100%",
|
||||
md: "100%",
|
||||
lg: "100%",
|
||||
xl: "90%",
|
||||
xxl: "85%",
|
||||
};
|
||||
const drawerPercentage = selectedBreakpoint
|
||||
? bpoints[selectedBreakpoint[0]]
|
||||
: "100%";
|
||||
const bpoints = {
|
||||
xs: "100%",
|
||||
sm: "100%",
|
||||
md: "100%",
|
||||
lg: "100%",
|
||||
xl: "90%",
|
||||
xxl: "85%",
|
||||
};
|
||||
const drawerPercentage = selectedBreakpoint
|
||||
? bpoints[selectedBreakpoint[0]]
|
||||
: "100%";
|
||||
|
||||
const searchParams =queryString.parse(useLocation().search);
|
||||
const { selected } = searchParams;
|
||||
const history = useNavigate();
|
||||
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
||||
variables: { id: selected },
|
||||
skip: !selected,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const handleDrawerClose = () => {
|
||||
delete searchParams.selected;
|
||||
history({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
}),
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const {selected} = searchParams;
|
||||
const history = useNavigate();
|
||||
const {loading, error, data, refetch} = useQuery(GET_JOB_BY_PK, {
|
||||
variables: {id: selected},
|
||||
skip: !selected,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={!!selected}
|
||||
destroyOnClose
|
||||
width={drawerPercentage}
|
||||
placement="right"
|
||||
onClose={handleDrawerClose}
|
||||
>
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{data ? (
|
||||
<PageHeader
|
||||
onBack={() => window.history.back()}
|
||||
title={data.jobs_by_pk.ro_number || t("general.labels.na")}
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
id: data.jobs_by_pk.id,
|
||||
job: data.jobs_by_pk,
|
||||
type: "job",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
}
|
||||
const {t} = useTranslation();
|
||||
const handleDrawerClose = () => {
|
||||
delete searchParams.selected;
|
||||
history({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={!!selected}
|
||||
destroyOnClose
|
||||
width={drawerPercentage}
|
||||
placement="right"
|
||||
onClose={handleDrawerClose}
|
||||
>
|
||||
<JobsDetailHeader job={data.jobs_by_pk} disabled />
|
||||
<Divider type="horizontal" />
|
||||
<Tabs size="large">
|
||||
<Tabs.TabPane key="lines" tab={t("jobs.labels.lines")}>
|
||||
<JobLinesContainer
|
||||
jobId={selected}
|
||||
joblines={data.jobs_by_pk.joblines}
|
||||
job={data.jobs_by_pk}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="documents" tab={t("jobs.labels.documents")}>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={searchParams.selected} />
|
||||
)}
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="notes" tab={t("jobs.labels.notes")}>
|
||||
<JobNotesContainer jobId={searchParams.selected} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</PageHeader>
|
||||
) : null}
|
||||
</Drawer>
|
||||
);
|
||||
{loading ? <LoadingSpinner/> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error"/> : null}
|
||||
{data ? (
|
||||
<PageHeader
|
||||
onBack={() => window.history.back()}
|
||||
title={data.jobs_by_pk.ro_number || t("general.labels.na")}
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: {refetch: refetch},
|
||||
context: {
|
||||
id: data.jobs_by_pk.id,
|
||||
job: data.jobs_by_pk,
|
||||
type: "job",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PrinterFilled/>
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<JobsDetailHeader job={data.jobs_by_pk} disabled/>
|
||||
<Divider type="horizontal"/>
|
||||
<Tabs
|
||||
size="large"
|
||||
defaultActiveKey="lines"
|
||||
items={[
|
||||
{
|
||||
key: "lines",
|
||||
tab: t("jobs.labels.lines"),
|
||||
children: (
|
||||
<JobLinesContainer
|
||||
jobId={selected}
|
||||
joblines={data.jobs_by_pk.joblines}
|
||||
job={data.jobs_by_pk}
|
||||
refetch={refetch}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "documents",
|
||||
tab: t("jobs.labels.documents"),
|
||||
children: bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={searchParams.selected}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
tab: t("jobs.labels.notes"),
|
||||
children: <JobNotesContainer jobId={searchParams.selected}/>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</PageHeader>
|
||||
) : null}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TechLookupJobsDrawer);
|
||||
|
||||
@@ -40,50 +40,60 @@ export function TechSider({ technician, techLogout }) {
|
||||
collapsed={collapsed}
|
||||
onCollapse={onCollapse}
|
||||
>
|
||||
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
|
||||
<Menu.Item
|
||||
key="1"
|
||||
disabled={!!technician}
|
||||
icon={<Icon component={FiLogIn} />}
|
||||
>
|
||||
<Link to={`/tech/login`}>{t("menus.tech.login")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" disabled={!!!technician} icon={<SearchOutlined />}>
|
||||
<Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="3"
|
||||
disabled={!!!technician}
|
||||
icon={<Icon component={FaBusinessTime} />}
|
||||
>
|
||||
<Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="4"
|
||||
disabled={!!!technician}
|
||||
icon={<Icon component={MdTimer} />}
|
||||
>
|
||||
<Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="5" disabled={!!!technician} icon={<ScheduleOutlined />}>
|
||||
<Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="6"
|
||||
disabled={!!!technician}
|
||||
icon={<Icon component={BsKanban} />}
|
||||
>
|
||||
<Link to={`/tech/board`}> {t("menus.tech.productionboard")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="7"
|
||||
disabled={!!!technician}
|
||||
onClick={() => techLogout()}
|
||||
icon={<Icon component={FiLogOut} />}
|
||||
>
|
||||
{t("menus.tech.logout")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={["1"]}
|
||||
mode="inline"
|
||||
onClick={(e) => {
|
||||
if (e.key === "7") {
|
||||
techLogout();
|
||||
}
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: "1",
|
||||
icon: <Icon component={FiLogIn} />,
|
||||
disabled: !!technician,
|
||||
label: <Link to={`/tech/login`}>{t("menus.tech.login")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
icon: <SearchOutlined />,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
icon: <Icon component={FaBusinessTime} />,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
icon: <Icon component={MdTimer} />,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "5",
|
||||
icon: <ScheduleOutlined />,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "6",
|
||||
icon: <Icon component={BsKanban} />,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/board`}>{t("menus.tech.productionboard")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "7",
|
||||
icon: <Icon component={FiLogOut} />,
|
||||
disabled: !!!technician,
|
||||
label: t("menus.tech.logout"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const VendorSearchSelect = (
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
dropdownMatchSelectWidth={false}
|
||||
popupMatchSelectWidth={false}
|
||||
onChange={setOption}
|
||||
optionFilterProp="name"
|
||||
onSelect={onSelect}
|
||||
|
||||
@@ -122,9 +122,8 @@ class Header extends React.Component {
|
||||
mode={isMobile ? 'inline' : 'horizontal'}
|
||||
defaultSelectedKeys={['sub0']}
|
||||
theme="dark"
|
||||
>
|
||||
{navChildren}
|
||||
</Menu>
|
||||
items={navChildren}
|
||||
/>
|
||||
</TweenOne>
|
||||
</div>
|
||||
</TweenOne>
|
||||
|
||||
@@ -77,58 +77,33 @@ export function ContractDetailPage({
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycarcontract")
|
||||
.courtesy_car_contract.key,
|
||||
variables: { id: contract.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("contracts.actions.printcontract")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycarcontract")
|
||||
.courtesy_car_terms.key,
|
||||
variables: { id: contract.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t(
|
||||
"printcenter.courtesycarcontract.courtesy_car_terms"
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycarcontract")
|
||||
.courtesy_car_impound.key,
|
||||
variables: { id: contract.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t(
|
||||
"printcenter.courtesycarcontract.courtesy_car_impound"
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
menu={
|
||||
<Menu
|
||||
onClick={(e) => {
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycarcontract")[e.key].key,
|
||||
variables: { id: contract.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: "courtesy_car_contract",
|
||||
label: t("contracts.actions.printcontract"),
|
||||
},
|
||||
{
|
||||
key: "courtesy_car_terms",
|
||||
label: t("printcenter.courtesycarcontract.courtesy_car_terms"),
|
||||
},
|
||||
{
|
||||
key: "courtesy_car_impound",
|
||||
label: t("printcenter.courtesycarcontract.courtesy_car_impound"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button>{t("general.labels.print")}</Button>
|
||||
|
||||
@@ -254,135 +254,97 @@ export function JobsDetailPage({
|
||||
<JobsDetailHeader job={job} />
|
||||
<Divider type="horizontal" />
|
||||
<FormFieldsChanged form={form} />
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history({ search: `?tab=${key}` })}
|
||||
tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }}
|
||||
>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaShieldAlt} />
|
||||
{t("menus.jobsdetail.general")}
|
||||
</span>
|
||||
}
|
||||
key="general"
|
||||
>
|
||||
<JobsDetailGeneral job={job} form={form} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<BarsOutlined />
|
||||
{t("menus.jobsdetail.repairdata")}
|
||||
</span>
|
||||
}
|
||||
key="repairdata"
|
||||
>
|
||||
<JobsLinesContainer
|
||||
job={job}
|
||||
joblines={job.joblines}
|
||||
refetch={refetch}
|
||||
form={form}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<DollarCircleOutlined />
|
||||
{t("menus.jobsdetail.rates")}
|
||||
</span>
|
||||
}
|
||||
key="rates"
|
||||
>
|
||||
<JobsDetailRates job={job} form={form} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<DollarCircleOutlined />
|
||||
{t("menus.jobsdetail.totals")}
|
||||
</span>
|
||||
}
|
||||
key="totals"
|
||||
>
|
||||
<JobsDetailTotals job={job} refetch={refetch} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<ToolFilled />
|
||||
{t("menus.jobsdetail.partssublet")}
|
||||
</span>
|
||||
}
|
||||
key="partssublet"
|
||||
>
|
||||
<JobsDetailPliContainer job={job} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaHardHat} />
|
||||
{t("menus.jobsdetail.labor")}
|
||||
</span>
|
||||
}
|
||||
key="labor"
|
||||
>
|
||||
<JobsDetailLaborContainer job={job} jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<CalendarFilled />
|
||||
{t("menus.jobsdetail.dates")}
|
||||
</span>
|
||||
}
|
||||
key="dates"
|
||||
>
|
||||
<JobsDetailDatesComponent job={job} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<FileImageFilled />
|
||||
{t("jobs.labels.documents")}
|
||||
</span>
|
||||
}
|
||||
key="documents"
|
||||
>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery job={job} />
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={job.id} />
|
||||
)}
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaRegStickyNote} />
|
||||
{t("jobs.labels.notes")}
|
||||
</span>
|
||||
}
|
||||
key="notes"
|
||||
>
|
||||
<JobNotesContainer jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<HistoryOutlined />
|
||||
{t("jobs.labels.audit")}
|
||||
</span>
|
||||
}
|
||||
key="audit"
|
||||
>
|
||||
<JobAuditTrail jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history({ search: `?tab=${key}` })}
|
||||
tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }}
|
||||
items={[
|
||||
{
|
||||
key: "general",
|
||||
tab: (
|
||||
<span><Icon component={FaShieldAlt} />{t("menus.jobsdetail.general")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: <JobsDetailGeneral job={job} form={form} />,
|
||||
},
|
||||
{
|
||||
key: "repairdata",
|
||||
tab: (
|
||||
<span><BarsOutlined />{t("menus.jobsdetail.repairdata")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: (
|
||||
<JobsLinesContainer
|
||||
job={job}
|
||||
joblines={job.joblines}
|
||||
refetch={refetch}
|
||||
form={form}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "rates",
|
||||
tab: (
|
||||
<span><DollarCircleOutlined />{t("menus.jobsdetail.rates")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: <JobsDetailRates job={job} form={form} />,
|
||||
},
|
||||
{
|
||||
key: "totals",
|
||||
tab: (
|
||||
<span><DollarCircleOutlined />{t("menus.jobsdetail.totals")}</span>
|
||||
),
|
||||
children: <JobsDetailTotals job={job} refetch={refetch} />,
|
||||
},
|
||||
{
|
||||
key: "partssublet",
|
||||
tab: (
|
||||
<span><ToolFilled />{t("menus.jobsdetail.partssublet")}</span>
|
||||
),
|
||||
children: <JobsDetailPliContainer job={job} />,
|
||||
},
|
||||
{
|
||||
key: "labor",
|
||||
tab: (
|
||||
<span><Icon component={FaHardHat} />{t("menus.jobsdetail.labor")}</span>
|
||||
),
|
||||
children: <JobsDetailLaborContainer job={job} jobId={job.id} />,
|
||||
},
|
||||
{
|
||||
key: "dates",
|
||||
tab: (
|
||||
<span><CalendarFilled />{t("menus.jobsdetail.dates")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: <JobsDetailDatesComponent job={job} />,
|
||||
},
|
||||
{
|
||||
key: "documents",
|
||||
tab: (
|
||||
<span><FileImageFilled />{t("jobs.labels.documents")}</span>
|
||||
),
|
||||
children: bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery job={job} />
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={job.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
tab: (
|
||||
<span><Icon component={FaRegStickyNote} />{t("jobs.labels.notes")}</span>
|
||||
),
|
||||
children: <JobNotesContainer jobId={job.id} />,
|
||||
},
|
||||
{
|
||||
key: "audit",
|
||||
tab: (<span><HistoryOutlined />{t("jobs.labels.audit")}</span>
|
||||
),
|
||||
children: <JobAuditTrail jobId={job.id} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,19 +53,19 @@ export function PhonebookContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
: "100%";
|
||||
|
||||
return (
|
||||
<RbacWrapper action="phonebook:view">
|
||||
<PhonebookPage />
|
||||
<Drawer
|
||||
width={drawerPercentage}
|
||||
onClose={() => {
|
||||
delete search.phonebookentry;
|
||||
navigate({ search: queryString.stringify(search) });
|
||||
}}
|
||||
visible={phonebookentry}
|
||||
>
|
||||
<PhonebookFormContainer />
|
||||
</Drawer>
|
||||
</RbacWrapper>
|
||||
(<RbacWrapper action="phonebook:view">
|
||||
<PhonebookPage />
|
||||
<Drawer
|
||||
width={drawerPercentage}
|
||||
onClose={() => {
|
||||
delete search.phonebookentry;
|
||||
navigate({ search: queryString.stringify(search) });
|
||||
}}
|
||||
open={phonebookentry}
|
||||
>
|
||||
<PhonebookFormContainer />
|
||||
</Drawer>
|
||||
</RbacWrapper>)
|
||||
);
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(PhonebookContainer);
|
||||
@@ -66,52 +66,38 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
<FeatureWrapper featureName="scoreboard">
|
||||
<RbacWrapper action="scoreboard:view">
|
||||
<Tabs
|
||||
activeKey={tab || "sb"}
|
||||
destroyInactiveTabPane
|
||||
onChange={(key) => {
|
||||
searchParams.tab = key;
|
||||
history({
|
||||
search: queryString.stringify(searchParams),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaShieldAlt} />
|
||||
{t("scoreboard.labels.jobs")}
|
||||
</span>
|
||||
}
|
||||
activeKey={tab || "sb"}
|
||||
destroyInactiveTabPane
|
||||
key="sb"
|
||||
>
|
||||
<ScoreboardDisplay />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<FieldTimeOutlined />
|
||||
{t("scoreboard.labels.timeticketsemployee")}
|
||||
</span>
|
||||
}
|
||||
destroyInactiveTabPane
|
||||
key="tickets"
|
||||
>
|
||||
<ScoreboardTimeTickets />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<FieldTimeOutlined />
|
||||
{t("scoreboard.labels.allemployeetimetickets")}
|
||||
</span>
|
||||
}
|
||||
destroyInactiveTabPane
|
||||
key="ticketsstats"
|
||||
>
|
||||
<ScoreboardTimeTicketsStats />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
onChange={(key) => {
|
||||
searchParams.tab = key;
|
||||
history({
|
||||
search: queryString.stringify(searchParams),
|
||||
});
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: "sb",
|
||||
tab: (<span><Icon component={FaShieldAlt} />{t("scoreboard.labels.jobs")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: <ScoreboardDisplay />,
|
||||
},
|
||||
{
|
||||
key: "tickets",
|
||||
tab: (<span><FieldTimeOutlined />{t("scoreboard.labels.timeticketsemployee")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: <ScoreboardTimeTickets />,
|
||||
},
|
||||
{
|
||||
key: "ticketsstats",
|
||||
tab: (<span><FieldTimeOutlined />{t("scoreboard.labels.allemployeetimetickets")}</span>
|
||||
),
|
||||
forceRender: true,
|
||||
children: <ScoreboardTimeTicketsStats />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
</FeatureWrapper>
|
||||
);
|
||||
|
||||
@@ -26,19 +26,18 @@ export default function ShopVendorPageComponent() {
|
||||
: "100%";
|
||||
|
||||
return (
|
||||
<div>
|
||||
(<div>
|
||||
<VendorsListContainer />
|
||||
|
||||
<Drawer
|
||||
width={drawerPercentage}
|
||||
onClose={() => {
|
||||
searchParams.delete("selectedvendor");
|
||||
navigate({ search: searchParams.toString() });
|
||||
}}
|
||||
visible={selectedvendor}
|
||||
open={selectedvendor}
|
||||
>
|
||||
<VendorsFormContainer />
|
||||
</Drawer>
|
||||
</div>
|
||||
</div>)
|
||||
);
|
||||
}
|
||||
@@ -47,22 +47,31 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
return (
|
||||
<RbacWrapper action="shop:config">
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
activeKey={search.tab}
|
||||
onChange={(key) => history({ search: `?tab=${key}` })}
|
||||
>
|
||||
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info">
|
||||
<ShopInfoContainer />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t("bodyshop.labels.employees")} key="employees">
|
||||
<ShopEmployeesContainer />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t("bodyshop.labels.licensing")} key="licensing">
|
||||
<ShopInfoUsersComponent />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t("bodyshop.labels.csiq")} key="csiq">
|
||||
<ShopCsiConfig />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
tabs={[
|
||||
{
|
||||
key: "info",
|
||||
tab: t("bodyshop.labels.shopinfo"),
|
||||
children: <ShopInfoContainer />,
|
||||
},
|
||||
{
|
||||
key: "employees",
|
||||
tab: t("bodyshop.labels.employees"),
|
||||
children: <ShopEmployeesContainer />,
|
||||
},
|
||||
{
|
||||
key: "licensing",
|
||||
tab: t("bodyshop.labels.licensing"),
|
||||
children: <ShopInfoUsersComponent />,
|
||||
},
|
||||
{
|
||||
key: "csiq",
|
||||
tab: t("bodyshop.labels.csiq"),
|
||||
children: <ShopCsiConfig />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user