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}
ref={ref}
showSearch
dropdownMatchSelectWidth={false}
popupMatchSelectWidth={false}
// optionFilterProp="line_desc"
filterOption={(inputValue, option) => {
return (

View File

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

View File

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

View File

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

View File

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

View File

@@ -178,6 +178,25 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
)
: courtesycars;
const menuItems = [
{
key: "courtesycar_inventory",
label: t("printcenter.courtesycarcontract.courtesy_car_inventory"),
onClick: () =>
GenerateDocument(
{
name: TemplateList("courtesycar").courtesy_car_inventory.key,
variables: {
//id: contract.id
},
},
{},
"p"
),
},
];
return (
<Card
title={t("menus.header.courtesycars")}
@@ -188,26 +207,8 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
</Button>
<Dropdown
trigger="click"
overlay={
<Menu>
<Menu.Item
onClick={() =>
GenerateDocument(
{
name: TemplateList("courtesycar").courtesy_car_inventory
.key,
variables: {
//id: contract.id
},
},
{},
"p"
)
}
>
{t("printcenter.courtesycarcontract.courtesy_car_inventory")}
</Menu.Item>
</Menu>
menu={
<Menu items={menuItems} />
}
>
<Button>{t("general.labels.print")}</Button>

View File

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

View File

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

View File

@@ -1,27 +1,27 @@
import { DeleteFilled, DownOutlined } from "@ant-design/icons";
import {DeleteFilled, DownOutlined} from "@ant-design/icons";
import {
Button,
Card,
Divider,
Dropdown,
Form,
Input,
InputNumber,
Menu,
Select,
Space,
Statistic,
Switch,
Typography,
Button,
Card,
Divider,
Dropdown,
Form,
Input,
InputNumber,
Menu,
Select,
Space,
Statistic,
Switch,
Typography,
} from "antd";
import Dinero from "dinero.js";
import dayjs from "../../utils/day";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { determineDmsType } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {determineDmsType} from "../../pages/dms/dms.container";
import {selectBodyshop} from "../../redux/user/user.selectors";
import i18n from "../../translations/i18n";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
@@ -30,440 +30,427 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
const [form] = Form.useForm();
const { t } = useTranslation();
export function DmsPostForm({bodyshop, socket, job, logsRef}) {
const [form] = Form.useForm();
const {t} = useTranslation();
const handlePayerSelect = (value, index) => {
form.setFieldsValue({
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
if (index !== mapIndex) return payer;
const cdkPayer =
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
const handlePayerSelect = (value, index) => {
form.setFieldsValue({
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
if (index !== mapIndex) return payer;
const cdkPayer =
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
if (!cdkPayer) return payer;
if (!cdkPayer) return payer;
return {
...cdkPayer,
dms_acctnumber: cdkPayer.dms_acctnumber,
controlnumber: job && job[cdkPayer.control_type],
};
}),
});
};
const handleFinish = (values) => {
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
jobid: job.id,
txEnvelope: values,
});
console.log(logsRef);
if (logsRef) {
console.log("executing", logsRef);
logsRef.curent &&
logsRef.current.scrollIntoView({
behavior: "smooth",
return {
...cdkPayer,
dms_acctnumber: cdkPayer.dms_acctnumber,
controlnumber: job && job[cdkPayer.control_type],
};
}),
});
}
};
};
return (
<Card title={t("jobs.labels.dms.postingform")}>
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{
story: `${t("jobs.labels.dms.defaultstory", {
ro_number: job.ro_number,
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`.trim(),
ins_co_nm: job.ins_co_nm || "N/A",
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
job.po_number || ""
}`,
}).trim()}.${
job.area_of_damage && job.area_of_damage.impact1
? " " +
t("jobs.labels.dms.damageto", {
area_of_damage:
(job.area_of_damage && job.area_of_damage.impact1) ||
"UNKNOWN",
})
: ""
}`.slice(0, 239),
inservicedate: dayjs("2019-01-01"),
}}
>
<LayoutFormRow grow>
<Form.Item
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.default_journal
}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="kmin"
label={t("jobs.fields.kmin")}
initialValue={job && job.kmin}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber disabled />
</Form.Item>
<Form.Item
name="kmout"
label={t("jobs.fields.kmout")}
initialValue={job && job.kmout}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber disabled />
</Form.Item>
</LayoutFormRow>
const handleFinish = (values) => {
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
jobid: job.id,
txEnvelope: values,
});
console.log(logsRef);
if (logsRef) {
console.log("executing", logsRef);
logsRef.curent &&
logsRef.current.scrollIntoView({
behavior: "smooth",
});
}
};
{bodyshop.cdk_dealerid && (
<div>
<LayoutFormRow style={{ justifyContent: "center" }} grow>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
name="dms_model"
label={t("jobs.fields.dms.dms_model")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
name="inservicedate"
label={t("jobs.fields.dms.inservicedate")}
>
<FormDatePicker />
</Form.Item>
</LayoutFormRow>
<Space>
<DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakesRefetch />
<Form.Item
name="dms_unsold"
label={t("jobs.fields.dms.dms_unsold")}
initialValue={false}
>
<Switch />
</Form.Item>
<Form.Item
name="dms_model_override"
label={t("jobs.fields.dms.dms_model_override")}
initialValue={false}
>
<Switch />
</Form.Item>
</Space>
</div>
)}
<Form.Item
name="story"
label={t("jobs.fields.dms.story")}
rules={[
{
required: true,
},
]}
>
<Input.TextArea maxLength={240} />
</Form.Item>
<Divider />
<Space size="large" wrap align="center">
<Statistic
title={t("jobs.fields.ded_amt")}
value={Dinero(
job.job_totals.totals.custPayable.deductible
).toFormat()}
/>
<Statistic
title={t("jobs.labels.total_cust_payable")}
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
/>
</Space>
<Form.List name={["payers"]}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space wrap>
<Form.Item
label={t("jobs.fields.dms.payer.name")}
key={`${index}name`}
name={[field.name, "name"]}
rules={[
{
required: true,
},
]}
>
<Select
style={{ minWidth: "15rem" }}
onSelect={(value) => handlePayerSelect(value, index)}
>
{bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.map((payer) => (
<Select.Option key={payer.name}>
{payer.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.dms_acctnumber")}
key={`${index}dms_acctnumber`}
name={[field.name, "dms_acctnumber"]}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.amount")}
key={`${index}amount`}
name={[field.name, "amount"]}
rules={[
{
required: true,
},
]}
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item
label={
<div>
{t("jobs.fields.dms.payer.controlnumber")}{" "}
<Dropdown
overlay={
<Menu>
{bodyshop.cdk_configuration.controllist &&
bodyshop.cdk_configuration.controllist.map(
(key, idx) => (
<Menu.Item
key={idx}
onClick={() => {
form.setFieldsValue({
payers: form
.getFieldValue("payers")
.map((row, mapIndex) => {
if (index !== mapIndex)
return row;
return {
...row,
controlnumber:
key.controlnumber,
};
}),
});
}}
>
{key.name}
</Menu.Item>
)
)}
</Menu>
}
>
<a href=" #" onClick={(e) => e.preventDefault()}>
<DownOutlined />
</a>
</Dropdown>
</div>
return (
<Card title={t("jobs.labels.dms.postingform")}>
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{
story: `${t("jobs.labels.dms.defaultstory", {
ro_number: job.ro_number,
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`.trim(),
ins_co_nm: job.ins_co_nm || "N/A",
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
job.po_number || ""
}`,
}).trim()}.${
job.area_of_damage && job.area_of_damage.impact1
? " " +
t("jobs.labels.dms.damageto", {
area_of_damage:
(job.area_of_damage && job.area_of_damage.impact1) ||
"UNKNOWN",
})
: ""
}`.slice(0, 239),
inservicedate: dayjs("2019-01-01"),
}}
>
<LayoutFormRow grow>
<Form.Item
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.default_journal
}
key={`${index}controlnumber`}
name={[field.name, "controlnumber"]}
rules={[
{
required: true,
},
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
>
<Input/>
</Form.Item>
<Form.Item
name="kmin"
label={t("jobs.fields.kmin")}
initialValue={job && job.kmin}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber disabled/>
</Form.Item>
<Form.Item
name="kmout"
label={t("jobs.fields.kmout")}
initialValue={job && job.kmout}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber disabled/>
</Form.Item>
</LayoutFormRow>
<Form.Item shouldUpdate>
{() => {
const payers = form.getFieldValue("payers");
const row = payers && payers[index];
const cdkPayer =
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find(
(i) => i && row && i.name === row.name
);
if (
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)
)
return (
<div>
{cdkPayer &&
t(`jobs.fields.${cdkPayer?.control_type}`)}
</div>
);
else if (
i18n.exists(
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
)
) {
return (
<div>
{cdkPayer &&
t(
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
)}
</div>
);
} else {
return null;
}
}}
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</Space>
</Form.Item>
))}
<Form.Item>
<Button
disabled={!(fields.length < 3)}
onClick={() => {
if (fields.length < 3) add();
}}
style={{ width: "100%" }}
>
{t("jobs.actions.dms.addpayer")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item shouldUpdate>
{() => {
//Perform Calculation to determine discrepancy.
let totalAllocated = Dinero();
const payers = form.getFieldValue("payers");
payers &&
payers.forEach((payer) => {
totalAllocated = totalAllocated.add(
Dinero({ amount: Math.round((payer?.amount || 0) * 100) })
);
});
const totals =
socket.allocationsSummary &&
socket.allocationsSummary.reduce(
(acc, val) => {
return {
totalSale: acc.totalSale.add(Dinero(val.sale)),
totalCost: acc.totalCost.add(Dinero(val.cost)),
};
},
{
totalSale: Dinero(),
totalCost: Dinero(),
}
);
const discrep = totals
? totals.totalSale.subtract(totalAllocated)
: Dinero();
return (
<Space size="large" wrap align="center">
<Statistic
title={t("jobs.labels.subtotal")}
value={(totals ? totals.totalSale : Dinero()).toFormat()}
/>
<Typography.Title>-</Typography.Title>
<Statistic
title={t("jobs.labels.dms.totalallocated")}
value={totalAllocated.toFormat()}
/>
<Typography.Title>=</Typography.Title>
<Statistic
title={t("jobs.labels.dms.notallocated")}
valueStyle={{
color: discrep.getAmount() === 0 ? "green" : "red",
}}
value={discrep.toFormat()}
/>
<Button
disabled={
!socket.allocationsSummary || discrep.getAmount() !== 0
}
htmlType="submit"
{bodyshop.cdk_dealerid && (
<div>
<LayoutFormRow style={{justifyContent: "center"}} grow>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input disabled/>
</Form.Item>
<Form.Item
name="dms_model"
label={t("jobs.fields.dms.dms_model")}
rules={[
{
required: true,
},
]}
>
<Input disabled/>
</Form.Item>
<Form.Item
name="inservicedate"
label={t("jobs.fields.dms.inservicedate")}
>
<FormDatePicker/>
</Form.Item>
</LayoutFormRow>
<Space>
<DmsCdkMakes form={form} socket={socket} job={job}/>
<DmsCdkMakesRefetch/>
<Form.Item
name="dms_unsold"
label={t("jobs.fields.dms.dms_unsold")}
initialValue={false}
>
<Switch/>
</Form.Item>
<Form.Item
name="dms_model_override"
label={t("jobs.fields.dms.dms_model_override")}
initialValue={false}
>
<Switch/>
</Form.Item>
</Space>
</div>
)}
<Form.Item
name="story"
label={t("jobs.fields.dms.story")}
rules={[
{
required: true,
},
]}
>
{t("jobs.actions.dms.post")}
</Button>
</Space>
);
}}
</Form.Item>
</Form>
</Card>
);
<Input.TextArea maxLength={240}/>
</Form.Item>
<Divider/>
<Space size="large" wrap align="center">
<Statistic
title={t("jobs.fields.ded_amt")}
value={Dinero(
job.job_totals.totals.custPayable.deductible
).toFormat()}
/>
<Statistic
title={t("jobs.labels.total_cust_payable")}
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
/>
</Space>
<Form.List name={["payers"]}>
{(fields, {add, remove}) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space wrap>
<Form.Item
label={t("jobs.fields.dms.payer.name")}
key={`${index}name`}
name={[field.name, "name"]}
rules={[
{
required: true,
},
]}
>
<Select
style={{minWidth: "15rem"}}
onSelect={(value) => handlePayerSelect(value, index)}
>
{bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.map((payer) => (
<Select.Option key={payer.name}>
{payer.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.dms_acctnumber")}
key={`${index}dms_acctnumber`}
name={[field.name, "dms_acctnumber"]}
rules={[
{
required: true,
},
]}
>
<Input disabled/>
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.amount")}
key={`${index}amount`}
name={[field.name, "amount"]}
rules={[
{
required: true,
},
]}
>
<CurrencyInput min={0}/>
</Form.Item>
<Form.Item
label={
<div>
{t("jobs.fields.dms.payer.controlnumber")}{" "}
<Dropdown
menu={<Menu
items={bodyshop.cdk_configuration.controllist?.map((key, idx) => ({
key: idx,
label: key.name,
onClick: () => {
form.setFieldsValue({
payers: form.getFieldValue("payers").map((row, mapIndex) => {
if (index !== mapIndex) return row;
return {
...row,
controlnumber: key.controlnumber,
};
}),
});
},
}))}
/>}
>
<a href=" #" onClick={(e) => e.preventDefault()}>
<DownOutlined/>
</a>
</Dropdown>
</div>
}
key={`${index}controlnumber`}
name={[field.name, "controlnumber"]}
rules={[
{
required: true,
},
]}
>
<Input/>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const payers = form.getFieldValue("payers");
const row = payers && payers[index];
const cdkPayer =
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find(
(i) => i && row && i.name === row.name
);
if (
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)
)
return (
<div>
{cdkPayer &&
t(`jobs.fields.${cdkPayer?.control_type}`)}
</div>
);
else if (
i18n.exists(
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
)
) {
return (
<div>
{cdkPayer &&
t(
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
)}
</div>
);
} else {
return null;
}
}}
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</Space>
</Form.Item>
))}
<Form.Item>
<Button
disabled={!(fields.length < 3)}
onClick={() => {
if (fields.length < 3) add();
}}
style={{width: "100%"}}
>
{t("jobs.actions.dms.addpayer")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item shouldUpdate>
{() => {
//Perform Calculation to determine discrepancy.
let totalAllocated = Dinero();
const payers = form.getFieldValue("payers");
payers &&
payers.forEach((payer) => {
totalAllocated = totalAllocated.add(
Dinero({amount: Math.round((payer?.amount || 0) * 100)})
);
});
const totals =
socket.allocationsSummary &&
socket.allocationsSummary.reduce(
(acc, val) => {
return {
totalSale: acc.totalSale.add(Dinero(val.sale)),
totalCost: acc.totalCost.add(Dinero(val.cost)),
};
},
{
totalSale: Dinero(),
totalCost: Dinero(),
}
);
const discrep = totals
? totals.totalSale.subtract(totalAllocated)
: Dinero();
return (
<Space size="large" wrap align="center">
<Statistic
title={t("jobs.labels.subtotal")}
value={(totals ? totals.totalSale : Dinero()).toFormat()}
/>
<Typography.Title>-</Typography.Title>
<Statistic
title={t("jobs.labels.dms.totalallocated")}
value={totalAllocated.toFormat()}
/>
<Typography.Title>=</Typography.Title>
<Statistic
title={t("jobs.labels.dms.notallocated")}
valueStyle={{
color: discrep.getAmount() === 0 ? "green" : "red",
}}
value={discrep.toFormat()}
/>
<Button
disabled={
!socket.allocationsSummary || discrep.getAmount() !== 0
}
htmlType="submit"
>
{t("jobs.actions.dms.post")}
</Button>
</Space>
);
}}
</Form.Item>
</Form>
</Card>
);
}

View File

@@ -1,267 +1,269 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import {
Button,
Divider,
Dropdown,
Form,
Input,
Menu,
Select,
Space,
Tabs,
Upload,
} from "antd";
import {UploadOutlined, UserAddOutlined} from "@ant-design/icons";
import {Button, Divider, Dropdown, Form, Input, Menu, Select, Space, Tabs, Upload,} from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectEmailConfig} from "../../redux/email/email.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {CreateExplorerLinkForJob} from "../../utils/localmedia";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
emailConfig: selectEmailConfig,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
emailConfig: selectEmailConfig,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(EmailOverlayComponent);
export function EmailOverlayComponent({
emailConfig,
form,
selectedMediaState,
bodyshop,
currentUser,
}) {
const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({
to: _.uniq([
...form.getFieldValue("to"),
...(typeof email === "string" ? [email] : email),
]),
});
};
const handle_CC_Click = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({
cc: _.uniq([
...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email),
]),
});
};
emailConfig,
form,
selectedMediaState,
bodyshop,
currentUser,
}) {
const {t} = useTranslation();
const handleClick = ({item, key, keyPath}) => {
const email = item.props.value;
form.setFieldsValue({
to: _.uniq([
...form.getFieldValue("to"),
...(typeof email === "string" ? [email] : email),
]),
});
};
const handle_CC_Click = ({item, key, keyPath}) => {
const email = item.props.value;
form.setFieldsValue({
cc: _.uniq([
...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email),
]),
});
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
{bodyshop.md_to_emails.map((e, idx) => (
<Menu.Item value={e.emails} key={idx + "group"}>
{e.label}
</Menu.Item>
))}
</Menu>
</div>
);
const menuCC = (
<div>
<Menu onClick={handle_CC_Click}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
{bodyshop.md_to_emails.map((e, idx) => (
<Menu.Item value={e.emails} key={idx + "group"}>
{e.label}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
<Form.Item
label={t("emails.fields.from")}
name="from"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select>
<Select.Option key={currentUser.email}>
{currentUser.email}
</Select.Option>
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
{bodyshop.md_from_emails &&
bodyshop.md_from_emails.map((e) => (
<Select.Option key={e}>{e}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={
<Space>
{t("emails.fields.to")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="to"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item
label={
<Space>
{t("emails.fields.cc")}
<Dropdown overlay={menuCC}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="cc"
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item
label={t("emails.fields.subject")}
name="subject"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Divider>{t("emails.labels.preview")}</Divider>
{bodyshop.attach_pdf_to_email && (
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
)}
<Form.Item shouldUpdate>
{() => {
return (
<div
style={{
padding: "1rem",
backgroundColor: "lightgray",
borderLeft: "6px solid #2196F3",
}}
dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }}
const menu = (
<Menu
onClick={handleClick}
items={[
...bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => ({
key: idx,
label: `${e.first_name} ${e.last_name}`,
value: e.user_email,
})),
...bodyshop.md_to_emails.map((e, idx) => ({
key: idx + "group",
label: e.label,
value: e.emails,
})),
]}
/>
);
}}
</Form.Item>
);
<Tabs>
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
<EmailDocumentsComponent
selectedMediaState={selectedMediaState}
form={form}
/>
</Tabs.TabPane>
const menuCC = (
<div>
<Menu
onClick={handle_CC_Click}
items={[
...bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => ({
key: idx,
label: `${e.first_name} ${e.last_name}`,
value: e.user_email,
})),
...bodyshop.md_to_emails.map((e, idx) => ({
key: idx + "group",
label: e.label,
value: e.emails,
})),
]}
/>
</div>
);
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>
<Button>{t("documents.labels.openinexplorer")}</Button>
</a>
)}
<Form.Item
name="fileList"
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
}}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
const totalSize = value.reduce(
(acc, val) => (acc = acc + val.size),
0
);
const limit =
10485760 - new Blob([form.getFieldValue("html")]).size;
if (totalSize > limit) {
return Promise.reject(t("general.errors.sizelimit"));
}
return Promise.resolve();
},
}),
]}
>
<Upload.Dragger
beforeUpload={Upload.LIST_IGNORE}
multiple
listType="picture-card"
return (
<div>
<Form.Item
label={t("emails.fields.from")}
name="from"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<>
<p className="ant-upload-drag-icon">
<UploadOutlined />
</p>
<p className="ant-upload-text">
Click or drag files to this area to upload.
</p>
</>
</Upload.Dragger>
</Form.Item>
</Tabs.TabPane>
</Tabs>
</div>
);
<Select>
<Select.Option key={currentUser.email}>
{currentUser.email}
</Select.Option>
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
{bodyshop.md_from_emails &&
bodyshop.md_from_emails.map((e) => (
<Select.Option key={e}>{e}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={
<Space>
{t("emails.fields.to")}
<Dropdown menu={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined/>
</a>
</Dropdown>
</Space>
}
name="to"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select mode="tags" tokenSeparators={[",", ";"]}/>
</Form.Item>
<Form.Item
label={
<Space>
{t("emails.fields.cc")}
<Dropdown menu={menuCC}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined/>
</a>
</Dropdown>
</Space>
}
name="cc"
>
<Select mode="tags" tokenSeparators={[",", ";"]}/>
</Form.Item>
<Form.Item
label={t("emails.fields.subject")}
name="subject"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input/>
</Form.Item>
<Divider>{t("emails.labels.preview")}</Divider>
{bodyshop.attach_pdf_to_email && (
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
)}
<Form.Item shouldUpdate>
{() => {
return (
<div
style={{
padding: "1rem",
backgroundColor: "lightgray",
borderLeft: "6px solid #2196F3",
}}
dangerouslySetInnerHTML={{__html: form.getFieldValue("html")}}
/>
);
}}
</Form.Item>
<Tabs
defaultActiveKey="documents"
items={[
{
key: "documents",
tab: t("emails.labels.documents"),
children: (
<EmailDocumentsComponent
selectedMediaState={selectedMediaState}
form={form}
/>
),
},
{
key: "attachments",
tab: t("emails.labels.attachments"),
children: (
<>
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
<a href={CreateExplorerLinkForJob({jobid: emailConfig.jobid})}>
<Button>{t("documents.labels.openinexplorer")}</Button>
</a>
)}
<Form.Item
name="fileList"
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
}}
rules={[
({getFieldValue}) => ({
validator(rule, value) {
const totalSize = value.reduce(
(acc, val) => (acc = acc + val.size),
0
);
const limit =
10485760 - new Blob([form.getFieldValue("html")]).size;
if (totalSize > limit) {
return Promise.reject(t("general.errors.sizelimit"));
}
return Promise.resolve();
},
}),
]}
>
<Upload.Dragger
beforeUpload={Upload.LIST_IGNORE}
multiple
listType="picture-card"
>
<>
<p className="ant-upload-drag-icon">
<UploadOutlined/>
</p>
<p className="ant-upload-text">
Click or drag files to this area to upload.
</p>
</>
</Upload.Dragger>
</Form.Item>
</>
),
},
]}
/>
</div>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,48 +1,45 @@
import React from "react";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { Timeline } from "antd";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import {Timeline} from "antd";
import {useTranslation} from "react-i18next";
import {DateTimeFormatter} from "../../utils/DateFormatter";
export default function ScheduleExistingAppointmentsList({
existingAppointments,
}) {
const { t } = useTranslation();
if (existingAppointments.loading) return <LoadingSpinner />;
if (existingAppointments.error)
existingAppointments,
}) {
const {t} = useTranslation();
if (existingAppointments.loading) return <LoadingSpinner/>;
if (existingAppointments.error)
return (
<AlertComponent
message={existingAppointments.error.message}
type="error"
/>
);
return (
<AlertComponent
message={existingAppointments.error.message}
type="error"
/>
<div>
{t("appointments.labels.priorappointments")}
<Timeline
items={
existingAppointments.data
? existingAppointments.data.appointments.map((item) => ({
key: item.id,
color: item.canceled ? "red" : item.arrived ? "green" : "blue",
label: (
<>
{item.canceled
? t("appointments.labels.cancelledappointment")
: item.arrived
? t("appointments.labels.arrivedon")
: t("appointments.labels.scheduledfor")}
<DateTimeFormatter>{item.start}</DateTimeFormatter>
</>
),
}))
: []
}
/></div>
);
return (
<div>
{t("appointments.labels.priorappointments")}
<Timeline>
{existingAppointments.data
? existingAppointments.data.appointments.map((item) => {
return (
<Timeline.Item
key={item.id}
color={
item.canceled ? "red" : item.arrived ? "green" : "blue"
}
>
{item.canceled
? t("appointments.labels.cancelledappointment")
: item.arrived
? t("appointments.labels.arrivedon")
: t("appointments.labels.scheduledfor")}
<DateTimeFormatter>{item.start}</DateTimeFormatter>
</Timeline.Item>
);
})
: null}
</Timeline>
</div>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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