IO-3020 IO-3036 Add upsell components to several components. Add upsell mask wrapper.

This commit is contained in:
Patrick Fic
2024-12-06 14:24:03 -08:00
parent 77e966dfe1
commit eaea73a955
25 changed files with 3001 additions and 2265 deletions

View File

@@ -11,7 +11,7 @@
<preset_collections/> <preset_collections/>
<framework>react-intl</framework> <framework>react-intl</framework>
<filename>bodyshop_translations.babel</filename> <filename>bodyshop_translations.babel</filename>
<source_root_dir>client</source_root_dir> <source_root_dir>client/src</source_root_dir>
<folder_node> <folder_node>
<name></name> <name></name>
<children> <children>
@@ -40921,6 +40921,27 @@
<folder_node> <folder_node>
<name>fields</name> <name>fields</name>
<children> <children>
<concept_node>
<name>accountingid</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>address</name> <name>address</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -52328,6 +52349,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>purchase_return_ratio_excel</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>purchase_return_ratio_grouped_by_vendor_detail</name> <name>purchase_return_ratio_grouped_by_vendor_detail</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -52853,6 +52895,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_loss_jobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>unclaimed_hrs</name> <name>unclaimed_hrs</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -59609,6 +59672,589 @@
</folder_node> </folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>upsell</name>
<children>
<folder_node>
<name>cta</name>
<children>
<concept_node>
<name>learnmore</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>messages</name>
<children>
<folder_node>
<name>audit</name>
<children>
<folder_node>
<name>general</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>bills</name>
<children>
<folder_node>
<name>autoreconcile</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>general</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>lifecycle</name>
<children>
<folder_node>
<name>general</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>media</name>
<children>
<folder_node>
<name>general</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>mobile</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>scheduling</name>
<children>
<folder_node>
<name>datepicker</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>general</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>hrsdelta</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>timetickets</name>
<children>
<folder_node>
<name>allocations</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>general</name>
<children>
<concept_node>
<name>subtitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>user</name> <name>user</name>
<children> <children>

View File

@@ -18,7 +18,7 @@ import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import UpsellComponent from "../upsell/upsell.component"; import UpsellComponent from "../upsell/upsell.component";
import upsellEnum from "../upsell/upsell.enum"; import { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -240,7 +240,7 @@ export function BillsListTableComponent({
onChange={handleTableChange} onChange={handleTableChange}
locale={{ locale={{
...(!hasBillsAccess && { ...(!hasBillsAccess && {
emptyText: <UpsellComponent upsell={upsellEnum.bills.table} /> emptyText: <UpsellComponent upsell={upsellEnum.bills.general} />
}) })
}} }}
/> />

View File

@@ -1,24 +1,27 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Col, Row, Table, Tag } from "antd"; import { Button, Card, Col, Row, Table, Tag } from "antd";
import { SyncOutlined } from "@ant-design/icons";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors"; import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser currentUser: selectCurrentUser,
bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail); export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail);
export function JobAuditTrail({ currentUser, jobId }) { export function JobAuditTrail({ bodyshop, currentUser, jobId }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, { const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { jobid: jobId }, variables: { jobid: jobId },
@@ -137,11 +140,17 @@ export function JobAuditTrail({ currentUser, jobId }) {
) )
} }
]; ];
const hasAuditAccess = HasFeatureAccess({ bodyshop, featureName: "audit" });
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{ {!hasAuditAccess && (
//TODO:Upsell <Col span={24}>
} <Card>
<UpsellComponent upsell={upsellEnum.audit.general} disableMask />
</Card>
</Col>
)}
<Col span={24}> <Col span={24}>
<Card <Card
title={t("jobs.labels.audit")} title={t("jobs.labels.audit")}

View File

@@ -7,8 +7,21 @@ 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"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
export default function JobBillsTotalComponent({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobBillsTotalComponent({
bodyshop,
loading, loading,
bills, bills,
partsOrders, partsOrders,
@@ -120,10 +133,10 @@ export default function JobBillsTotalComponent({
warningCallback({ key: "cm", warning: t("jobs.labels.outstanding_credit_memos") }); warningCallback({ key: "cm", warning: t("jobs.labels.outstanding_credit_memos") });
} }
} }
const hasBillsAccess = HasFeatureAccess({ bodyshop, featureName: "bills" });
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col md={24} lg={18}> <Col {...(hasBillsAccess ? { md: 24, lg: 18 } : { span: 12 })}>
<Card title={t("jobs.labels.jobtotals")} style={{ height: "100%" }}> <Card title={t("jobs.labels.jobtotals")} style={{ height: "100%" }}>
<Space wrap size="large"> <Space wrap size="large">
<Tooltip <Tooltip
@@ -240,9 +253,6 @@ export default function JobBillsTotalComponent({
</BlurWrapperComponent> </BlurWrapperComponent>
</Tooltip> </Tooltip>
</Space> </Space>
{
//TODO:Upsell
}
{showWarning && {showWarning &&
(discrepWithCms.getAmount() !== 0 || (discrepWithCms.getAmount() !== 0 ||
discrepWithLbrAdj.getAmount() !== 0 || discrepWithLbrAdj.getAmount() !== 0 ||
@@ -321,11 +331,17 @@ export default function JobBillsTotalComponent({
{showWarning && calculatedCreditsNotReceived.getAmount() > 0 && ( {showWarning && calculatedCreditsNotReceived.getAmount() > 0 && (
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_credit_memos")} /> <Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_credit_memos")} />
)} )}
{
//TODO:Upsell
}
</Card> </Card>
</Col> </Col>
{!hasBillsAccess && (
<Col span={6}>
<Card style={{ height: "100%" }}>
<UpsellComponent upsell={upsellEnum.bills.autoreconcile} disableMask />
</Card>
</Col>
)}
</Row> </Row>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobBillsTotalComponent);

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Checkbox, Form, notification, Popover, Tooltip } from "antd"; import { Button, Form, notification, Popover, Tooltip } from "antd";
import axios from "axios"; import axios from "axios";
import { t } from "i18next"; import { t } from "i18next";
import React, { useState } from "react"; import React, { useState } from "react";

View File

@@ -9,6 +9,7 @@ import { useTranslation } from "react-i18next";
import "./job-lifecycle.styles.scss"; import "./job-lifecycle.styles.scss";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
// show text on bar if text can fit // show text on bar if text can fit
export function JobLifecycleComponent({ job, statuses, ...rest }) { export function JobLifecycleComponent({ job, statuses, ...rest }) {
@@ -137,9 +138,10 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
} }
style={{ width: "100%" }} style={{ width: "100%" }}
> >
{ {" "}
//TODO:Upsell <Card type="inner" style={{ marginTop: "10px" }}>
} <UpsellComponent upsell={upsellEnum.lifecycle.general} />
</Card>
<BlurWrapperComponent featureName="lifecycle"> <BlurWrapperComponent featureName="lifecycle">
<div <div
id="bar-container" id="bar-container"

View File

@@ -4,10 +4,9 @@ import AlertComponent from "../alert/alert.component";
import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.container"; import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.container";
import BillsListTable from "../bills-list-table/bills-list-table.component"; import BillsListTable from "../bills-list-table/bills-list-table.component";
import JobBillsTotal from "../job-bills-total/job-bills-total.component"; import JobBillsTotal from "../job-bills-total/job-bills-total.component";
import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.component";
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component"; import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container"; import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.component";
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
export default function JobsDetailPliComponent({ export default function JobsDetailPliComponent({
job, job,

View File

@@ -18,6 +18,7 @@ import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
@@ -114,7 +115,7 @@ function JobsDocumentsComponent({
setgalleryImages(documents); setgalleryImages(documents);
}, [data, setgalleryImages, t]); }, [data, setgalleryImages, t]);
const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" });
const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" });
return ( return (
<div> <div>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
@@ -129,9 +130,14 @@ function JobsDocumentsComponent({
{!billId && <JobsDocumentsGalleryReassign galleryImages={galleryImages} callback={refetch} />} {!billId && <JobsDocumentsGalleryReassign galleryImages={galleryImages} callback={refetch} />}
</Space> </Space>
</Col> </Col>
{ {!hasMediaAccess && (
//TODO:Upsell <Col span={24}>
} <Card>
<UpsellComponent upsell={upsellEnum.media.general} />
</Card>
</Col>
)}
<Col span={24}> <Col span={24}>
<Card> <Card>
<DocumentsUploadComponent <DocumentsUploadComponent
@@ -143,7 +149,13 @@ function JobsDocumentsComponent({
/> />
</Card> </Card>
</Col> </Col>
{hasMediaAccess && !hasMobileAccess && (
<Col span={24}>
<Card>
<UpsellComponent upsell={upsellEnum.media.mobile} />
</Card>
</Col>
)}
<Col span={24}> <Col span={24}>
<Card title={t("jobs.labels.documents-images")}> <Card title={t("jobs.labels.documents-images")}>
<Gallery <Gallery

View File

@@ -13,7 +13,7 @@ import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit
import "./labor-allocations-table.styles.scss"; import "./labor-allocations-table.styles.scss";
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility"; import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician technician: selectTechnician
@@ -205,12 +205,9 @@ export function LaborAllocationsTable({
locale={{ locale={{
...(!hasTimeTicketAccess && { ...(!hasTimeTicketAccess && {
emptyText: ( emptyText: (
<div> <Card>
Upsell <UpsellComponent upsell={upsellEnum.timetickets.allocations} />
{ </Card>
//TODO:Upsell
}
</div>
) )
}) })
}} }}
@@ -251,12 +248,9 @@ export function LaborAllocationsTable({
locale={{ locale={{
...(!hasTimeTicketAccess && { ...(!hasTimeTicketAccess && {
emptyText: ( emptyText: (
<div> <Card>
Upsell <UpsellComponent upsell={upsellEnum.timetickets.allocations} />
{ </Card>
//TODO:Upsell
}
</div>
) )
}) })
}} }}

View File

@@ -20,14 +20,14 @@ import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import FeatureWrapperComponent, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component"; import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component"; import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component"; import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component"; import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component"; import PrintWrapper from "../print-wrapper/print-wrapper.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,

View File

@@ -14,11 +14,11 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import FeatureWrapperComponent, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component"; import PrintWrapper from "../print-wrapper/print-wrapper.component";
import PartsOrderDrawer from "./parts-order-list-table-drawer.component"; import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,

View File

@@ -1,10 +1,3 @@
import isBetween from "dayjs/plugin/isBetween";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import localeData from "dayjs/plugin/localeData";
import localizedFormat from "dayjs/plugin/localizedFormat";
import minMax from "dayjs/plugin/minMax";
import utc from "dayjs/plugin/utc";
import { DateLocalizer } from "react-big-calendar"; import { DateLocalizer } from "react-big-calendar";
function arrayWithHoles(arr) { function arrayWithHoles(arr) {

View File

@@ -7,6 +7,7 @@ import { Legend, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart,
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import { UpsellMaskWrapper, upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -47,27 +48,26 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
<strong>{loadData?.expectedJobCount}</strong> <strong>{loadData?.expectedJobCount}</strong>
</BlurWrapperComponent> </BlurWrapperComponent>
</Space> </Space>
<BlurWrapperComponent featureName="smartscheduling"> <UpsellMaskWrapper upsell={upsellEnum.smartscheduling.general}>
<RadarChart <BlurWrapperComponent featureName="smartscheduling">
// cx={300} <RadarChart
// cy={250} // cx={300}
// outerRadius={150} // cy={250}
width={800} // outerRadius={150}
height={600} width={800}
data={data} height={600}
> data={data}
<PolarGrid /> >
<PolarAngleAxis dataKey="bucket" /> <PolarGrid />
<PolarRadiusAxis angle={90} /> <PolarAngleAxis dataKey="bucket" />
<Radar name="Ideal Load" dataKey="target" stroke="darkgreen" fill="white" fillOpacity={0} /> <PolarRadiusAxis angle={90} />
<Radar name="EOD Load" dataKey="current" stroke="dodgerblue" fill="dodgerblue" fillOpacity={0.6} /> <Radar name="Ideal Load" dataKey="target" stroke="darkgreen" fill="white" fillOpacity={0} />
<Tooltip /> <Radar name="EOD Load" dataKey="current" stroke="dodgerblue" fill="dodgerblue" fillOpacity={0.6} />
<Legend /> <Tooltip />
</RadarChart> <Legend />
</BlurWrapperComponent> </RadarChart>
{ </BlurWrapperComponent>
//TODO:Upsell </UpsellMaskWrapper>
}
</div> </div>
); );

View File

@@ -1,5 +1,5 @@
import Icon from "@ant-design/icons"; import Icon from "@ant-design/icons";
import { Popover, Space } from "antd"; import { Card, Popover, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
@@ -12,14 +12,12 @@ import { createStructuredSelector } from "reselect";
import { selectScheduleLoad, selectScheduleLoadCalculating } from "../../redux/application/application.selectors"; import { selectScheduleLoad, selectScheduleLoadCalculating } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { default as BlurWrapper, default as BlurWrapperComponent } from "../feature-wrapper/blur-wrapper.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component"; import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component";
import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component"; import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component";
import InstanceRenderMgr from "../../utils/instanceRenderMgr"; import UpsellComponent, { upsellEnum, UpsellMaskWrapper } from "../upsell/upsell.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -89,11 +87,11 @@ export function ScheduleCalendarHeaderComponent({
</BlurWrapperComponent> </BlurWrapperComponent>
</tr> </tr>
)} )}
{
//TODO:Upsell
}
</tbody> </tbody>
</table> </table>
<Card style={{ maxWidth: "30rem" }}>
<UpsellComponent size="small" upsell={upsellEnum.smartscheduling.hrsdelta} />
</Card>
</div> </div>
); );
@@ -131,9 +129,9 @@ export function ScheduleCalendarHeaderComponent({
</BlurWrapperComponent> </BlurWrapperComponent>
</tr> </tr>
)} )}
{ <Card style={{ maxWidth: "30rem" }}>
//TODO:Upsell <UpsellComponent size="small" upsell={upsellEnum.smartscheduling.hrsdelta} />
} </Card>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@@ -1,21 +1,20 @@
import dayjs from "../../utils/day"; import { Alert, Collapse, Space } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React from "react";
import { Calendar } from "react-big-calendar"; import { Calendar } from "react-big-calendar";
import { Trans, useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectProblemJobs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import Event from "../job-at-change/schedule-event.container"; import Event from "../job-at-change/schedule-event.container";
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
import local from "./localizer";
import HeaderComponent from "./schedule-calendar-header.component"; import HeaderComponent from "./schedule-calendar-header.component";
import "./schedule-calendar.styles.scss"; import "./schedule-calendar.styles.scss";
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
import { selectProblemJobs } from "../../redux/application/application.selectors";
import { Alert, Collapse, Space } from "antd";
import { Trans, useTranslation } from "react-i18next";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import local from "./localizer";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,

View File

@@ -17,6 +17,7 @@ import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-
import "./schedule-job-modal.scss"; import "./schedule-job-modal.scss";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import { BlurWrapper } from "../feature-wrapper/blur-wrapper.component"; import { BlurWrapper } from "../feature-wrapper/blur-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -66,6 +67,8 @@ export function ScheduleJobModalComponent({
} }
}; };
const hasSmartSchedulingAccess = HasFeatureAccess({ bodyshop, featureName: "smartscheduling" });
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={12}> <Col span={12}>
@@ -110,30 +113,34 @@ export function ScheduleJobModalComponent({
{t("appointments.actions.calculate")} {t("appointments.actions.calculate")}
</LockWrapperComponent> </LockWrapperComponent>
</Button> </Button>
{smartOptions.map((d, idx) => ( {smartOptions.map((d, idx) => (
<BlurWrapper featureName="smartscheduling" key={idx}> <Button
<Button key={idx}
className="imex-flex-row__margin" className="imex-flex-row__margin"
disabled={!HasFeatureAccess({ bodyshop, featureName: "smartscheduling" })} disabled={!hasSmartSchedulingAccess}
onClick={() => { onClick={() => {
const ssDate = dayjs(d); const ssDate = dayjs(d);
if (ssDate.isBefore(dayjs())) { if (ssDate.isBefore(dayjs())) {
form.setFieldsValue({ start: dayjs() }); form.setFieldsValue({ start: dayjs() });
} else { } else {
form.setFieldsValue({ form.setFieldsValue({
start: dayjs(d).add(8, "hour") start: dayjs(d).add(8, "hour")
}); });
} }
handleDateBlur(); handleDateBlur();
}} }}
> >
<DateFormatter includeDay>{d}</DateFormatter> <BlurWrapper featureName="smartscheduling">
</Button> <span>
</BlurWrapper> <DateFormatter includeDay>{d}</DateFormatter>
</span>
</BlurWrapper>
</Button>
))} ))}
{ {smartOptions.length > 1 && hasSmartSchedulingAccess && (
//TODO:Upsell <UpsellComponent upsell={upsellEnum.smartscheduling.general} />
} )}
</Space> </Space>
</> </>
} }

View File

@@ -16,6 +16,7 @@ import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component"; import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -344,7 +345,6 @@ export function TimeTicketList({
<Table.Summary.Cell>{t("general.labels.totals")}</Table.Summary.Cell> <Table.Summary.Cell>{t("general.labels.totals")}</Table.Summary.Cell>
<Table.Summary.Cell /> <Table.Summary.Cell />
<Table.Summary.Cell /> <Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell>{totals.productivehrs.toFixed(1)}</Table.Summary.Cell> <Table.Summary.Cell>{totals.productivehrs.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell>{totals.actualhrs.toFixed(1)}</Table.Summary.Cell> <Table.Summary.Cell>{totals.actualhrs.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell> <Table.Summary.Cell>
@@ -363,12 +363,9 @@ export function TimeTicketList({
locale={{ locale={{
...(!hasTimeTicketsAccess && { ...(!hasTimeTicketsAccess && {
emptyText: ( emptyText: (
<div> <Card>
Upsell <UpsellComponent upsell={upsellEnum.timetickets.general} />
{ </Card>
//TODO:Upsell
}
</div>
) )
}) })
}} }}

View File

@@ -1,8 +1,9 @@
import { AppstoreAddOutlined } from "@ant-design/icons"; import { AppstoreAddOutlined, CalendarOutlined, CarOutlined, MobileOutlined } from "@ant-design/icons";
import { Result } from "antd"; import { Result, Button, Card } from "antd";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import upsellEnum from "./upsell.enum"; import i18n from "i18next";
import "./upsell.styles.scss";
export default function UpsellComponent({ featureName, subFeatureName, upsell, disableMask = false }) { export default function UpsellComponent({ featureName, subFeatureName, upsell, disableMask = false }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -19,16 +20,17 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
mask.style.left = 0; mask.style.left = 0;
mask.style.width = "100%"; mask.style.width = "100%";
mask.style.height = "100%"; mask.style.height = "100%";
mask.style.backgroundColor = "rgba(0, 0, 0, 0.25)"; mask.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
mask.style.zIndex = 9999; // mask.style.zIndex = 9999;
parentElement.style.position = "relative"; parentElement.style.position = "relative";
parentElement.appendChild(mask);
parentElement.prepend(mask);
return () => { return () => {
parentElement.removeChild(mask); parentElement.removeChild(mask);
}; };
} }
}, []); }, [disableMask]);
if (!resultProps) return <Result status="info" title={t("upsell.messages.generic")} />; if (!resultProps) return <Result status="info" title={t("upsell.messages.generic")} />;
return ( return (
@@ -37,3 +39,102 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
</div> </div>
); );
} }
//Kept in the same function as the result props line must mirror and doesnt warrant a separate function.
export function UpsellMaskWrapper({ children, upsell, featureName, subFeatureName, disableMask = false }) {
const resultProps = upsell || upsellEnum[featureName][subFeatureName];
return (
<div className="mask-wrapper">
<div className="mask-content">{children}</div>
<div className="mask-overlay">
<Card size="small">
<Result status="info" icon={<AppstoreAddOutlined />} {...resultProps} />
</Card>
</div>
</div>
);
}
//This is kept in this function as pulling it out into it's own util/enum prevents passing JSX as an `extra` prop
export const upsellEnum = {
bills: {
autoreconcile: {
//icon: null,
title: i18n.t("upsell.messages.bills.autoreconcile.title"),
subTitle: i18n.t("upsell.messages.bills.autoreconcile.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
//status: null
},
general: {
//icon: null,
title: i18n.t("upsell.messages.bills.general.title"),
subTitle: i18n.t("upsell.messages.bills.general.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
//status: null
}
},
audit: {
general: {
//icon: null,
title: i18n.t("upsell.messages.audit.general.title"),
subTitle: i18n.t("upsell.messages.audit.general.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
//status: null
}
},
lifecycle: {
general: {
//icon: null,
title: i18n.t("upsell.messages.lifecycle.general.title"),
subTitle: i18n.t("upsell.messages.lifecycle.general.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
//status: null }
}
},
media: {
general: {
//icon: null,
title: i18n.t("upsell.messages.media.general.title"),
subTitle: i18n.t("upsell.messages.media.general.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
//status: null }
},
mobile: {
icon: <MobileOutlined />,
title: i18n.t("upsell.messages.media.mobile.title"),
subTitle: i18n.t("upsell.messages.media.mobile.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
//status: null }
}
},
timetickets: {
allocations: {
title: i18n.t("upsell.messages.timetickets.allocations.title"),
subTitle: i18n.t("upsell.messages.timetickets.allocations.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
},
general: {
title: i18n.t("upsell.messages.timetickets.general.title"),
subTitle: i18n.t("upsell.messages.timetickets.general.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
}
},
smartscheduling: {
general: {
icon: <CalendarOutlined />,
title: i18n.t("upsell.messages.smartscheduling.general.title"),
subTitle: i18n.t("upsell.messages.smartscheduling.general.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
},
hrsdelta: {
icon: <CarOutlined />,
title: i18n.t("upsell.messages.smartscheduling.hrsdelta.title"),
subTitle: i18n.t("upsell.messages.smartscheduling.hrsdelta.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
},
datepicker: {
icon: <CarOutlined />,
title: i18n.t("upsell.messages.smartscheduling.datepicker.title"),
subTitle: i18n.t("upsell.messages.smartscheduling.datepicker.subtitle"),
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
}
}
};

View File

@@ -1,34 +0,0 @@
import i18n from "i18next";
const upsellEnum = {
bills: {
postbills: {
icon: null,
title: i18n.t("upsell.messages.bills.postbills.title"),
subTitle: i18n.t("upsell.messages.bills.postbills.subtitle"),
status: null //Nullable
},
reconcile: {
icon: null,
title: i18n.t("upsell.messages.bills.reconcile.title"),
subTitle: i18n.t("upsell.messages.bills.reconcile.subtitle"),
status: null //Nullable
},
table: {
//icon: null,
title: i18n.t("upsell.messages.bills.table.title"),
subTitle: i18n.t("upsell.messages.bills.table.subtitle"),
//status: null //Nullable
}
},
timetickets: {
table: {
icon: null,
title: i18n.t("upsell.messages.timetickets.table.title"),
subTitle: i18n.t("upsell.messages.timetickets.table.subtitle"),
status: null //Nullable
}
}
};
export default upsellEnum;

View File

@@ -0,0 +1,56 @@
.mask-wrapper {
position: relative;
//Newly added
display: flex;
justify-content: center;
align-items: center;
min-height: 100px; /* Adjust as needed */
}
.mask-content {
// filter: blur(5px);
background-color: rgba(0, 0, 0, 0.05);
pointer-events: none;
//Newly added
//width: 100%;
}
.mask-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
// width: 100%
}
.mask-overlay .ant-card {
max-width: 100%;
}
// .mask-wrapper {
// position: relative;
// display: inline-block;
// }
// .mask-content {
// filter: blur(5px);
// pointer-events: none;
// }
// .mask-overlay {
// position: absolute;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// display: flex;
// justify-content: center;
// align-items: center;
// z-index: 10;
// }
// .mask-overlay .ant-card {
// max-width: 100%;
// }

View File

@@ -2397,7 +2397,7 @@
"selectexistingornew": "Select an existing owner record or create a new one. " "selectexistingornew": "Select an existing owner record or create a new one. "
}, },
"fields": { "fields": {
"accountingid": "Accounting ID", "accountingid": "Accounting ID",
"address": "Address", "address": "Address",
"allow_text_message": "Permission to Text?", "allow_text_message": "Permission to Text?",
"name": "Name", "name": "Name",
@@ -3061,7 +3061,7 @@
"production_not_production_status": "Production not in Production Status", "production_not_production_status": "Production not in Production Status",
"production_over_time": "Production Level over Time", "production_over_time": "Production Level over Time",
"psr_by_make": "Percent of Sales by Vehicle Make", "psr_by_make": "Percent of Sales by Vehicle Make",
"purchase_return_ratio_excel": "Purchase & Return Ratio - Excel", "purchase_return_ratio_excel": "Purchase & Return Ratio - Excel",
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)", "purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)", "purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)", "purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
@@ -3087,7 +3087,7 @@
"timetickets": "Time Tickets", "timetickets": "Time Tickets",
"timetickets_employee": "Employee Time Tickets", "timetickets_employee": "Employee Time Tickets",
"timetickets_summary": "Time Tickets Summary", "timetickets_summary": "Time Tickets Summary",
"total_loss_jobs": "Jobs Marked as Total Loss", "total_loss_jobs": "Jobs Marked as Total Loss",
"unclaimed_hrs": "Unflagged Hours", "unclaimed_hrs": "Unflagged Hours",
"void_ros": "Void ROs", "void_ros": "Void ROs",
"work_in_progress_committed_labour": "Work in Progress - Committed Labor", "work_in_progress_committed_labour": "Work in Progress - Committed Labor",
@@ -3482,6 +3482,69 @@
"calculate": "Calculate" "calculate": "Calculate"
} }
}, },
"upsell": {
"cta": {
"learnmore": "Learn More"
},
"messages": {
"audit": {
"general": {
"subtitle": "Know exactly what happened and when through the entire repair process.",
"title": "Comprehensive Audit Trails"
}
},
"bills": {
"autoreconcile": {
"subtitle": "Let your management system do the tedious work - take advantage of automatic job reconciliation today.",
"title": "Did you account for every invoice?"
},
"general": {
"subtitle": "Our Bills module allows you to precisely account for every penny on the repair order to ensure you maximize your bottom line.",
"title": "Boost your profits by taking control of costs!"
}
},
"lifecycle": {
"general": {
"subtitle": "Job life cycles tell you exactly how long the job stayed in each stage of the repair process to identify bottle necks and inefficiencies.",
"title": "Are your repair KPIs suffering?"
}
},
"media": {
"general": {
"subtitle": "Store your vehicle repair documentation, invoices, and related documents digitally to access them anywhere, any time.",
"title": "Go paperless."
},
"mobile": {
"subtitle": "Use our mobile app to attach images from your device to the job, increasing your efficiency.",
"title": "Accelerate repair documentation with Mobile"
}
},
"scheduling": {
"datepicker": {
"subtitle": "Smart Scsheduling gives you the best dates for a vehicle to come in based on your current and predicted production load.",
"title": "When's the best time for this job to arrive?"
},
"general": {
"subtitle": "Know exactly how busy you will be tomorrow, the day after, or next week to know exactly when to schedule your next repair.",
"title": "Forecast your production with Smart Scheduling"
},
"hrsdelta": {
"subtitle": "Understand changes to your Work in Progress and production load instantly.",
"title": "Cars come and cars go."
}
},
"timetickets": {
"allocations": {
"subtitle": "Ensure your technicians are paid out exactly what they are owed - not a penny less, not a penny more.",
"title": "Technician Payments Done Just Right"
},
"general": {
"subtitle": "Track your technicians time with precision, giving you insight to repair progress and labor efficiency.",
"title": "Who did what and for how long?"
}
}
}
},
"user": { "user": {
"actions": { "actions": {
"changepassword": "Change Password", "changepassword": "Change Password",

View File

@@ -2397,7 +2397,7 @@
"selectexistingornew": "" "selectexistingornew": ""
}, },
"fields": { "fields": {
"accountingid": "", "accountingid": "",
"address": "Dirección", "address": "Dirección",
"allow_text_message": "Permiso de texto?", "allow_text_message": "Permiso de texto?",
"name": "Nombre", "name": "Nombre",
@@ -3061,7 +3061,7 @@
"production_not_production_status": "", "production_not_production_status": "",
"production_over_time": "", "production_over_time": "",
"psr_by_make": "", "psr_by_make": "",
"purchase_return_ratio_excel": "", "purchase_return_ratio_excel": "",
"purchase_return_ratio_grouped_by_vendor_detail": "", "purchase_return_ratio_grouped_by_vendor_detail": "",
"purchase_return_ratio_grouped_by_vendor_summary": "", "purchase_return_ratio_grouped_by_vendor_summary": "",
"purchases_by_cost_center_detail": "", "purchases_by_cost_center_detail": "",
@@ -3087,7 +3087,7 @@
"timetickets": "", "timetickets": "",
"timetickets_employee": "", "timetickets_employee": "",
"timetickets_summary": "", "timetickets_summary": "",
"total_loss_jobs": "", "total_loss_jobs": "",
"unclaimed_hrs": "", "unclaimed_hrs": "",
"void_ros": "", "void_ros": "",
"work_in_progress_committed_labour": "", "work_in_progress_committed_labour": "",
@@ -3482,6 +3482,69 @@
"calculate": "" "calculate": ""
} }
}, },
"upsell": {
"cta": {
"learnmore": ""
},
"messages": {
"audit": {
"general": {
"subtitle": "",
"title": ""
}
},
"bills": {
"autoreconcile": {
"subtitle": "",
"title": ""
},
"general": {
"subtitle": "",
"title": ""
}
},
"lifecycle": {
"general": {
"subtitle": "",
"title": ""
}
},
"media": {
"general": {
"subtitle": "",
"title": ""
},
"mobile": {
"subtitle": "",
"title": ""
}
},
"scheduling": {
"datepicker": {
"subtitle": "",
"title": ""
},
"general": {
"subtitle": "",
"title": ""
},
"hrsdelta": {
"subtitle": "",
"title": ""
}
},
"timetickets": {
"allocations": {
"subtitle": "",
"title": ""
},
"general": {
"subtitle": "",
"title": ""
}
}
}
},
"user": { "user": {
"actions": { "actions": {
"changepassword": "", "changepassword": "",

View File

@@ -2397,7 +2397,7 @@
"selectexistingornew": "" "selectexistingornew": ""
}, },
"fields": { "fields": {
"accountingid": "", "accountingid": "",
"address": "Adresse", "address": "Adresse",
"allow_text_message": "Autorisation de texte?", "allow_text_message": "Autorisation de texte?",
"name": "Prénom", "name": "Prénom",
@@ -3061,7 +3061,7 @@
"production_not_production_status": "", "production_not_production_status": "",
"production_over_time": "", "production_over_time": "",
"psr_by_make": "", "psr_by_make": "",
"purchase_return_ratio_excel": "", "purchase_return_ratio_excel": "",
"purchase_return_ratio_grouped_by_vendor_detail": "", "purchase_return_ratio_grouped_by_vendor_detail": "",
"purchase_return_ratio_grouped_by_vendor_summary": "", "purchase_return_ratio_grouped_by_vendor_summary": "",
"purchases_by_cost_center_detail": "", "purchases_by_cost_center_detail": "",
@@ -3087,7 +3087,7 @@
"timetickets": "", "timetickets": "",
"timetickets_employee": "", "timetickets_employee": "",
"timetickets_summary": "", "timetickets_summary": "",
"total_loss_jobs": "", "total_loss_jobs": "",
"unclaimed_hrs": "", "unclaimed_hrs": "",
"void_ros": "", "void_ros": "",
"work_in_progress_committed_labour": "", "work_in_progress_committed_labour": "",
@@ -3482,6 +3482,69 @@
"calculate": "" "calculate": ""
} }
}, },
"upsell": {
"cta": {
"learnmore": ""
},
"messages": {
"audit": {
"general": {
"subtitle": "",
"title": ""
}
},
"bills": {
"autoreconcile": {
"subtitle": "",
"title": ""
},
"general": {
"subtitle": "",
"title": ""
}
},
"lifecycle": {
"general": {
"subtitle": "",
"title": ""
}
},
"media": {
"general": {
"subtitle": "",
"title": ""
},
"mobile": {
"subtitle": "",
"title": ""
}
},
"scheduling": {
"datepicker": {
"subtitle": "",
"title": ""
},
"general": {
"subtitle": "",
"title": ""
},
"hrsdelta": {
"subtitle": "",
"title": ""
}
},
"timetickets": {
"allocations": {
"subtitle": "",
"title": ""
},
"general": {
"subtitle": "",
"title": ""
}
}
}
},
"user": { "user": {
"actions": { "actions": {
"changepassword": "", "changepassword": "",

View File

@@ -18,6 +18,7 @@ i18n
.use(initReactI18next) .use(initReactI18next)
.init( .init(
{ {
ns: ["translation"],
resources, resources,
//lng: "en", //lng: "en",
detection: {}, detection: {},

3931
package-lock.json generated

File diff suppressed because it is too large Load Diff