IO-3020 IO-3036 Update ESLint. Add LockWrapper for Header. Add Blur Wrapper.
This commit is contained in:
@@ -3,10 +3,19 @@ import pluginJs from "@eslint/js";
|
|||||||
import pluginReact from "eslint-plugin-react";
|
import pluginReact from "eslint-plugin-react";
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.Config[]} */
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ files: ["**/*.{js,mjs,cjs,jsx}"] },
|
{
|
||||||
|
files: ["**/*.{js,mjs,cjs,jsx}"]
|
||||||
|
},
|
||||||
{ languageOptions: { globals: globals.browser } },
|
{ languageOptions: { globals: globals.browser } },
|
||||||
pluginJs.configs.recommended,
|
pluginJs.configs.recommended,
|
||||||
pluginReact.configs.flat.recommended, // This is not a plugin object, but a shareable config object
|
{
|
||||||
pluginReact.configs.flat["jsx-runtime"] // Add this if you are using React 17+
|
...pluginReact.configs.flat.recommended,
|
||||||
|
rules: {
|
||||||
|
...pluginReact.configs.flat.recommended.rules,
|
||||||
|
"react/prop-types": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pluginReact.configs.flat["jsx-runtime"]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -173,3 +173,9 @@
|
|||||||
.muted-button:hover {
|
.muted-button:hover {
|
||||||
color: darkgrey;
|
color: darkgrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blur-feature {
|
||||||
|
filter: blur(5px);
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import Dinero from "dinero.js";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { HasFeatureAccess } from "./feature-wrapper.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
const blurringProps = {
|
||||||
|
filter: "blur(6px)"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BlurWrapper({
|
||||||
|
bodyshop,
|
||||||
|
featureName,
|
||||||
|
styleProp = "style",
|
||||||
|
valueProp = "value",
|
||||||
|
overrideValue = true,
|
||||||
|
overrideValueFunction,
|
||||||
|
children
|
||||||
|
}) {
|
||||||
|
if (!HasFeatureAccess({ featureName, bodyshop })) {
|
||||||
|
const childrenWithBlurProps = React.Children.map(children, (child) => {
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
//Clone the child, and spread in our props to overwrite it.
|
||||||
|
let newValueProp;
|
||||||
|
if (!overrideValue) {
|
||||||
|
newValueProp = child.props[valueProp];
|
||||||
|
} else {
|
||||||
|
if (typeof overrideValueFunction === "function") {
|
||||||
|
newValueProp = overrideValueFunction();
|
||||||
|
} else if (overrideValueFunction === "RandomDinero") {
|
||||||
|
newValueProp = RandomDinero();
|
||||||
|
} else {
|
||||||
|
newValueProp = "This is some random text. Nothing interesting here.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
[valueProp]: newValueProp,
|
||||||
|
[styleProp]: { ...child.props[styleProp], ...blurringProps }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
return childrenWithBlurProps;
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
export default connect(mapStateToProps, null)(BlurWrapper);
|
||||||
|
|
||||||
|
function RandomDinero() {
|
||||||
|
return Dinero({ amount: Math.round(Math.exp(Math.random() * 100, 2)) }).toFormat();
|
||||||
|
}
|
||||||
@@ -11,24 +11,36 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps }) {
|
function FeatureWrapper({ bodyshop, featureName, noauth, blurContent = false, children, ...restProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (HasFeatureAccess({ featureName, bodyshop })) return children;
|
if (HasFeatureAccess({ featureName, bodyshop })) return children;
|
||||||
|
|
||||||
return (
|
if (blurContent) {
|
||||||
noauth || (
|
const childrenWithBlurProps = React.Children.map(children, (child) => {
|
||||||
<AlertComponent
|
// Checking isValidElement is the safe way and avoids a
|
||||||
message={t("general.messages.nofeatureaccess", {
|
// typescript error too.
|
||||||
app: InstanceRenderManager({
|
if (React.isValidElement(child)) {
|
||||||
imex: "$t(titles.imexonline)",
|
return React.cloneElement(child, { blur: true });
|
||||||
rome: "$t(titles.romeonline)"
|
}
|
||||||
})
|
return child;
|
||||||
})}
|
});
|
||||||
type="warning"
|
return childrenWithBlurProps;
|
||||||
/>
|
} else {
|
||||||
)
|
return (
|
||||||
);
|
noauth || (
|
||||||
|
<AlertComponent
|
||||||
|
message={t("general.messages.nofeatureaccess", {
|
||||||
|
app: InstanceRenderManager({
|
||||||
|
imex: "$t(titles.imexonline)",
|
||||||
|
rome: "$t(titles.romeonline)"
|
||||||
|
})
|
||||||
|
})}
|
||||||
|
type="warning"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HasFeatureAccess({ featureName, bodyshop }) {
|
export function HasFeatureAccess({ featureName, bodyshop }) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import Icon, {
|
|||||||
UserOutlined
|
UserOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Layout, Menu } from "antd";
|
import { Layout, Menu, Space } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BsKanban } from "react-icons/bs";
|
import { BsKanban } from "react-icons/bs";
|
||||||
@@ -44,6 +44,7 @@ import { signOutStart } from "../../redux/user/user.actions";
|
|||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
|
import LockWrapper from "../lock-wrapper/locker-wrapper.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -128,33 +129,39 @@ function Header({
|
|||||||
|
|
||||||
const accountingChildren = [];
|
const accountingChildren = [];
|
||||||
|
|
||||||
if (
|
accountingChildren.push(
|
||||||
InstanceRenderManager({
|
{
|
||||||
imex: HasFeatureAccess({ featureName: "bills", bodyshop }),
|
key: "bills",
|
||||||
rome: "USE_IMEX"
|
id: "header-accounting-bills",
|
||||||
})
|
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||||
) {
|
label: (
|
||||||
accountingChildren.push(
|
<Link to="/manage/bills">
|
||||||
{
|
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||||
key: "bills",
|
{t("menus.header.bills")}
|
||||||
id: "header-accounting-bills",
|
</LockWrapper>
|
||||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
</Link>
|
||||||
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "enterbills",
|
key: "enterbills",
|
||||||
id: "header-accounting-enterbills",
|
id: "header-accounting-enterbills",
|
||||||
icon: <Icon component={GiPayMoney} />,
|
icon: <Icon component={GiPayMoney} />,
|
||||||
label: t("menus.header.enterbills"),
|
label: (
|
||||||
onClick: () => {
|
<Space>
|
||||||
|
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||||
|
{t(t("menus.header.enterbills"))}
|
||||||
|
</LockWrapper>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
HasFeatureAccess({ featureName: "bills", bodyshop }) &&
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {}
|
context: {}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
if (Simple_Inventory.treatment === "on") {
|
if (Simple_Inventory.treatment === "on") {
|
||||||
accountingChildren.push(
|
accountingChildren.push(
|
||||||
@@ -169,36 +176,41 @@ function Header({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
InstanceRenderManager({
|
accountingChildren.push(
|
||||||
imex: HasFeatureAccess({ featureName: "payments", bodyshop }),
|
{
|
||||||
rome: "USE_IMEX"
|
type: "divider"
|
||||||
})
|
},
|
||||||
) {
|
{
|
||||||
accountingChildren.push(
|
key: "allpayments",
|
||||||
{
|
id: "header-accounting-allpayments",
|
||||||
type: "divider"
|
icon: <BankFilled />,
|
||||||
},
|
label: (
|
||||||
{
|
<Link to="/manage/payments">
|
||||||
key: "allpayments",
|
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
||||||
id: "header-accounting-allpayments",
|
{t("menus.header.allpayments")}
|
||||||
icon: <BankFilled />,
|
</LockWrapper>
|
||||||
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
</Link>
|
||||||
},
|
)
|
||||||
{
|
},
|
||||||
key: "enterpayments",
|
{
|
||||||
id: "header-accounting-enterpayments",
|
key: "enterpayments",
|
||||||
icon: <Icon component={FaCreditCard} />,
|
id: "header-accounting-enterpayments",
|
||||||
label: t("menus.header.enterpayment"),
|
icon: <Icon component={FaCreditCard} />,
|
||||||
onClick: () => {
|
label: (
|
||||||
|
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.enterpayment")}
|
||||||
|
</LockWrapper>
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: null
|
context: null
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
if (ImEXPay.treatment === "on") {
|
if (ImEXPay.treatment === "on") {
|
||||||
accountingChildren.push({
|
accountingChildren.push({
|
||||||
@@ -215,39 +227,44 @@ function Header({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
accountingChildren.push(
|
||||||
InstanceRenderManager({
|
{
|
||||||
imex: HasFeatureAccess({ featureName: "timetickets", bodyshop }),
|
type: "divider"
|
||||||
rome: "USE_IMEX"
|
},
|
||||||
})
|
{
|
||||||
) {
|
key: "timetickets",
|
||||||
accountingChildren.push(
|
id: "header-accounting-timetickets",
|
||||||
{
|
icon: <FieldTimeOutlined />,
|
||||||
type: "divider"
|
label: (
|
||||||
},
|
<Link to="/manage/timetickets">
|
||||||
{
|
<LockWrapper featureName="timetickets" bodyshop={bodyshop}>
|
||||||
key: "timetickets",
|
{t("menus.header.timetickets")}
|
||||||
id: "header-accounting-timetickets",
|
</LockWrapper>
|
||||||
icon: <FieldTimeOutlined />,
|
</Link>
|
||||||
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
|
)
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
|
||||||
accountingChildren.push({
|
|
||||||
key: "ttapprovals",
|
|
||||||
id: "header-accounting-ttapprovals",
|
|
||||||
icon: <FieldTimeOutlined />,
|
|
||||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
accountingChildren.push(
|
);
|
||||||
{
|
|
||||||
key: "entertimetickets",
|
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||||
icon: <Icon component={GiPlayerTime} />,
|
accountingChildren.push({
|
||||||
label: t("menus.header.entertimeticket"),
|
key: "ttapprovals",
|
||||||
id: "header-accounting-entertimetickets",
|
id: "header-accounting-ttapprovals",
|
||||||
onClick: () => {
|
icon: <FieldTimeOutlined />,
|
||||||
|
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
accountingChildren.push(
|
||||||
|
{
|
||||||
|
key: "entertimetickets",
|
||||||
|
icon: <Icon component={GiPlayerTime} />,
|
||||||
|
label: (
|
||||||
|
<LockWrapper featureName="timetickets" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.entertimeticket")}
|
||||||
|
</LockWrapper>
|
||||||
|
),
|
||||||
|
id: "header-accounting-entertimetickets",
|
||||||
|
onClick: () => {
|
||||||
|
HasFeatureAccess({ featureName: "timetickets", bodyshop }) &&
|
||||||
setTimeTicketContext({
|
setTimeTicketContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {
|
context: {
|
||||||
@@ -256,19 +273,24 @@ function Header({
|
|||||||
: currentUser.email
|
: currentUser.email
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "divider"
|
|
||||||
}
|
}
|
||||||
);
|
},
|
||||||
}
|
{
|
||||||
|
type: "divider"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const accountingExportChildren = [
|
const accountingExportChildren = [
|
||||||
{
|
{
|
||||||
key: "receivables",
|
key: "receivables",
|
||||||
id: "header-accounting-receivables",
|
id: "header-accounting-receivables",
|
||||||
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
|
label: (
|
||||||
|
<Link to="/manage/accounting/receivables">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.accounting-receivables")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -276,7 +298,13 @@ function Header({
|
|||||||
accountingExportChildren.push({
|
accountingExportChildren.push({
|
||||||
key: "payables",
|
key: "payables",
|
||||||
id: "header-accounting-payables",
|
id: "header-accounting-payables",
|
||||||
label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
|
label: (
|
||||||
|
<Link to="/manage/accounting/payables">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.accounting-payables")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +312,13 @@ function Header({
|
|||||||
accountingExportChildren.push({
|
accountingExportChildren.push({
|
||||||
key: "payments",
|
key: "payments",
|
||||||
id: "header-accounting-payments",
|
id: "header-accounting-payments",
|
||||||
label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
|
label: (
|
||||||
|
<Link to="/manage/accounting/payments">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.accounting-payments")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,24 +329,27 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "exportlogs",
|
key: "exportlogs",
|
||||||
id: "header-accounting-exportlogs",
|
id: "header-accounting-exportlogs",
|
||||||
label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
|
label: (
|
||||||
|
<Link to="/manage/accounting/exportlogs">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.export-logs")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
accountingChildren.push({
|
||||||
InstanceRenderManager({
|
key: "accountingexport",
|
||||||
imex: HasFeatureAccess({ featureName: "export", bodyshop }),
|
id: "header-accounting-export",
|
||||||
rome: "USE_IMEX"
|
icon: <ExportOutlined />,
|
||||||
})
|
label: (
|
||||||
) {
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
accountingChildren.push({
|
{t("menus.header.export")}
|
||||||
key: "accountingexport",
|
</LockWrapper>
|
||||||
id: "header-accounting-export",
|
),
|
||||||
icon: <ExportOutlined />,
|
children: accountingExportChildren
|
||||||
label: t("menus.header.export"),
|
});
|
||||||
children: accountingExportChildren
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
@@ -381,36 +418,35 @@ function Header({
|
|||||||
icon: <ScheduleOutlined />,
|
icon: <ScheduleOutlined />,
|
||||||
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
|
||||||
imex: HasFeatureAccess({ featureName: "visualboard", bodyshop }),
|
|
||||||
rome: "USE_IMEX"
|
|
||||||
})
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "productionboard",
|
|
||||||
id: "header-production-board",
|
|
||||||
icon: <Icon component={BsKanban} />,
|
|
||||||
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
|
|
||||||
...(InstanceRenderManager({
|
{
|
||||||
imex: HasFeatureAccess({ featureName: "scoreboard", bodyshop }),
|
key: "productionboard",
|
||||||
rome: "USE_IMEX"
|
id: "header-production-board",
|
||||||
})
|
icon: <Icon component={BsKanban} />,
|
||||||
? [
|
label: (
|
||||||
{
|
<Link to="/manage/production/board">
|
||||||
type: "divider"
|
<LockWrapper featureName="visualboard" bodyshop={bodyshop}>
|
||||||
},
|
{t("menus.header.productionboard")}
|
||||||
{
|
</LockWrapper>
|
||||||
key: "scoreboard",
|
</Link>
|
||||||
id: "header-scoreboard",
|
)
|
||||||
icon: <LineChartOutlined />,
|
},
|
||||||
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
|
||||||
}
|
{
|
||||||
]
|
type: "divider"
|
||||||
: [])
|
},
|
||||||
|
{
|
||||||
|
key: "scoreboard",
|
||||||
|
id: "header-scoreboard",
|
||||||
|
icon: <LineChartOutlined />,
|
||||||
|
label: (
|
||||||
|
<Link to="/manage/scoreboard">
|
||||||
|
<LockWrapper featureName="scoreboard" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.scoreboard")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -433,39 +469,54 @@ function Header({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
{
|
||||||
imex: HasFeatureAccess({ featureName: "courtesycars", bodyshop }),
|
key: "ccs",
|
||||||
rome: "USE_IMEX"
|
id: "header-css",
|
||||||
})
|
icon: <CarFilled />,
|
||||||
? [
|
label: (
|
||||||
{
|
<LockWrapper featureName="courtesycars" bodyshop={bodyshop}>
|
||||||
key: "ccs",
|
{t("menus.header.courtesycars")}
|
||||||
id: "header-css",
|
</LockWrapper>
|
||||||
icon: <CarFilled />,
|
),
|
||||||
label: t("menus.header.courtesycars"),
|
children: [
|
||||||
children: [
|
{
|
||||||
{
|
key: "courtesycarsall",
|
||||||
key: "courtesycarsall",
|
id: "header-courtesycars-all",
|
||||||
id: "header-courtesycars-all",
|
icon: <CarFilled />,
|
||||||
icon: <CarFilled />,
|
label: (
|
||||||
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
<Link to="/manage/courtesycars">
|
||||||
},
|
<LockWrapper featureName="courtesycars" bodyshop={bodyshop}>
|
||||||
{
|
{t("menus.header.courtesycars-all")}
|
||||||
key: "contracts",
|
</LockWrapper>
|
||||||
id: "header-contracts",
|
</Link>
|
||||||
icon: <FileFilled />,
|
)
|
||||||
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
},
|
||||||
},
|
{
|
||||||
{
|
key: "contracts",
|
||||||
key: "newcontract",
|
id: "header-contracts",
|
||||||
id: "header-newcontract",
|
icon: <FileFilled />,
|
||||||
icon: <FileAddFilled />,
|
label: (
|
||||||
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
<Link to="/manage/courtesycars/contracts">
|
||||||
}
|
<LockWrapper featureName="courtesycars" bodyshop={bodyshop}>
|
||||||
]
|
{t("menus.header.courtesycars-contracts")}
|
||||||
}
|
</LockWrapper>
|
||||||
]
|
</Link>
|
||||||
: []),
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "newcontract",
|
||||||
|
id: "header-newcontract",
|
||||||
|
icon: <FileAddFilled />,
|
||||||
|
label: (
|
||||||
|
<Link to="/manage/courtesycars/contracts/new">
|
||||||
|
<LockWrapper featureName="courtesycars" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.courtesycars-newcontract")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
...(accountingChildren.length > 0
|
...(accountingChildren.length > 0
|
||||||
? [
|
? [
|
||||||
@@ -484,19 +535,20 @@ function Header({
|
|||||||
icon: <PhoneOutlined />,
|
icon: <PhoneOutlined />,
|
||||||
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
|
||||||
imex: HasFeatureAccess({ featureName: "media", bodyshop }),
|
{
|
||||||
rome: "USE_IMEX"
|
key: "temporarydocs",
|
||||||
})
|
id: "header-temporarydocs",
|
||||||
? [
|
icon: <PaperClipOutlined />,
|
||||||
{
|
label: (
|
||||||
key: "temporarydocs",
|
<Link to="/manage/temporarydocs">
|
||||||
id: "header-temporarydocs",
|
<LockWrapper featureName="media" bodyshop={bodyshop}>
|
||||||
icon: <PaperClipOutlined />,
|
{t("menus.header.temporarydocs")}
|
||||||
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
</LockWrapper>
|
||||||
}
|
</Link>
|
||||||
]
|
)
|
||||||
: []),
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: "tasks",
|
key: "tasks",
|
||||||
id: "tasks",
|
id: "tasks",
|
||||||
@@ -562,19 +614,19 @@ function Header({
|
|||||||
icon: <Icon component={IoBusinessOutline} />,
|
icon: <Icon component={IoBusinessOutline} />,
|
||||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
|
||||||
imex: HasFeatureAccess({ featureName: "csi", bodyshop }),
|
{
|
||||||
rome: "USE_IMEX"
|
key: "shop-csi",
|
||||||
})
|
id: "header-shop-csi",
|
||||||
? [
|
icon: <Icon component={RiSurveyLine} />,
|
||||||
{
|
label: (
|
||||||
key: "shop-csi",
|
<Link to="/manage/shop/csi">
|
||||||
id: "header-shop-csi",
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
icon: <Icon component={RiSurveyLine} />,
|
{t("menus.header.shop_csi")}
|
||||||
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
</LockWrapper>
|
||||||
}
|
</Link>
|
||||||
]
|
)
|
||||||
: [])
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -630,7 +682,13 @@ function Header({
|
|||||||
key: "shiftclock",
|
key: "shiftclock",
|
||||||
id: "header-shiftclock",
|
id: "header-shiftclock",
|
||||||
icon: <Icon component={GiPlayerTime} />,
|
icon: <Icon component={GiPlayerTime} />,
|
||||||
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
label: (
|
||||||
|
<Link to="/manage/shiftclock">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.shiftclock")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import "./job-bills-total.styles.scss";
|
import "./job-bills-total.styles.scss";
|
||||||
|
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
|
||||||
|
|
||||||
export default function JobBillsTotalComponent({
|
export default function JobBillsTotalComponent({
|
||||||
loading,
|
loading,
|
||||||
@@ -18,7 +19,7 @@ export default function JobBillsTotalComponent({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (loading) return <LoadingSkeleton />;
|
if (loading) return <LoadingSkeleton />;
|
||||||
if (!!!jobTotals) {
|
if (!jobTotals) {
|
||||||
if (showWarning && warningCallback && typeof warningCallback === "function") {
|
if (showWarning && warningCallback && typeof warningCallback === "function") {
|
||||||
warningCallback({ key: "bills", warning: t("jobs.errors.nofinancial") });
|
warningCallback({ key: "bills", warning: t("jobs.errors.nofinancial") });
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ export default function JobBillsTotalComponent({
|
|||||||
.add(
|
.add(
|
||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: Dinero(),
|
imex: Dinero(),
|
||||||
rome: Dinero(totals.additional.additionalCosts),
|
rome: Dinero(totals.additional.additionalCosts)
|
||||||
})
|
})
|
||||||
); // Additional costs were captured for Rome, but not imex.
|
); // Additional costs were captured for Rome, but not imex.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { LockOutlined } from "@ant-design/icons";
|
||||||
|
import { Space } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||||
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
recentItems: selectRecentItems,
|
||||||
|
selectedHeader: selectSelectedHeader,
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
const LockWrapper = ({ featureName, bodyshop, children, disabled = true }) => {
|
||||||
|
let renderedChildren = children;
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
renderedChildren = React.Children.map(children, (child) => {
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
disabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
{!HasFeatureAccess({ featureName: featureName, bodyshop }) && <LockOutlined style={{ color: "tomato" }} />}
|
||||||
|
{renderedChildren}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default connect(mapStateToProps, null)(LockWrapper);
|
||||||
Reference in New Issue
Block a user