Allow for Component Token Overrides.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2023-12-28 17:33:21 -05:00
parent e5d8cc2bea
commit 79dce5d069
57 changed files with 1890 additions and 1861 deletions

View File

@@ -15,7 +15,7 @@ const BillLineSearchSelect = (
disabled={disabled} disabled={disabled}
ref={ref} ref={ref}
showSearch showSearch
dropdownMatchSelectWidth={false} popupMatchSelectWidth={false}
// optionFilterProp="line_desc" // optionFilterProp="line_desc"
filterOption={(inputValue, option) => { filterOption={(inputValue, option) => {
return ( return (

View File

@@ -1,55 +1,64 @@
import { HomeFilled } from "@ant-design/icons"; import {HomeFilled} from "@ant-design/icons";
import { Breadcrumb, Row, Col } from "antd"; import {Breadcrumb, Col, Row} from "antd";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { Link } from "react-router-dom"; import {Link} from "react-router-dom";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors"; import {selectBreadcrumbs} from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import GlobalSearchOs from "../global-search/global-search-os.component"; import GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss"; import "./breadcrumbs.styles.scss";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs, breadcrumbs: selectBreadcrumbs,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
export function BreadCrumbs({ breadcrumbs, bodyshop }) { export function BreadCrumbs({breadcrumbs, bodyshop}) {
const { treatments: {OpenSearch} } = useSplitTreatments({ const {treatments: {OpenSearch}} = useSplitTreatments({
attributes: {}, attributes: {},
names: ["OpenSearch"], names: ["OpenSearch"],
splitKey: bodyshop && bodyshop.imexshopid, splitKey: bodyshop && bodyshop.imexshopid,
}); });
// TODO - Client Update - Technically key is not doing anything here
return ( return (
<Row className="breadcrumb-container"> <Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}> <Col xs={24} sm={24} md={16}>
<Breadcrumb separator=">"> <Breadcrumb
<Breadcrumb.Item> separator=">"
<Link to={`/manage/`}> items={[
<HomeFilled />{" "} {
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || key: "home",
""} title: (
</Link> <Link to={`/manage/`}>
</Breadcrumb.Item> <HomeFilled/>{" "}
{breadcrumbs.map((item) => {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
item.link ? ( ""}
<Breadcrumb.Item key={item.label}> </Link>
<Link to={item.link}>{item.label} </Link> ),
</Breadcrumb.Item> },
) : ( ...breadcrumbs.map((item) =>
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item> item.link
) ? {
)} key: item.label,
</Breadcrumb> title: <Link to={item.link}>{item.label}</Link>,
</Col> }
<Col xs={24} sm={24} md={8}> : {
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />} key: item.label,
</Col> title: item.label,
</Row> }
); ),
]}
/>
</Col>
<Col xs={24} sm={24} md={8}>
{OpenSearch.treatment === "on" ? <GlobalSearchOs/> : <GlobalSearch/>}
</Col>
</Row>
);
} }
export default connect(mapStateToProps, null)(BreadCrumbs); export default connect(mapStateToProps, null)(BreadCrumbs);

View File

@@ -16,19 +16,19 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export function ChatPresetsComponent({ bodyshop, setMessage, className }) { 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 = ( const menu = (
<Menu> <Menu items={menuItems} />
{bodyshop.md_messaging_presets.map((i, idx) => (
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
{i.label}
</Menu.Item>
))}
</Menu>
); );
return ( return (
<div className={className}> <div className={className}>
<Dropdown trigger={["click"]} overlay={menu}> <Dropdown trigger={["click"]} menu={menu}>
<PlusCircleOutlined /> <PlusCircleOutlined />
</Dropdown> </Dropdown>
</div> </div>

View File

@@ -19,7 +19,7 @@ export default function ChatTagRoComponent({
<Select <Select
showSearch showSearch
autoFocus autoFocus
dropdownMatchSelectWidth popupMatchSelectWidth
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}
filterOption={false} filterOption={false}
onSearch={handleSearch} onSearch={handleSearch}

View File

@@ -18,20 +18,20 @@ export function ContractsRatesChangeButton({ disabled, form, bodyshop }) {
form.setFieldsValue(rate); form.setFieldsValue(rate);
}; };
const menuItems = bodyshop.md_ccc_rates.map((i, idx) => ({
key: idx,
label: i.label,
value: i,
}));
const menu = ( const menu = (
<div> <div>
<Menu onClick={handleClick}> <Menu onClick={handleClick} items={menuItems} />
{bodyshop.md_ccc_rates.map((rate, idx) => (
<Menu.Item value={rate} key={idx}>
{rate.label}
</Menu.Item>
))}
</Menu>
</div> </div>
); );
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -178,6 +178,25 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase()) (t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
) )
: courtesycars; : 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 ( return (
<Card <Card
title={t("menus.header.courtesycars")} title={t("menus.header.courtesycars")}
@@ -188,26 +207,8 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
</Button> </Button>
<Dropdown <Dropdown
trigger="click" trigger="click"
overlay={ menu={
<Menu> <Menu items={menuItems} />
<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>
} }
> >
<Button>{t("general.labels.print")}</Button> <Button>{t("general.labels.print")}</Button>

View File

@@ -123,18 +123,16 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
[data] [data]
); );
const existingLayoutKeys = state.items.map((i) => i.i); 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 = ( const addComponentOverlay = (
<Menu onClick={handleAddComponent}> <Menu onClick={handleAddComponent} items={menuItems} />
{Object.keys(componentList).map((key) => (
<Menu.Item
key={key}
value={key}
disabled={existingLayoutKeys.includes(key)}
>
{componentList[key].label}
</Menu.Item>
))}
</Menu>
); );
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
@@ -147,7 +145,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined />
</Button> </Button>
<Dropdown overlay={addComponentOverlay} trigger={["click"]}> <Dropdown menu={addComponentOverlay} trigger={["click"]}>
<Button>{t("dashboard.actions.addcomponent")}</Button> <Button>{t("dashboard.actions.addcomponent")}</Button>
</Dropdown> </Dropdown>
</Space> </Space>

View File

@@ -22,20 +22,22 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
export function DmsLogEvents({ socket, logs, bodyshop }) { export function DmsLogEvents({ socket, logs, bodyshop }) {
return ( return (
<Timeline pending <Timeline
reverse={true} pending
> reverse={true}
{logs.map((log, idx) => ( items={logs.map((log, idx) => ({
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}> key: idx,
color: LogLevelHierarchy(log.level),
label: (
<Space wrap align="start" style={{}}> <Space wrap align="start" style={{}}>
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag> <Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
<span>{dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span> <span>{dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span>
<Divider type="vertical" /> <Divider type="vertical" />
<span>{log.message}</span> <span>{log.message}</span>
</Space> </Space>
</Timeline.Item> ),
))} }))}
</Timeline> />
); );
} }

View File

@@ -1,27 +1,27 @@
import { DeleteFilled, DownOutlined } from "@ant-design/icons"; import {DeleteFilled, DownOutlined} from "@ant-design/icons";
import { import {
Button, Button,
Card, Card,
Divider, Divider,
Dropdown, Dropdown,
Form, Form,
Input, Input,
InputNumber, InputNumber,
Menu, Menu,
Select, Select,
Space, Space,
Statistic, Statistic,
Switch, Switch,
Typography, Typography,
} from "antd"; } from "antd";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { determineDmsType } from "../../pages/dms/dms.container"; import {determineDmsType} from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import i18n from "../../translations/i18n"; import i18n from "../../translations/i18n";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component"; import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component"; import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
@@ -30,440 +30,427 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm); export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
export function DmsPostForm({ bodyshop, socket, job, logsRef }) { export function DmsPostForm({bodyshop, socket, job, logsRef}) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const { t } = useTranslation(); const {t} = useTranslation();
const handlePayerSelect = (value, index) => { const handlePayerSelect = (value, index) => {
form.setFieldsValue({ form.setFieldsValue({
payers: form.getFieldValue("payers").map((payer, mapIndex) => { payers: form.getFieldValue("payers").map((payer, mapIndex) => {
if (index !== mapIndex) return payer; if (index !== mapIndex) return payer;
const cdkPayer = const cdkPayer =
bodyshop.cdk_configuration.payers && bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find((i) => i.name === value); bodyshop.cdk_configuration.payers.find((i) => i.name === value);
if (!cdkPayer) return payer; if (!cdkPayer) return payer;
return { return {
...cdkPayer, ...cdkPayer,
dms_acctnumber: cdkPayer.dms_acctnumber, dms_acctnumber: cdkPayer.dms_acctnumber,
controlnumber: job && job[cdkPayer.control_type], 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 ( const handleFinish = (values) => {
<Card title={t("jobs.labels.dms.postingform")}> socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
<Form jobid: job.id,
form={form} txEnvelope: values,
layout="vertical" });
onFinish={handleFinish} console.log(logsRef);
initialValues={{ if (logsRef) {
story: `${t("jobs.labels.dms.defaultstory", { console.log("executing", logsRef);
ro_number: job.ro_number, logsRef.curent &&
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ logsRef.current.scrollIntoView({
job.ownr_co_nm || "" behavior: "smooth",
}`.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>
{bodyshop.cdk_dealerid && ( return (
<div> <Card title={t("jobs.labels.dms.postingform")}>
<LayoutFormRow style={{ justifyContent: "center" }} grow> <Form
<Form.Item form={form}
name="dms_make" layout="vertical"
label={t("jobs.fields.dms.dms_make")} onFinish={handleFinish}
rules={[ initialValues={{
{ story: `${t("jobs.labels.dms.defaultstory", {
required: true, ro_number: job.ro_number,
}, ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
]} job.ownr_co_nm || ""
> }`.trim(),
<Input disabled /> ins_co_nm: job.ins_co_nm || "N/A",
</Form.Item> clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
<Form.Item job.po_number || ""
name="dms_model" }`,
label={t("jobs.fields.dms.dms_model")} }).trim()}.${
rules={[ job.area_of_damage && job.area_of_damage.impact1
{ ? " " +
required: true, t("jobs.labels.dms.damageto", {
}, area_of_damage:
]} (job.area_of_damage && job.area_of_damage.impact1) ||
> "UNKNOWN",
<Input disabled /> })
</Form.Item> : ""
<Form.Item }`.slice(0, 239),
name="inservicedate" inservicedate: dayjs("2019-01-01"),
label={t("jobs.fields.dms.inservicedate")} }}
> >
<FormDatePicker /> <LayoutFormRow grow>
</Form.Item> <Form.Item
</LayoutFormRow> name="journal"
<Space> label={t("jobs.fields.dms.journal")}
<DmsCdkMakes form={form} socket={socket} job={job} /> initialValue={
<DmsCdkMakesRefetch /> bodyshop.cdk_configuration &&
<Form.Item bodyshop.cdk_configuration.default_journal
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>
} }
key={`${index}controlnumber`}
name={[field.name, "controlnumber"]}
rules={[ rules={[
{ {
required: true, required: true,
}, //message: t("general.validation.required"),
},
]} ]}
> >
<Input /> <Input/>
</Form.Item> </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> {bodyshop.cdk_dealerid && (
{() => { <div>
const payers = form.getFieldValue("payers"); <LayoutFormRow style={{justifyContent: "center"}} grow>
<Form.Item
const row = payers && payers[index]; name="dms_make"
label={t("jobs.fields.dms.dms_make")}
const cdkPayer = rules={[
bodyshop.cdk_configuration.payers && {
bodyshop.cdk_configuration.payers.find( required: true,
(i) => i && row && i.name === row.name },
); ]}
if ( >
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`) <Input disabled/>
) </Form.Item>
return ( <Form.Item
<div> name="dms_model"
{cdkPayer && label={t("jobs.fields.dms.dms_model")}
t(`jobs.fields.${cdkPayer?.control_type}`)} rules={[
</div> {
); required: true,
else if ( },
i18n.exists( ]}
`jobs.fields.dms.control_type.${cdkPayer?.control_type}` >
) <Input disabled/>
) { </Form.Item>
return ( <Form.Item
<div> name="inservicedate"
{cdkPayer && label={t("jobs.fields.dms.inservicedate")}
t( >
`jobs.fields.dms.control_type.${cdkPayer?.control_type}` <FormDatePicker/>
)} </Form.Item>
</div> </LayoutFormRow>
); <Space>
} else { <DmsCdkMakes form={form} socket={socket} job={job}/>
return null; <DmsCdkMakesRefetch/>
} <Form.Item
}} name="dms_unsold"
</Form.Item> label={t("jobs.fields.dms.dms_unsold")}
initialValue={false}
<DeleteFilled >
onClick={() => { <Switch/>
remove(field.name); </Form.Item>
}} <Form.Item
/> name="dms_model_override"
</Space> label={t("jobs.fields.dms.dms_model_override")}
</Form.Item> initialValue={false}
))} >
<Form.Item> <Switch/>
<Button </Form.Item>
disabled={!(fields.length < 3)} </Space>
onClick={() => { </div>
if (fields.length < 3) add(); )}
}} <Form.Item
style={{ width: "100%" }} name="story"
> label={t("jobs.fields.dms.story")}
{t("jobs.actions.dms.addpayer")} rules={[
</Button> {
</Form.Item> required: true,
</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")} <Input.TextArea maxLength={240}/>
</Button> </Form.Item>
</Space>
); <Divider/>
}} <Space size="large" wrap align="center">
</Form.Item> <Statistic
</Form> title={t("jobs.fields.ded_amt")}
</Card> 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>
);
} }

View File

@@ -1,267 +1,269 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons"; import {UploadOutlined, UserAddOutlined} from "@ant-design/icons";
import { import {Button, Divider, Dropdown, Form, Input, Menu, Select, Space, Tabs, Upload,} from "antd";
Button,
Divider,
Dropdown,
Form,
Input,
Menu,
Select,
Space,
Tabs,
Upload,
} from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { selectEmailConfig } from "../../redux/email/email.selectors"; import {selectEmailConfig} from "../../redux/email/email.selectors";
import { import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
selectBodyshop, import {CreateExplorerLinkForJob} from "../../utils/localmedia";
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import EmailDocumentsComponent from "../email-documents/email-documents.component"; import EmailDocumentsComponent from "../email-documents/email-documents.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
emailConfig: selectEmailConfig, emailConfig: selectEmailConfig,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(EmailOverlayComponent); )(EmailOverlayComponent);
export function EmailOverlayComponent({ export function EmailOverlayComponent({
emailConfig, emailConfig,
form, form,
selectedMediaState, selectedMediaState,
bodyshop, bodyshop,
currentUser, currentUser,
}) { }) {
const { t } = useTranslation(); const {t} = useTranslation();
const handleClick = ({ item, key, keyPath }) => { const handleClick = ({item, key, keyPath}) => {
const email = item.props.value; const email = item.props.value;
form.setFieldsValue({ form.setFieldsValue({
to: _.uniq([ to: _.uniq([
...form.getFieldValue("to"), ...form.getFieldValue("to"),
...(typeof email === "string" ? [email] : email), ...(typeof email === "string" ? [email] : email),
]), ]),
}); });
}; };
const handle_CC_Click = ({ item, key, keyPath }) => { const handle_CC_Click = ({item, key, keyPath}) => {
const email = item.props.value; const email = item.props.value;
form.setFieldsValue({ form.setFieldsValue({
cc: _.uniq([ cc: _.uniq([
...(form.getFieldValue("cc") || ""), ...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email), ...(typeof email === "string" ? [email] : email),
]), ]),
}); });
}; };
const menu = ( const menu = (
<div> <Menu
<Menu onClick={handleClick}> onClick={handleClick}
{bodyshop.employees items={[
.filter((e) => e.user_email) ...bodyshop.employees
.map((e, idx) => ( .filter((e) => e.user_email)
<Menu.Item value={e.user_email} key={idx}> .map((e, idx) => ({
{`${e.first_name} ${e.last_name}`} key: idx,
</Menu.Item> label: `${e.first_name} ${e.last_name}`,
))} value: e.user_email,
{bodyshop.md_to_emails.map((e, idx) => ( })),
<Menu.Item value={e.emails} key={idx + "group"}> ...bodyshop.md_to_emails.map((e, idx) => ({
{e.label} key: idx + "group",
</Menu.Item> label: e.label,
))} value: e.emails,
</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") }}
/> />
); );
}}
</Form.Item>
<Tabs> const menuCC = (
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents"> <div>
<EmailDocumentsComponent <Menu
selectedMediaState={selectedMediaState} onClick={handle_CC_Click}
form={form} items={[
/> ...bodyshop.employees
</Tabs.TabPane> .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"> return (
{bodyshop.uselocalmediaserver && emailConfig.jobid && ( <div>
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}> <Form.Item
<Button>{t("documents.labels.openinexplorer")}</Button> label={t("emails.fields.from")}
</a> name="from"
)} rules={[
<Form.Item {
name="fileList" required: true,
valuePropName="fileList" //message: t("general.validation.required"),
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"
> >
<> <Select>
<p className="ant-upload-drag-icon"> <Select.Option key={currentUser.email}>
<UploadOutlined /> {currentUser.email}
</p> </Select.Option>
<p className="ant-upload-text"> <Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
Click or drag files to this area to upload. {bodyshop.md_from_emails &&
</p> bodyshop.md_from_emails.map((e) => (
</> <Select.Option key={e}>{e}</Select.Option>
</Upload.Dragger> ))}
</Form.Item> </Select>
</Tabs.TabPane> </Form.Item>
</Tabs> <Form.Item
</div> 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>
);
} }

View File

@@ -38,17 +38,20 @@ export function JobAltTransportChange({ bodyshop, job }) {
} }
}; };
const menu = ( const menu = (
<Menu selectedKeys={[job && job.alt_transport]} onClick={onClick}> <Menu
{bodyshop.appt_alt_transport && selectedKeys={[job && job.alt_transport]}
bodyshop.appt_alt_transport.map((alt) => ( onClick={onClick}
<Menu.Item key={alt}>{alt}</Menu.Item> items={[
))} ...(bodyshop.appt_alt_transport || []).map((alt) => ({
<Menu.Divider /> key: alt,
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item> label: alt,
</Menu> })),
{ key: "null", label: t("general.actions.clear") },
]}
/>
); );
return ( return (
<Dropdown overlay={menu}> <Dropdown menu={menu}>
<a href=" #" onClick={(e) => e.preventDefault()}> <a href=" #" onClick={(e) => e.preventDefault()}>
<DownOutlined /> <DownOutlined />
</a> </a>

View File

@@ -45,20 +45,23 @@ export function ScheduleEventColor({ bodyshop, event }) {
?.label; ?.label;
const menu = ( const menu = (
<Menu selectedKeys={[event.color]} onClick={onClick}> <Menu
{bodyshop.appt_colors && selectedKeys={[event.color]}
bodyshop.appt_colors.map((color) => ( onClick={onClick}
<Menu.Item style={{ color: color.color.hex }} key={color.color.hex}> items={[
{color.label} ...(bodyshop.appt_colors || []).map((color) => ({
</Menu.Item> key: color.color.hex,
))} label: color.label,
<Menu.Divider /> style: { color: color.color.hex },
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item> })),
</Menu> { key: "divider", label: <hr />, disabled: true },
{ key: "null", label: t("general.actions.clear") },
]}
/>
); );
return ( return (
<Dropdown overlay={menu}> <Dropdown menu={menu}>
<a href=" #" onClick={(e) => e.preventDefault()}> <a href=" #" onClick={(e) => e.preventDefault()}>
{selectedColor} {selectedColor}
<DownOutlined /> <DownOutlined />

View File

@@ -185,55 +185,57 @@ export function ScheduleEventComponent({
) : null} ) : null}
{event.job ? ( {event.job ? (
<Dropdown <Dropdown
overlay={ menu={
<Menu> <Menu
<Menu.Item items={[
onClick={() => { {
const Template = TemplateList("job").appointment_reminder; key: "email",
GenerateDocument( label: t("general.labels.email"),
{ disabled: event.arrived,
name: Template.key, onClick: () => {
variables: { id: event.job.id }, const Template = TemplateList("job").appointment_reminder;
}, GenerateDocument(
{ {
to: event.job && event.job.ownr_ea, name: Template.key,
subject: Template.subject, variables: { id: event.job.id },
}, },
"e", {
event.job && event.job.id to: event.job && event.job.ownr_ea,
); subject: Template.subject,
}} },
disabled={event.arrived} "e",
> event.job && event.job.id
{t("general.labels.email")} );
</Menu.Item> },
<Menu.Item },
onClick={() => { {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA"); key: "sms",
if (p && p.isValid()) { label: t("general.labels.sms"),
openChatByPhone({ disabled: event.arrived || !bodyshop.messagingservicesid,
phone_num: p.formatInternational(), onClick: () => {
jobid: event.job.id, const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
}); if (p && p.isValid()) {
setMessage( openChatByPhone({
t("appointments.labels.reminder", { phone_num: p.formatInternational(),
shopname: bodyshop.shopname, jobid: event.job.id,
date: dayjs(event.start).format("MM/DD/YYYY"), });
time: dayjs(event.start).format("HH:mm a"), setMessage(
}) t("appointments.labels.reminder", {
); shopname: bodyshop.shopname,
setVisible(false); date: dayjs(event.start).format("MM/DD/YYYY"),
} else { time: dayjs(event.start).format("HH:mm a"),
notification["error"]({ })
message: t("messaging.error.invalidphone"), );
}); setVisible(false);
} } else {
}} notification["error"]({
disabled={event.arrived || !bodyshop.messagingservicesid} message: t("messaging.error.invalidphone"),
> });
{t("general.labels.sms")} }
</Menu.Item> },
</Menu> },
]}
/>
} }
> >
<Button>{t("appointments.actions.sendreminder")}</Button> <Button>{t("appointments.actions.sendreminder")}</Button>

View File

@@ -1,119 +1,188 @@
import { Timeline } from "antd"; import {Timeline} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import {DateTimeFormatter} from "../../utils/DateFormatter";
import CardTemplate from "./job-detail-cards.template.component"; import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsDatesComponent({ loading, data }) { export default function JobDetailCardsDatesComponent({loading, data}) {
const { t } = useTranslation(); const {t} = useTranslation();
return ( return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.dates")}>
{data ? ( {data ? (
<Timeline> <Timeline
{!( items={[
data.actual_in || ...(data.date_last_contacted
data.scheduled_completion || ? [
data.scheduled_in || {
data.actual_completion || key: "date_last_contacted",
data.scheduled_delivery || label: (
data.actual_delivery || <>
data.date_estimated || <label>{t("jobs.fields.date_last_contacted")}: </label>
data.date_open || <DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
data.date_scheduled || </>
data.date_invoiced || ),
data.date_exported },
) ? ( ]
<div>{t("jobs.errors.nodates")}</div> : []),
) : null} ...(data.date_open
{data.date_last_contacted ? ( ? [
<Timeline.Item> {
<label>{t("jobs.fields.date_last_contacted")}: </label> key: "date_open",
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter> label: (
</Timeline.Item> <>
) : null} <label>{t("jobs.fields.date_open")}: </label>
{data.date_open ? ( <DateTimeFormatter>{data.date_open}</DateTimeFormatter>
<Timeline.Item> </>
<label>{t("jobs.fields.date_open")}: </label> ),
<DateTimeFormatter>{data.date_open}</DateTimeFormatter> },
</Timeline.Item> ]
) : null} : []),
...(data.date_estimated
{data.date_estimated ? ( ? [
<Timeline.Item> {
<label>{t("jobs.fields.date_estimated")}: </label> key: "date_estimated",
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter> label: (
</Timeline.Item> <>
) : null} <label>{t("jobs.fields.date_estimated")}: </label>
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
{data.date_scheduled ? ( </>
<Timeline.Item> ),
<label>{t("jobs.fields.date_scheduled")}: </label> },
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter> ]
</Timeline.Item> : []),
) : null} ...(data.date_scheduled
? [
{data.scheduled_in ? ( {
<Timeline.Item> key: "date_scheduled",
<label>{t("jobs.fields.scheduled_in")}: </label> label: (
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter> <>
</Timeline.Item> <label>{t("jobs.fields.date_scheduled")}: </label>
) : null} <DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
{data.actual_in ? ( </>
<Timeline.Item> ),
<label>{t("jobs.fields.actual_in")}: </label> },
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter> ]
</Timeline.Item> : []),
) : null} ...(data.scheduled_in
{data.date_repairstarted ? ( ? [
<Timeline.Item> {
<label>{t("jobs.fields.date_repairstarted")}: </label> key: "scheduled_in",
<DateTimeFormatter>{data.date_repairstarted}</DateTimeFormatter> label: (
</Timeline.Item> <>
) : null} <label>{t("jobs.fields.scheduled_in")}: </label>
{data.scheduled_completion ? ( <DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
<Timeline.Item> </>
<label>{t("jobs.fields.scheduled_completion")}: </label> ),
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter> },
</Timeline.Item> ]
) : null} : []),
...(data.actual_in
{data.actual_completion ? ( ? [
<Timeline.Item> {
<label>{t("jobs.fields.actual_completion")}: </label> key: "actual_in",
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter> label: (
</Timeline.Item> <>
) : null} <label>{t("jobs.fields.actual_in")}: </label>
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
{data.scheduled_delivery ? ( </>
<Timeline.Item> ),
<label>{t("jobs.fields.scheduled_delivery")}: </label> },
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter> ]
</Timeline.Item> : []),
) : null} ...(data.date_repairstarted
? [
{data.actual_delivery ? ( {
<Timeline.Item> key: "date_repairstarted",
<label>{t("jobs.fields.actual_delivery")}: </label> label: (
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter> <>
</Timeline.Item> <label>{t("jobs.fields.date_repairstarted")}: </label>
) : null} <DateTimeFormatter>{data.date_repairstarted}</DateTimeFormatter>
</>
{data.date_invoiced ? ( ),
<Timeline.Item> },
<label>{t("jobs.fields.date_invoiced")}: </label> ]
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter> : []),
</Timeline.Item> ...(data.scheduled_completion
) : null} ? [
{
{data.date_exported ? ( key: "scheduled_completion",
<Timeline.Item> label: (
<label>{t("jobs.fields.date_exported")}: </label> <>
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter> <label>{t("jobs.fields.scheduled_completion")}: </label>
</Timeline.Item> <DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
) : null} </>
</Timeline> ),
) : null} },
</CardTemplate> ]
); : []),
...(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>
);
} }

View File

@@ -1,94 +1,95 @@
import { useQuery } from "@apollo/client"; import {useQuery} from "@apollo/client";
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd"; import {Col, Divider, Row, Skeleton, Space, Timeline, Typography} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { Link } from "react-router-dom"; import {Link} from "react-router-dom";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries"; import {GET_JOB_LINE_ORDERS} from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import {DateFormatter} from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
export default function JobLinesExpander({ jobline, jobid }) { export default function JobLinesExpander({jobline, jobid}) {
const { t } = useTranslation(); const {t} = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
joblineid: jobline.id, joblineid: jobline.id,
}, },
}); });
if (loading) return <Skeleton />; if (loading) return <Skeleton/>;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error"/>;
return ( return (
<Row> <Row>
<Col md={24} lg={12}> <Col md={24} lg={12}>
<Typography.Title level={4}> <Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")} {t("parts_orders.labels.parts_orders")}
</Typography.Title> </Typography.Title>
<Timeline> <Timeline
{data.parts_order_lines.length > 0 ? ( items={
data.parts_order_lines.map((line) => ( data.parts_order_lines.length > 0
<Timeline.Item key={line.id}> ? data.parts_order_lines.map((line) => ({
<Space split={<Divider type="vertical" />} wrap> key: line.id,
<Link label: (
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`} <Space split={<Divider type="vertical"/>} wrap>
> <Link
{line.parts_order.order_number} to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
</Link> >
<DateFormatter>{line.parts_order.order_date}</DateFormatter> {line.parts_order.order_number}
{line.parts_order.vendor.name} </Link>
</Space> <DateFormatter>{line.parts_order.order_date}</DateFormatter>
</Timeline.Item> {line.parts_order.vendor.name}
)) </Space>
) : ( ),
<Timeline.Item> }))
{t("parts_orders.labels.notyetordered")} : [
</Timeline.Item> {
)} key: "no-orders",
</Timeline> label: t("parts_orders.labels.notyetordered"),
</Col> },
<Col md={24} lg={12}> ]
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title> }
<Timeline> /> </Col>
{data.billlines.length > 0 ? ( <Col md={24} lg={12}>
data.billlines.map((line) => ( <Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline.Item key={line.id}> <Timeline
<Row wrap> items={
<Col span={4}> data.billlines.length > 0
<Link ? data.billlines.map((line) => ({
to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`} key: line.id,
> label: (
{line.bill.invoice_number} <Row wrap>
</Link> <Col span={4}>
</Col> <Link
<Col span={4}> to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}
<span> >
{`${t("billlines.fields.actual_price")}: `} {line.bill.invoice_number}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter> </Link>
</span> </Col>
</Col> <Col span={4}>
<Col span={4}> <span>{`${t("billlines.fields.actual_price")}: `}<CurrencyFormatter>{line.actual_price}</CurrencyFormatter></span>
<span> </Col>
{`${t("billlines.fields.actual_cost")}: `} <Col span={4}>
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter> <span>{`${t("billlines.fields.actual_cost")}: `}<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter></span>
</span> </Col>
</Col> <Col span={4}>
<Col span={4}> <DateFormatter>{line.bill.date}</DateFormatter>
<DateFormatter>{line.bill.date}</DateFormatter> </Col>
</Col> <Col span={4}> {line.bill.vendor.name}</Col>
<Col span={4}> {line.bill.vendor.name}</Col> </Row>
</Row> ),
</Timeline.Item> }))
)) : [
) : ( {
<Timeline.Item> key: "no-orders",
{t("parts_orders.labels.notyetordered")} label: t("parts_orders.labels.notyetordered"),
</Timeline.Item> },
)} ]
</Timeline> }
</Col> />
</Row> </Col>
); </Row>
);
} }

View File

@@ -411,14 +411,17 @@ export function JobLinesComponent({
}; };
const markMenu = ( const markMenu = (
<Menu onClick={handleMark}> <Menu
<Menu.Item key="PAA">{t("joblines.fields.part_types.PAA")}</Menu.Item> onClick={handleMark}
<Menu.Item key="PAN">{t("joblines.fields.part_types.PAN")}</Menu.Item> items={[
<Menu.Item key="PAL">{t("joblines.fields.part_types.PAL")}</Menu.Item> { key: "PAA", label: t("joblines.fields.part_types.PAA") },
<Menu.Item key="PAS">{t("joblines.fields.part_types.PAS")}</Menu.Item> { key: "PAN", label: t("joblines.fields.part_types.PAN") },
<Menu.Divider /> { key: "PAL", label: t("joblines.fields.part_types.PAL") },
<Menu.Item key="clear">{t("general.labels.clear")}</Menu.Item> { key: "PAS", label: t("joblines.fields.part_types.PAS") },
</Menu> { key: "divider", label: <hr />, disabled: true },
{ key: "clear", label: t("general.labels.clear") },
]}
/>
); );
return ( return (
@@ -549,7 +552,7 @@ export function JobLinesComponent({
> >
<FilterFilled /> {t("jobs.actions.filterpartsonly")} <FilterFilled /> {t("jobs.actions.filterpartsonly")}
</Button> </Button>
<Dropdown overlay={markMenu} trigger={["click"]}> <Dropdown menu={markMenu} trigger={["click"]}>
<Button>{t("jobs.actions.mark")}</Button> <Button>{t("jobs.actions.mark")}</Button>
</Dropdown> </Dropdown>
<Button <Button

View File

@@ -57,7 +57,7 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
<Select <Select
autoFocus autoFocus
allowClear allowClear
dropdownMatchSelectWidth={100} popupMatchSelectWidth={100}
value={location} value={location}
onClear={() => setLocation(null)} onClear={() => setLocation(null)}
onSelect={handleChange} onSelect={handleChange}

View File

@@ -57,7 +57,7 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
<Select <Select
autoFocus autoFocus
allowClear allowClear
dropdownMatchSelectWidth={100} popupMatchSelectWidth={100}
value={status} value={status}
onSelect={handleChange} onSelect={handleChange}
onBlur={handleSave} onBlur={handleSave}

View File

@@ -22,29 +22,25 @@ export function JoblinePresetButton({ bodyshop, form }) {
}; };
const menu = ( const menu = (
<Menu <Menu
style={{ style={{
columnCount: Math.max( columnCount: Math.max(
Math.floor(bodyshop.md_jobline_presets.length / 15), Math.floor(bodyshop.md_jobline_presets.length / 15),
1 1
), ),
}} }}
> items={bodyshop.md_jobline_presets.map((i, idx) => ({
{bodyshop.md_jobline_presets.map((i, idx) => ( key: idx,
<Menu.Item label: i.label,
onClick={() => handleSelect(i)} style: { breakInside: "avoid" },
key={idx} onClick: () => handleSelect(i),
style={{ breakInside: "avoid" }} }))}
> />
{i.label}
</Menu.Item>
))}
</Menu>
); );
return ( return (
<div> <div>
<Dropdown trigger={["click"]} overlay={menu}> <Dropdown trigger={["click"]} menu={menu}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href="# " href="# "

View File

@@ -45,15 +45,15 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
onClick={(e) => { onClick={(e) => {
updateJobStatus(e.key); updateJobStatus(e.key);
}} }}
> items={bodyshop.md_ro_statuses.statuses.map((item) => ({
{bodyshop.md_ro_statuses.statuses.map((item) => ( key: item,
<Menu.Item key={item}>{item}</Menu.Item> label: item,
))} }))}
</Menu> />
); );
return ( return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus"> <Dropdown menu={statusmenu} trigger={["click"]} key="changestatus">
<Button shape="round"> <Button shape="round">
<span>{job.status}</span> <span>{job.status}</span>

View File

@@ -86,24 +86,27 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
onClick={(e) => { onClick={(e) => {
updateJobStatus(e.key); updateJobStatus(e.key);
}} }}
> items={[
{availableStatuses.map((item) => ( ...availableStatuses.map((item) => ({
<Menu.Item key={item}>{item}</Menu.Item> key: item,
))} label: item,
{job.converted && ( })),
<> ...(job.converted
<Menu.Divider /> ? [
{otherStages.map((item, idx) => ( { key: "divider", label: <hr />, disabled: true },
<Menu.Item key={item}>{item}</Menu.Item> ...otherStages.map((item) => ({
))} key: item,
</> label: item,
)} })),
</Menu> ]
: []),
]}
/>
); );
return ( return (
<Dropdown <Dropdown
overlay={statusmenu} menu={statusmenu}
trigger={["click"]} trigger={["click"]}
key="changestatus" key="changestatus"
disabled={jobRO || !job.converted} disabled={jobRO || !job.converted}

View File

@@ -63,17 +63,18 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
}; };
const overlay = (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( const overlay = (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
<Menu onClick={handleMenuClick}> <Menu
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ( onClick={handleMenuClick}
<Menu.Item disabled={disabled} key={mapping.name}> items={bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => ({
{mapping.name} key: mapping.name,
</Menu.Item> label: mapping.name,
))} disabled: disabled,
</Menu> }))}
/>
); );
return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? ( return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? (
<Dropdown overlay={overlay}> <Dropdown menu={overlay}>
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button> <Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
</Dropdown> </Dropdown>
) : ( ) : (

View File

@@ -16,19 +16,18 @@ export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) {
}; };
const menu = ( const menu = (
<div> <Menu
<Menu onClick={handleClick}> onClick={handleClick}
{bodyshop.md_estimators.map((est, idx) => ( items={bodyshop.md_estimators.map((est, idx) => ({
<Menu.Item value={est} key={idx}> key: idx,
{`${est.est_ct_fn} ${est.est_ct_ln}`} label: `${est.est_ct_fn} ${est.est_ct_ln}`,
</Menu.Item> value: est,
))} }))}
</Menu> />
</div>
); );
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -21,17 +21,17 @@ export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
style={{ style={{
columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1, columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1,
}} }}
> items={bodyshop.md_filehandlers.map((est, idx) => ({
{bodyshop.md_filehandlers.map((est, idx) => ( key: idx,
<Menu.Item value={est} key={idx} style={{ breakInside: "avoid" }}> label: `${est.ins_ct_fn} ${est.ins_ct_ln}`,
{`${est.ins_ct_fn} ${est.ins_ct_ln}`} value: est,
</Menu.Item> style: { breakInside: "avoid" },
))} }))}
</Menu> />
); );
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -560,7 +560,7 @@ export function JobsDetailHeaderActions({
</Menu> </Menu>
); );
return ( return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus"> <Dropdown menu={statusmenu} trigger={["click"]} key="changestatus">
<Button> <Button>
<span>{t("general.labels.actions")}</span> <span>{t("general.labels.actions")}</span>

View File

@@ -19,19 +19,18 @@ export function JobsDetailRatesChangeButton({ disabled, form, bodyshop }) {
}; };
const menu = ( const menu = (
<div> <Menu
<Menu onClick={handleClick}> onClick={handleClick}
{bodyshop.md_labor_rates.map((rate, idx) => ( items={bodyshop.md_labor_rates.map((rate, idx) => ({
<Menu.Item value={rate} key={idx}> key: idx,
{rate.rate_label} label: rate.rate_label,
</Menu.Item> value: rate,
))} }))}
</Menu> />
</div>
); );
return ( return (
<Dropdown overlay={menu} disabled={disabled}> <Dropdown menu={menu} disabled={disabled}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -26,22 +26,18 @@ export function NotesPresetButton({ bodyshop, form }) {
style={{ style={{
columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1, columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1,
}} }}
> items={bodyshop.md_notes_presets.map((i, idx) => ({
{bodyshop.md_notes_presets.map((i, idx) => ( key: idx,
<Menu.Item label: i.label,
onClick={() => handleSelect(i)} style: { breakInside: "avoid" },
key={idx} onClick: () => handleSelect(i),
style={{ breakInside: "avoid" }} }))}
> />
{i.label}
</Menu.Item>
))}
</Menu>
); );
return ( return (
<div> <div>
<Dropdown trigger={["click"]} overlay={menu}> <Dropdown trigger={["click"]} menu={menu}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href="# " href="# "

View File

@@ -87,7 +87,7 @@ export default function PartsOrderModalPriceChange({ form, field }) {
/> />
); );
return ( return (
<Dropdown overlay={menu} trigger="click"> <Dropdown menu={menu} trigger="click">
<Space> <Space>
% %
<DownOutlined /> <DownOutlined />

View File

@@ -40,15 +40,14 @@ export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, i
}; };
const menu = ( const menu = (
<div> <Menu
<Menu onClick={handleClick}> onClick={handleClick}
{bodyshop.md_parts_order_comment.map((comment, idx) => ( items={bodyshop.md_parts_order_comment.map((comment, idx) => ({
<Menu.Item value={comment.comment} key={idx}> key: idx,
{comment.label} label: comment.label,
</Menu.Item> value: comment.comment,
))} }))}
</Menu> />
</div>
); );
return ( return (
@@ -286,7 +285,7 @@ export function PartsOrderModalComponent({bodyshop, vendorList, sendTypeState, i
label={ label={
<Space> <Space>
{t("parts_orders.fields.comments")} {t("parts_orders.fields.comments")}
<Dropdown overlay={menu}> <Dropdown menu={menu}>
<a <a
className="ant-dropdown-link" className="ant-dropdown-link"
href=" #" href=" #"

View File

@@ -53,19 +53,18 @@ export function ProductionColumnsComponent({
style={{ style={{
columnCount: Math.max(Math.floor(cols.length / 10), 1), columnCount: Math.max(Math.floor(cols.length / 10), 1),
}} }}
> items={cols
{cols
.filter((i) => !columnKeys.includes(i.key)) .filter((i) => !columnKeys.includes(i.key))
.map((item) => ( .map((item) => ({
<Menu.Item key={item.key} style={{ breakInside: "avoid" }}> key: item.key,
{item.title} label: item.title,
</Menu.Item> style: { breakInside: "avoid" },
))} }))}
</Menu> />
); );
return ( return (
<div> <div>
<Dropdown overlay={menu}> <Dropdown menu={menu}>
<Button>{t("production.actions.addcolumns")}</Button> <Button>{t("production.actions.addcolumns")}</Button>
</Dropdown> </Dropdown>
</div> </div>

View File

@@ -53,14 +53,18 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
return ( return (
<Dropdown <Dropdown
overlay={ menu={
<Menu> <Menu
<Menu.Item key="toggleAlert" onClick={handleAlertToggle}> items={[
{record.production_vars && record.production_vars.alert {
? t("production.labels.alertoff") key: "toggleAlert",
: t("production.labels.alerton")} label: record.production_vars && record.production_vars.alert
</Menu.Item> ? t("production.labels.alertoff")
</Menu> : t("production.labels.alerton"),
onClick: handleAlertToggle,
},
]}
/>
} }
trigger={["contextMenu"]} trigger={["contextMenu"]}
> >

View File

@@ -31,24 +31,28 @@ export default function ProductionListColumnBodyPriority({ record }) {
return ( return (
<Dropdown <Dropdown
overlay={ menu={
<Menu onClick={handleSetBodyPriority}> <Menu
<Menu.Item key="clearBodyPriority"> onClick={handleSetBodyPriority}
{t("production.actions.bodypriority-clear")} items={[
</Menu.Item> {
<Menu.SubMenu key: "clearBodyPriority",
key="set" label: t("production.actions.bodypriority-clear"),
title={t("production.actions.bodypriority-set")} },
> {
{new Array(15).fill().map((value, index) => ( key: "set",
<Menu.Item key={index + 1}> label: t("production.actions.bodypriority-set"),
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}> children: new Array(15).fill().map((value, index) => ({
{index + 1} key: index + 1,
</div> label: (
</Menu.Item> <div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
))} {index + 1}
</Menu.SubMenu> </div>
</Menu> ),
})),
},
]}
/>
} }
trigger={["click"]} trigger={["click"]}
> >

View File

@@ -55,7 +55,7 @@ export default function ProductionListDate({
(dayjs().add(1, "day").isSame(dayjs(record[field]), "day") && (dayjs().add(1, "day").isSame(dayjs(record[field]), "day") &&
"production-completion-soon")); "production-completion-soon"));
} }
// TODO - Client Update = Why is the overlay a card?
return ( return (
<Dropdown <Dropdown
trigger={["click"]} trigger={["click"]}

View File

@@ -31,24 +31,28 @@ export default function ProductionListColumnDetailPriority({ record }) {
return ( return (
<Dropdown <Dropdown
overlay={ menu={
<Menu onClick={handleSetDetailPriority}> <Menu
<Menu.Item key="clearDetailPriority"> onClick={handleSetDetailPriority}
{t("production.actions.detailpriority-clear")} items={[
</Menu.Item> {
<Menu.SubMenu key: "clearDetailPriority",
key="set" label: t("production.actions.detailpriority-clear"),
title={t("production.actions.detailpriority-set")} },
> {
{new Array(15).fill().map((value, index) => ( key: "set",
<Menu.Item key={index + 1}> label: t("production.actions.detailpriority-set"),
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}> children: new Array(15).fill().map((value, index) => ({
{index + 1} key: index + 1,
</div> label: (
</Menu.Item> <div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
))} {index + 1}
</Menu.SubMenu> </div>
</Menu> ),
})),
},
]}
/>
} }
trigger={["click"]} trigger={["click"]}
> >

View File

@@ -89,6 +89,7 @@ export function ProductionLastContacted({ currentUser, record }) {
} }
}, [visible, form, record.date_last_contacted]); }, [visible, form, record.date_last_contacted]);
// TODO - Client Update - Why is this a card?
return ( return (
<div> <div>
<Dropdown <Dropdown

View File

@@ -31,24 +31,28 @@ export default function ProductionListColumnPaintPriority({ record }) {
return ( return (
<Dropdown <Dropdown
overlay={ menu={
<Menu onClick={handleSetPaintPriority}> <Menu
<Menu.Item key="clearPaintPriority"> onClick={handleSetPaintPriority}
{t("production.actions.paintpriority-clear")} items={[
</Menu.Item> {
<Menu.SubMenu key: "clearPaintPriority",
key="set" label: t("production.actions.paintpriority-clear"),
title={t("production.actions.paintpriority-set")} },
> {
{new Array(15).fill().map((value, index) => ( key: "set",
<Menu.Item key={index + 1}> label: t("production.actions.paintpriority-set"),
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}> children: new Array(15).fill().map((value, index) => ({
{index + 1} key: index + 1,
</div> label: (
</Menu.Item> <div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
))} {index + 1}
</Menu.SubMenu> </div>
</Menu> ),
})),
},
]}
/>
} }
trigger={["click"]} trigger={["click"]}
> >

View File

@@ -38,15 +38,15 @@ export function ProductionListColumnCategory({ record, bodyshop }) {
return ( return (
<Dropdown <Dropdown
overlay={ menu={
<Menu <Menu
style={{ maxHeight: "200px", overflowY: "auto" }} style={{ maxHeight: "200px", overflowY: "auto" }}
onClick={handleSetStatus} onClick={handleSetStatus}
> items={bodyshop.md_categories.map((item) => ({
{bodyshop.md_categories.map((item) => ( key: item,
<Menu.Item key={item}>{item}</Menu.Item> label: item,
))} }))}
</Menu> />
} }
trigger={["click"]} trigger={["click"]}
> >

View File

@@ -47,15 +47,15 @@ export function ProductionListColumnStatus({
return ( return (
<Dropdown <Dropdown
overlay={ menu={
<Menu <Menu
style={{ maxHeight: "200px", overflowY: "auto" }} style={{ maxHeight: "200px", overflowY: "auto" }}
onClick={handleSetStatus} onClick={handleSetStatus}
> items={bodyshop.md_ro_statuses.production_statuses.map((item) => ({
{bodyshop.md_ro_statuses.production_statuses.map((item) => ( key: item,
<Menu.Item key={item}>{item}</Menu.Item> label: item,
))} }))}
</Menu> />
} }
trigger={["click"]} trigger={["click"]}
> >

View File

@@ -31,99 +31,53 @@ export function ProductionListPrint({ bodyshop }) {
return ( return (
<Dropdown <Dropdown
trigger="click" trigger="click"
overlay={ menu={
<Menu> <Menu
{Object.keys(ProdTemplates).map((key) => ( onClick={async (e) => {
<Menu.Item setLoading(true);
key={key} await GenerateDocument(
onClick={async () => {
setLoading(true);
await GenerateDocument(
{ {
name: ProdTemplates[key].key, name: ProdTemplates[e.key].key,
// variables: { id: contract.id }, // variables: { id: contract.id },
}, },
{}, {},
"p" "p"
); );
setLoading(false); setLoading(false);
}} }}
> items={[
{ProdTemplates[key].title} ...Object.keys(ProdTemplates).map((key) => ({
</Menu.Item> key: key,
))} label: ProdTemplates[key].title,
<Menu.SubMenu })),
title={t("reportcenter.templates.production_by_technician_one")} {
> key: "production_by_technician_one",
{bodyshop.employees label: t("reportcenter.templates.production_by_technician_one"),
.filter((e) => e.active) children: bodyshop.employees
.map((e) => ( .filter((e) => e.active)
<Menu.Item .map((e) => ({
key={e.id} key: e.id,
onClick={async () => { label: `${e.first_name} ${e.last_name}`,
setLoading(true); })),
await GenerateDocument( },
{ {
name: production_by_technician_one.key, key: "production_by_category_one",
variables: { id: e.id }, label: t("reportcenter.templates.production_by_category_one"),
}, children: bodyshop.md_categories.map((e) => ({
{}, key: e,
"p" label: e,
); })),
setLoading(false); },
}} {
> key: "production_by_repair_status_one",
{e.first_name} {e.last_name} label: t("reportcenter.templates.production_by_repair_status_one"),
</Menu.Item> children: bodyshop.md_ro_statuses.production_statuses.map((e) => ({
))} key: e,
</Menu.SubMenu> label: e,
<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>
} }
> >
<Button loading={loading}>{t("general.labels.print")}</Button> <Button loading={loading}>{t("general.labels.print")}</Button>

View File

@@ -115,7 +115,7 @@ export function ProductionListTable({
onSelect={handleSelect} onSelect={handleSelect}
placeholder={t("production.labels.selectview")} placeholder={t("production.labels.selectview")}
optionLabelProp="label" optionLabelProp="label"
dropdownMatchSelectWidth={false} popupMatchSelectWidth={false}
defaultValue={defaultView} defaultValue={defaultView}
> >
{bodyshop.production_config.map((config) => ( {bodyshop.production_config.map((config) => (

View File

@@ -108,12 +108,16 @@ export function ProductionListTable({loading, data, refetch, bodyshop, technicia
const headerItem = (col) => ( const headerItem = (col) => (
<Dropdown <Dropdown
className="prod-header-dropdown" className="prod-header-dropdown"
overlay={ menu={
<Menu onClick={removeColumn}> <Menu
<Menu.Item key={col.key}> onClick={removeColumn}
{t("production.actions.removecolumn")} items={[
</Menu.Item> {
</Menu> key: col.key,
label: t("production.actions.removecolumn"),
},
]}
/>
} }
trigger={["contextMenu"]} trigger={["contextMenu"]}
> >

View File

@@ -56,14 +56,20 @@ export function ScheduleBlockDay({
}; };
const menu = ( const menu = (
<Menu onClick={handleMenu}> <Menu
<Menu.Item key="block">{t("appointments.actions.block")}</Menu.Item> onClick={handleMenu}
</Menu> items={[
{
key: "block",
label: t("appointments.actions.block"),
},
]}
/>
); );
return ( return (
<Dropdown <Dropdown
overlay={menu} menu={menu}
disabled={alreadyBlocked} disabled={alreadyBlocked}
trigger={["contextMenu"]} trigger={["contextMenu"]}
> >

View File

@@ -1,48 +1,45 @@
import React from "react"; import React from "react";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { Timeline } from "antd"; import {Timeline} from "antd";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import {DateTimeFormatter} from "../../utils/DateFormatter";
export default function ScheduleExistingAppointmentsList({ export default function ScheduleExistingAppointmentsList({
existingAppointments, existingAppointments,
}) { }) {
const { t } = useTranslation(); const {t} = useTranslation();
if (existingAppointments.loading) return <LoadingSpinner />; if (existingAppointments.loading) return <LoadingSpinner/>;
if (existingAppointments.error) if (existingAppointments.error)
return (
<AlertComponent
message={existingAppointments.error.message}
type="error"
/>
);
return ( return (
<AlertComponent <div>
message={existingAppointments.error.message} {t("appointments.labels.priorappointments")}
type="error" <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>
);
} }

View File

@@ -88,6 +88,8 @@ export default function ScoreboardEntryEdit({ entry }) {
</Card> </Card>
); );
// TODO Client Update, why is this a card
return ( return (
<div> <div>
<Dropdown open={visible} overlay={popContent}> <Dropdown open={visible} overlay={popContent}>

View File

@@ -28,13 +28,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
export function ShopInfoComponent({bodyshop, form, saveLoading}) { export function ShopInfoComponent({bodyshop, form, saveLoading}) {
const { treatments: {CriticalPartsScanning} } = useSplitTreatments({ const {treatments: {CriticalPartsScanning}} = useSplitTreatments({
attributes: {}, attributes: {},
names: ["CriticalPartsScanning"], names: ["CriticalPartsScanning"],
splitKey: bodyshop.imexshopid, splitKey: bodyshop.imexshopid,
}); });
const {t} = useTranslation(); const {t} = useTranslation();
const history = useNavigate(); const history = useNavigate();
const location = useLocation(); const location = useLocation();
const search = queryString.parse(location.search); const search = queryString.parse(location.search);
@@ -58,47 +58,63 @@ export function ShopInfoComponent({bodyshop, form, saveLoading}) {
search: `?tab=${search.tab}&subtab=${key}`, search: `?tab=${search.tab}&subtab=${key}`,
}) })
} }
> items={[
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}> {
<ShopInfoGeneral form={form}/> key: "general",
</Tabs.TabPane> tab: t("bodyshop.labels.shopinfo"),
<Tabs.TabPane key="speedprint" tab={t("bodyshop.labels.speedprint")}> children: <ShopInfoGeneral form={form}/>,
<ShopInfoSpeedPrint form={form}/> },
</Tabs.TabPane> {
<Tabs.TabPane key="rbac" tab={t("bodyshop.labels.rbac")}> key: "speedprint",
<ShopInfoRbacComponent form={form}/> tab: t("bodyshop.labels.speedprint"),
</Tabs.TabPane> children: <ShopInfoSpeedPrint form={form}/>,
<Tabs.TabPane key="roStatus" tab={t("bodyshop.labels.jobstatuses")}> },
<ShopInfoROStatusComponent form={form}/> {
</Tabs.TabPane> key: "rbac",
<Tabs.TabPane key="scheduling" tab={t("bodyshop.labels.scheduling")}> tab: t("bodyshop.labels.rbac"),
<ShopInfoSchedulingComponent form={form}/> children: <ShopInfoRbacComponent form={form}/>,
</Tabs.TabPane> },
<Tabs.TabPane {
key="orderStatus" key: "roStatus",
tab={t("bodyshop.labels.orderstatuses")} tab: t("bodyshop.labels.jobstatuses"),
> children: <ShopInfoROStatusComponent form={form}/>,
<ShopInfoOrderStatusComponent form={form}/> },
</Tabs.TabPane> {
<Tabs.TabPane key: "scheduling",
key="responsibilityCenters" tab: t("bodyshop.labels.scheduling"),
tab={t("bodyshop.labels.responsibilitycenters.title")} children: <ShopInfoSchedulingComponent form={form}/>,
> },
<ShopInfoResponsibilityCenterComponent form={form}/> {
</Tabs.TabPane> key: "orderStatus",
tab: t("bodyshop.labels.orderstatuses"),
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}> children: <ShopInfoOrderStatusComponent form={form}/>,
<ShopInfoIntakeChecklistComponent form={form}/> },
</Tabs.TabPane> {
<Tabs.TabPane key="laborrates" tab={t("bodyshop.labels.laborrates")}> key: "responsibilityCenters",
<ShopInfoLaborRates form={form}/> tab: t("bodyshop.labels.responsibilitycenters.title"),
</Tabs.TabPane> children: <ShopInfoResponsibilityCenterComponent form={form}/>,
{CriticalPartsScanning.treatment === "on" && ( },
<Tabs.TabPane key="partsscan" tab={t("bodyshop.labels.partsscan")}> {
<ShopInfoPartsScan form={form}/> key: "checklists",
</Tabs.TabPane> tab: t("bodyshop.labels.checklists"),
)} children: <ShopInfoIntakeChecklistComponent form={form}/>,
</Tabs> },
{
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> </Card>
); );
} }

View File

@@ -69,19 +69,27 @@ export function ShopTemplateAddComponent({
}; };
const TemplateListGenerated = TemplateList(); const TemplateListGenerated = TemplateList();
const menu = ( const menu = (
<Menu onClick={handleAdd}> <Menu
{availableTemplateKeys.length > 0 ? ( onClick={handleAdd}
availableTemplateKeys.map((tkey) => ( items={
<Menu.Item key={tkey}>{TemplateListGenerated[tkey].title}</Menu.Item> availableTemplateKeys.length > 0
)) ? availableTemplateKeys.map((tkey) => ({
) : ( key: tkey,
<div>{t("bodyshop.labels.notemplatesavailable")}</div> label: TemplateListGenerated[tkey].title,
)} }))
</Menu> : [
{
key: "no-templates",
label: t("bodyshop.labels.notemplatesavailable"),
disabled: true,
},
]
}
/>
); );
return ( return (
<Dropdown overlay={menu}> <Dropdown menu={menu}>
<span> <span>
{t("bodyshop.actions.addtemplate")} <DownOutlined /> {t("bodyshop.actions.addtemplate")} <DownOutlined />
</span> </span>

View File

@@ -35,7 +35,7 @@ export default function ShopTemplatesListContainer({ visibleState }) {
}; };
return ( return (
<Drawer (<Drawer
placement="left" placement="left"
width="25%" width="25%"
open={visible} open={visible}
@@ -64,7 +64,7 @@ export default function ShopTemplatesListContainer({ visibleState }) {
]} ]}
> >
<Skeleton title={false} loading={item.loading} active> <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].title}</div>
<div>{TemplateList()[item.name].description}</div> <div>{TemplateList()[item.name].description}</div>
<div>{TemplateList()[item.name].drivingid}</div> <div>{TemplateList()[item.name].drivingid}</div>
@@ -74,6 +74,6 @@ export default function ShopTemplatesListContainer({ visibleState }) {
)} )}
/> />
</div> </div>
</Drawer> </Drawer>)
); );
} }

View File

@@ -1,16 +1,16 @@
import { PrinterFilled } from "@ant-design/icons"; import {PrinterFilled} from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import {useQuery} from "@apollo/client";
import { Button, Divider, Drawer, Grid, Tabs } from "antd"; import {Button, Divider, Drawer, Grid, Tabs} from "antd";
import {PageHeader} from "@ant-design/pro-layout"; import {PageHeader} from "@ant-design/pro-layout";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { useNavigate, useLocation } from "react-router-dom"; import {useLocation, useNavigate} from "react-router-dom";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries"; import {GET_JOB_BY_PK} from "../../graphql/jobs.queries";
import { setModalContext } from "../../redux/modals/modals.actions"; import {setModalContext} from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobLinesContainer from "../job-detail-lines/job-lines.container"; import JobLinesContainer from "../job-detail-lines/job-lines.container";
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component"; 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 JobNotesContainer from "../jobs-notes/jobs-notes.container";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapStateToProps = createStructuredSelector({bodyshop: selectBodyshop});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })), dispatch(setModalContext({context: context, modal: "printCenter"})),
}); });
// const colBreakPoints = { // const colBreakPoints = {
@@ -35,105 +35,118 @@ const mapDispatchToProps = (dispatch) => ({
// }, // },
// }; // };
export function TechLookupJobsDrawer({ bodyshop, setPrintCenterContext }) { export function TechLookupJobsDrawer({bodyshop, setPrintCenterContext}) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
.slice(-1)[0]; .slice(-1)[0];
const bpoints = { const bpoints = {
xs: "100%", xs: "100%",
sm: "100%", sm: "100%",
md: "100%", md: "100%",
lg: "100%", lg: "100%",
xl: "90%", xl: "90%",
xxl: "85%", xxl: "85%",
}; };
const drawerPercentage = selectedBreakpoint const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]] ? bpoints[selectedBreakpoint[0]]
: "100%"; : "100%";
const searchParams =queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams; const {selected} = searchParams;
const history = useNavigate(); const history = useNavigate();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, { const {loading, error, data, refetch} = useQuery(GET_JOB_BY_PK, {
variables: { id: selected }, variables: {id: selected},
skip: !selected, skip: !selected,
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
});
const { t } = useTranslation();
const handleDrawerClose = () => {
delete searchParams.selected;
history({
search: queryString.stringify({
...searchParams,
}),
}); });
};
return ( const {t} = useTranslation();
<Drawer const handleDrawerClose = () => {
open={!!selected} delete searchParams.selected;
destroyOnClose history({
width={drawerPercentage} search: queryString.stringify({
placement="right" ...searchParams,
onClose={handleDrawerClose} }),
> });
{loading ? <LoadingSpinner /> : null} };
{error ? <AlertComponent message={error.message} type="error" /> : null}
{data ? ( return (
<PageHeader <Drawer
onBack={() => window.history.back()} open={!!selected}
title={data.jobs_by_pk.ro_number || t("general.labels.na")} destroyOnClose
extra={ width={drawerPercentage}
<Button placement="right"
onClick={() => { onClose={handleDrawerClose}
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 /> {loading ? <LoadingSpinner/> : null}
<Divider type="horizontal" /> {error ? <AlertComponent message={error.message} type="error"/> : null}
<Tabs size="large"> {data ? (
<Tabs.TabPane key="lines" tab={t("jobs.labels.lines")}> <PageHeader
<JobLinesContainer onBack={() => window.history.back()}
jobId={selected} title={data.jobs_by_pk.ro_number || t("general.labels.na")}
joblines={data.jobs_by_pk.joblines} extra={
job={data.jobs_by_pk} <Button
refetch={refetch} onClick={() => {
/> setPrintCenterContext({
</Tabs.TabPane> actions: {refetch: refetch},
<Tabs.TabPane key="documents" tab={t("jobs.labels.documents")}> context: {
{bodyshop.uselocalmediaserver ? ( id: data.jobs_by_pk.id,
<JobsDocumentsLocalGallery job: data.jobs_by_pk,
job={data ? data.jobs_by_pk : null} type: "job",
/> },
) : ( });
<JobsDocumentsGalleryContainer jobId={searchParams.selected} /> }}
)} >
</Tabs.TabPane> <PrinterFilled/>
<Tabs.TabPane key="notes" tab={t("jobs.labels.notes")}> {t("jobs.actions.printCenter")}
<JobNotesContainer jobId={searchParams.selected} /> </Button>
</Tabs.TabPane> }
</Tabs> >
</PageHeader> <JobsDetailHeader job={data.jobs_by_pk} disabled/>
) : null} <Divider type="horizontal"/>
</Drawer> <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( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(TechLookupJobsDrawer); )(TechLookupJobsDrawer);

View File

@@ -40,50 +40,60 @@ export function TechSider({ technician, techLogout }) {
collapsed={collapsed} collapsed={collapsed}
onCollapse={onCollapse} onCollapse={onCollapse}
> >
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline"> <Menu
<Menu.Item theme="dark"
key="1" defaultSelectedKeys={["1"]}
disabled={!!technician} mode="inline"
icon={<Icon component={FiLogIn} />} onClick={(e) => {
> if (e.key === "7") {
<Link to={`/tech/login`}>{t("menus.tech.login")}</Link> techLogout();
</Menu.Item> }
<Menu.Item key="2" disabled={!!!technician} icon={<SearchOutlined />}> }}
<Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link> items={[
</Menu.Item> {
<Menu.Item key: "1",
key="3" icon: <Icon component={FiLogIn} />,
disabled={!!!technician} disabled: !!technician,
icon={<Icon component={FaBusinessTime} />} label: <Link to={`/tech/login`}>{t("menus.tech.login")}</Link>,
> },
<Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link> {
</Menu.Item> key: "2",
<Menu.Item icon: <SearchOutlined />,
key="4" disabled: !!!technician,
disabled={!!!technician} label: <Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link>,
icon={<Icon component={MdTimer} />} },
> {
<Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link> key: "3",
</Menu.Item> icon: <Icon component={FaBusinessTime} />,
<Menu.Item key="5" disabled={!!!technician} icon={<ScheduleOutlined />}> disabled: !!!technician,
<Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link> label: <Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link>,
</Menu.Item> },
<Menu.Item {
key="6" key: "4",
disabled={!!!technician} icon: <Icon component={MdTimer} />,
icon={<Icon component={BsKanban} />} disabled: !!!technician,
> label: <Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link>,
<Link to={`/tech/board`}> {t("menus.tech.productionboard")}</Link> },
</Menu.Item> {
<Menu.Item key: "5",
key="7" icon: <ScheduleOutlined />,
disabled={!!!technician} disabled: !!!technician,
onClick={() => techLogout()} label: <Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>,
icon={<Icon component={FiLogOut} />} },
> {
{t("menus.tech.logout")} key: "6",
</Menu.Item> icon: <Icon component={BsKanban} />,
</Menu> 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> </Sider>
); );
} }

View File

@@ -36,7 +36,7 @@ const VendorSearchSelect = (
style={{ style={{
width: "100%", width: "100%",
}} }}
dropdownMatchSelectWidth={false} popupMatchSelectWidth={false}
onChange={setOption} onChange={setOption}
optionFilterProp="name" optionFilterProp="name"
onSelect={onSelect} onSelect={onSelect}

View File

@@ -122,9 +122,8 @@ class Header extends React.Component {
mode={isMobile ? 'inline' : 'horizontal'} mode={isMobile ? 'inline' : 'horizontal'}
defaultSelectedKeys={['sub0']} defaultSelectedKeys={['sub0']}
theme="dark" theme="dark"
> items={navChildren}
{navChildren} />
</Menu>
</TweenOne> </TweenOne>
</div> </div>
</TweenOne> </TweenOne>

View File

@@ -77,58 +77,33 @@ export function ContractDetailPage({
</Button> </Button>
<Dropdown <Dropdown
trigger="click" trigger="click"
overlay={ menu={
<Menu> <Menu
<Menu.Item onClick={(e) => {
onClick={() => GenerateDocument(
GenerateDocument( {
{ name: TemplateList("courtesycarcontract")[e.key].key,
name: TemplateList("courtesycarcontract") variables: { id: contract.id },
.courtesy_car_contract.key, },
variables: { id: contract.id }, {},
}, "p"
{}, );
"p" }}
) items={[
} {
> key: "courtesy_car_contract",
{t("contracts.actions.printcontract")} label: t("contracts.actions.printcontract"),
</Menu.Item> },
<Menu.Item {
onClick={() => key: "courtesy_car_terms",
GenerateDocument( label: t("printcenter.courtesycarcontract.courtesy_car_terms"),
{ },
name: TemplateList("courtesycarcontract") {
.courtesy_car_terms.key, key: "courtesy_car_impound",
variables: { id: contract.id }, label: t("printcenter.courtesycarcontract.courtesy_car_impound"),
}, },
{}, ]}
"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>
} }
> >
<Button>{t("general.labels.print")}</Button> <Button>{t("general.labels.print")}</Button>

View File

@@ -254,135 +254,97 @@ export function JobsDetailPage({
<JobsDetailHeader job={job} /> <JobsDetailHeader job={job} />
<Divider type="horizontal" /> <Divider type="horizontal" />
<FormFieldsChanged form={form} /> <FormFieldsChanged form={form} />
<Tabs <Tabs
defaultActiveKey={search.tab} defaultActiveKey={search.tab}
onChange={(key) => history({ search: `?tab=${key}` })} onChange={(key) => history({ search: `?tab=${key}` })}
tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }} tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }}
> items={[
<Tabs.TabPane {
forceRender key: "general",
tab={ tab: (
<span> <span><Icon component={FaShieldAlt} />{t("menus.jobsdetail.general")}</span>
<Icon component={FaShieldAlt} /> ),
{t("menus.jobsdetail.general")} forceRender: true,
</span> children: <JobsDetailGeneral job={job} form={form} />,
} },
key="general" {
> key: "repairdata",
<JobsDetailGeneral job={job} form={form} /> tab: (
</Tabs.TabPane> <span><BarsOutlined />{t("menus.jobsdetail.repairdata")}</span>
<Tabs.TabPane ),
forceRender forceRender: true,
tab={ children: (
<span> <JobsLinesContainer
<BarsOutlined /> job={job}
{t("menus.jobsdetail.repairdata")} joblines={job.joblines}
</span> refetch={refetch}
} form={form}
key="repairdata" />
> ),
<JobsLinesContainer },
job={job} {
joblines={job.joblines} key: "rates",
refetch={refetch} tab: (
form={form} <span><DollarCircleOutlined />{t("menus.jobsdetail.rates")}</span>
/> ),
</Tabs.TabPane> forceRender: true,
<Tabs.TabPane children: <JobsDetailRates job={job} form={form} />,
forceRender },
tab={ {
<span> key: "totals",
<DollarCircleOutlined /> tab: (
{t("menus.jobsdetail.rates")} <span><DollarCircleOutlined />{t("menus.jobsdetail.totals")}</span>
</span> ),
} children: <JobsDetailTotals job={job} refetch={refetch} />,
key="rates" },
> {
<JobsDetailRates job={job} form={form} /> key: "partssublet",
</Tabs.TabPane> tab: (
<Tabs.TabPane <span><ToolFilled />{t("menus.jobsdetail.partssublet")}</span>
tab={ ),
<span> children: <JobsDetailPliContainer job={job} />,
<DollarCircleOutlined /> },
{t("menus.jobsdetail.totals")} {
</span> key: "labor",
} tab: (
key="totals" <span><Icon component={FaHardHat} />{t("menus.jobsdetail.labor")}</span>
> ),
<JobsDetailTotals job={job} refetch={refetch} /> children: <JobsDetailLaborContainer job={job} jobId={job.id} />,
</Tabs.TabPane> },
<Tabs.TabPane {
tab={ key: "dates",
<span> tab: (
<ToolFilled /> <span><CalendarFilled />{t("menus.jobsdetail.dates")}</span>
{t("menus.jobsdetail.partssublet")} ),
</span> forceRender: true,
} children: <JobsDetailDatesComponent job={job} />,
key="partssublet" },
> {
<JobsDetailPliContainer job={job} /> key: "documents",
</Tabs.TabPane> tab: (
<Tabs.TabPane <span><FileImageFilled />{t("jobs.labels.documents")}</span>
tab={ ),
<span> children: bodyshop.uselocalmediaserver ? (
<Icon component={FaHardHat} /> <JobsDocumentsLocalGallery job={job} />
{t("menus.jobsdetail.labor")} ) : (
</span> <JobsDocumentsGalleryContainer jobId={job.id} />
} ),
key="labor" },
> {
<JobsDetailLaborContainer job={job} jobId={job.id} /> key: "notes",
</Tabs.TabPane> tab: (
<Tabs.TabPane <span><Icon component={FaRegStickyNote} />{t("jobs.labels.notes")}</span>
forceRender ),
tab={ children: <JobNotesContainer jobId={job.id} />,
<span> },
<CalendarFilled /> {
{t("menus.jobsdetail.dates")} key: "audit",
</span> tab: (<span><HistoryOutlined />{t("jobs.labels.audit")}</span>
} ),
key="dates" children: <JobAuditTrail jobId={job.id} />,
> },
<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>
</Form> </Form>
</div> </div>
); );

View File

@@ -53,19 +53,19 @@ export function PhonebookContainer({ setBreadcrumbs, setSelectedHeader }) {
: "100%"; : "100%";
return ( return (
<RbacWrapper action="phonebook:view"> (<RbacWrapper action="phonebook:view">
<PhonebookPage /> <PhonebookPage />
<Drawer <Drawer
width={drawerPercentage} width={drawerPercentage}
onClose={() => { onClose={() => {
delete search.phonebookentry; delete search.phonebookentry;
navigate({ search: queryString.stringify(search) }); navigate({ search: queryString.stringify(search) });
}} }}
visible={phonebookentry} open={phonebookentry}
> >
<PhonebookFormContainer /> <PhonebookFormContainer />
</Drawer> </Drawer>
</RbacWrapper> </RbacWrapper>)
); );
} }
export default connect(null, mapDispatchToProps)(PhonebookContainer); export default connect(null, mapDispatchToProps)(PhonebookContainer);

View File

@@ -66,52 +66,38 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
<FeatureWrapper featureName="scoreboard"> <FeatureWrapper featureName="scoreboard">
<RbacWrapper action="scoreboard:view"> <RbacWrapper action="scoreboard:view">
<Tabs <Tabs
activeKey={tab || "sb"} 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>
}
destroyInactiveTabPane destroyInactiveTabPane
key="sb" onChange={(key) => {
> searchParams.tab = key;
<ScoreboardDisplay /> history({
</Tabs.TabPane> search: queryString.stringify(searchParams),
<Tabs.TabPane });
tab={ }}
<span> items={[
<FieldTimeOutlined /> {
{t("scoreboard.labels.timeticketsemployee")} key: "sb",
</span> tab: (<span><Icon component={FaShieldAlt} />{t("scoreboard.labels.jobs")}</span>
} ),
destroyInactiveTabPane forceRender: true,
key="tickets" children: <ScoreboardDisplay />,
> },
<ScoreboardTimeTickets /> {
</Tabs.TabPane> key: "tickets",
<Tabs.TabPane tab: (<span><FieldTimeOutlined />{t("scoreboard.labels.timeticketsemployee")}</span>
tab={ ),
<span> forceRender: true,
<FieldTimeOutlined /> children: <ScoreboardTimeTickets />,
{t("scoreboard.labels.allemployeetimetickets")} },
</span> {
} key: "ticketsstats",
destroyInactiveTabPane tab: (<span><FieldTimeOutlined />{t("scoreboard.labels.allemployeetimetickets")}</span>
key="ticketsstats" ),
> forceRender: true,
<ScoreboardTimeTicketsStats /> children: <ScoreboardTimeTicketsStats />,
</Tabs.TabPane> },
</Tabs> ]}
/>
</RbacWrapper> </RbacWrapper>
</FeatureWrapper> </FeatureWrapper>
); );

View File

@@ -26,19 +26,18 @@ export default function ShopVendorPageComponent() {
: "100%"; : "100%";
return ( return (
<div> (<div>
<VendorsListContainer /> <VendorsListContainer />
<Drawer <Drawer
width={drawerPercentage} width={drawerPercentage}
onClose={() => { onClose={() => {
searchParams.delete("selectedvendor"); searchParams.delete("selectedvendor");
navigate({ search: searchParams.toString() }); navigate({ search: searchParams.toString() });
}} }}
visible={selectedvendor} open={selectedvendor}
> >
<VendorsFormContainer /> <VendorsFormContainer />
</Drawer> </Drawer>
</div> </div>)
); );
} }

View File

@@ -47,22 +47,31 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
return ( return (
<RbacWrapper action="shop:config"> <RbacWrapper action="shop:config">
<Tabs <Tabs
defaultActiveKey={search.tab} activeKey={search.tab}
onChange={(key) => history({ search: `?tab=${key}` })} onChange={(key) => history({ search: `?tab=${key}` })}
> tabs={[
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info"> {
<ShopInfoContainer /> key: "info",
</Tabs.TabPane> tab: t("bodyshop.labels.shopinfo"),
<Tabs.TabPane tab={t("bodyshop.labels.employees")} key="employees"> children: <ShopInfoContainer />,
<ShopEmployeesContainer /> },
</Tabs.TabPane> {
<Tabs.TabPane tab={t("bodyshop.labels.licensing")} key="licensing"> key: "employees",
<ShopInfoUsersComponent /> tab: t("bodyshop.labels.employees"),
</Tabs.TabPane> children: <ShopEmployeesContainer />,
<Tabs.TabPane tab={t("bodyshop.labels.csiq")} key="csiq"> },
<ShopCsiConfig /> {
</Tabs.TabPane> key: "licensing",
</Tabs> tab: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent />,
},
{
key: "csiq",
tab: t("bodyshop.labels.csiq"),
children: <ShopCsiConfig />,
},
]}
/>
</RbacWrapper> </RbacWrapper>
); );
} }