IO-3020 IO-3036 Update ESLint. Add LockWrapper for Header. Add Blur Wrapper.

This commit is contained in:
Patrick Fic
2024-11-29 14:38:56 -08:00
parent 4433f0f57f
commit 801cd724ac
7 changed files with 393 additions and 212 deletions

View File

@@ -3,10 +3,19 @@ import pluginJs from "@eslint/js";
import pluginReact from "eslint-plugin-react";
/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ["**/*.{js,mjs,cjs,jsx}"] },
{
files: ["**/*.{js,mjs,cjs,jsx}"]
},
{ languageOptions: { globals: globals.browser } },
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"]
];

View File

@@ -173,3 +173,9 @@
.muted-button:hover {
color: darkgrey;
}
.blur-feature {
filter: blur(5px);
user-select: none;
pointer-events: none;
}

View File

@@ -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();
}

View File

@@ -11,24 +11,36 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps }) {
function FeatureWrapper({ bodyshop, featureName, noauth, blurContent = false, children, ...restProps }) {
const { t } = useTranslation();
if (HasFeatureAccess({ featureName, bodyshop })) return children;
return (
noauth || (
<AlertComponent
message={t("general.messages.nofeatureaccess", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
})
})}
type="warning"
/>
)
);
if (blurContent) {
const childrenWithBlurProps = React.Children.map(children, (child) => {
// Checking isValidElement is the safe way and avoids a
// typescript error too.
if (React.isValidElement(child)) {
return React.cloneElement(child, { blur: true });
}
return child;
});
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 }) {

View File

@@ -26,7 +26,7 @@ import Icon, {
UserOutlined
} from "@ant-design/icons";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd";
import { Layout, Menu, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
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 InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockWrapper from "../lock-wrapper/locker-wrapper.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -128,33 +129,39 @@ function Header({
const accountingChildren = [];
if (
InstanceRenderManager({
imex: HasFeatureAccess({ featureName: "bills", bodyshop }),
rome: "USE_IMEX"
})
) {
accountingChildren.push(
{
key: "bills",
id: "header-accounting-bills",
icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
},
{
key: "enterbills",
id: "header-accounting-enterbills",
icon: <Icon component={GiPayMoney} />,
label: t("menus.header.enterbills"),
onClick: () => {
accountingChildren.push(
{
key: "bills",
id: "header-accounting-bills",
icon: <Icon component={FaFileInvoiceDollar} />,
label: (
<Link to="/manage/bills">
<LockWrapper featureName="bills" bodyshop={bodyshop}>
{t("menus.header.bills")}
</LockWrapper>
</Link>
)
},
{
key: "enterbills",
id: "header-accounting-enterbills",
icon: <Icon component={GiPayMoney} />,
label: (
<Space>
<LockWrapper featureName="bills" bodyshop={bodyshop}>
{t(t("menus.header.enterbills"))}
</LockWrapper>
</Space>
),
onClick: () => {
HasFeatureAccess({ featureName: "bills", bodyshop }) &&
setBillEnterContext({
actions: {},
context: {}
});
}
}
);
}
}
);
if (Simple_Inventory.treatment === "on") {
accountingChildren.push(
@@ -169,36 +176,41 @@ function Header({
}
);
}
if (
InstanceRenderManager({
imex: HasFeatureAccess({ featureName: "payments", bodyshop }),
rome: "USE_IMEX"
})
) {
accountingChildren.push(
{
type: "divider"
},
{
key: "allpayments",
id: "header-accounting-allpayments",
icon: <BankFilled />,
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
},
{
key: "enterpayments",
id: "header-accounting-enterpayments",
icon: <Icon component={FaCreditCard} />,
label: t("menus.header.enterpayment"),
onClick: () => {
accountingChildren.push(
{
type: "divider"
},
{
key: "allpayments",
id: "header-accounting-allpayments",
icon: <BankFilled />,
label: (
<Link to="/manage/payments">
<LockWrapper featureName="payments" bodyshop={bodyshop}>
{t("menus.header.allpayments")}
</LockWrapper>
</Link>
)
},
{
key: "enterpayments",
id: "header-accounting-enterpayments",
icon: <Icon component={FaCreditCard} />,
label: (
<LockWrapper featureName="payments" bodyshop={bodyshop}>
{t("menus.header.enterpayment")}
</LockWrapper>
),
onClick: () => {
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
setPaymentContext({
actions: {},
context: null
});
}
}
);
}
}
);
if (ImEXPay.treatment === "on") {
accountingChildren.push({
@@ -215,39 +227,44 @@ function Header({
});
}
if (
InstanceRenderManager({
imex: HasFeatureAccess({ featureName: "timetickets", bodyshop }),
rome: "USE_IMEX"
})
) {
accountingChildren.push(
{
type: "divider"
},
{
key: "timetickets",
id: "header-accounting-timetickets",
icon: <FieldTimeOutlined />,
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(
{
type: "divider"
},
{
key: "timetickets",
id: "header-accounting-timetickets",
icon: <FieldTimeOutlined />,
label: (
<Link to="/manage/timetickets">
<LockWrapper featureName="timetickets" bodyshop={bodyshop}>
{t("menus.header.timetickets")}
</LockWrapper>
</Link>
)
}
accountingChildren.push(
{
key: "entertimetickets",
icon: <Icon component={GiPlayerTime} />,
label: t("menus.header.entertimeticket"),
id: "header-accounting-entertimetickets",
onClick: () => {
);
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",
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({
actions: {},
context: {
@@ -256,19 +273,24 @@ function Header({
: currentUser.email
}
});
}
},
{
type: "divider"
}
);
}
},
{
type: "divider"
}
);
const accountingExportChildren = [
{
key: "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({
key: "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({
key: "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",
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 (
InstanceRenderManager({
imex: HasFeatureAccess({ featureName: "export", bodyshop }),
rome: "USE_IMEX"
})
) {
accountingChildren.push({
key: "accountingexport",
id: "header-accounting-export",
icon: <ExportOutlined />,
label: t("menus.header.export"),
children: accountingExportChildren
});
}
accountingChildren.push({
key: "accountingexport",
id: "header-accounting-export",
icon: <ExportOutlined />,
label: (
<LockWrapper featureName="export" bodyshop={bodyshop}>
{t("menus.header.export")}
</LockWrapper>
),
children: accountingExportChildren
});
const menuItems = [
{
@@ -381,36 +418,35 @@ function Header({
icon: <ScheduleOutlined />,
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 }),
rome: "USE_IMEX"
})
? [
{
type: "divider"
},
{
key: "scoreboard",
id: "header-scoreboard",
icon: <LineChartOutlined />,
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
}
]
: [])
{
key: "productionboard",
id: "header-production-board",
icon: <Icon component={BsKanban} />,
label: (
<Link to="/manage/production/board">
<LockWrapper featureName="visualboard" bodyshop={bodyshop}>
{t("menus.header.productionboard")}
</LockWrapper>
</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 }),
rome: "USE_IMEX"
})
? [
{
key: "ccs",
id: "header-css",
icon: <CarFilled />,
label: t("menus.header.courtesycars"),
children: [
{
key: "courtesycarsall",
id: "header-courtesycars-all",
icon: <CarFilled />,
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
},
{
key: "contracts",
id: "header-contracts",
icon: <FileFilled />,
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
},
{
key: "newcontract",
id: "header-newcontract",
icon: <FileAddFilled />,
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
}
]
}
]
: []),
{
key: "ccs",
id: "header-css",
icon: <CarFilled />,
label: (
<LockWrapper featureName="courtesycars" bodyshop={bodyshop}>
{t("menus.header.courtesycars")}
</LockWrapper>
),
children: [
{
key: "courtesycarsall",
id: "header-courtesycars-all",
icon: <CarFilled />,
label: (
<Link to="/manage/courtesycars">
<LockWrapper featureName="courtesycars" bodyshop={bodyshop}>
{t("menus.header.courtesycars-all")}
</LockWrapper>
</Link>
)
},
{
key: "contracts",
id: "header-contracts",
icon: <FileFilled />,
label: (
<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
? [
@@ -484,19 +535,20 @@ function Header({
icon: <PhoneOutlined />,
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: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
}
]
: []),
{
key: "temporarydocs",
id: "header-temporarydocs",
icon: <PaperClipOutlined />,
label: (
<Link to="/manage/temporarydocs">
<LockWrapper featureName="media" bodyshop={bodyshop}>
{t("menus.header.temporarydocs")}
</LockWrapper>
</Link>
)
},
{
key: "tasks",
id: "tasks",
@@ -562,19 +614,19 @@ function Header({
icon: <Icon component={IoBusinessOutline} />,
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: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
}
]
: [])
{
key: "shop-csi",
id: "header-shop-csi",
icon: <Icon component={RiSurveyLine} />,
label: (
<Link to="/manage/shop/csi">
<LockWrapper featureName="export" bodyshop={bodyshop}>
{t("menus.header.shop_csi")}
</LockWrapper>
</Link>
)
}
]
},
{
@@ -630,7 +682,13 @@ function Header({
key: "shiftclock",
id: "header-shiftclock",
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>
)
}
]
: []),

View File

@@ -6,6 +6,7 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./job-bills-total.styles.scss";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
export default function JobBillsTotalComponent({
loading,
@@ -18,7 +19,7 @@ export default function JobBillsTotalComponent({
const { t } = useTranslation();
if (loading) return <LoadingSkeleton />;
if (!!!jobTotals) {
if (!jobTotals) {
if (showWarning && warningCallback && typeof warningCallback === "function") {
warningCallback({ key: "bills", warning: t("jobs.errors.nofinancial") });
}
@@ -97,7 +98,7 @@ export default function JobBillsTotalComponent({
.add(
InstanceRenderManager({
imex: Dinero(),
rome: Dinero(totals.additional.additionalCosts),
rome: Dinero(totals.additional.additionalCosts)
})
); // Additional costs were captured for Rome, but not imex.

View File

@@ -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);