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/>
<framework>react-intl</framework>
<filename>bodyshop_translations.babel</filename>
<source_root_dir>client</source_root_dir>
<source_root_dir>client/src</source_root_dir>
<folder_node>
<name></name>
<children>
@@ -40921,6 +40921,27 @@
<folder_node>
<name>fields</name>
<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>
<name>address</name>
<definition_loaded>false</definition_loaded>
@@ -52328,6 +52349,27 @@
</translation>
</translations>
</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>
<name>purchase_return_ratio_grouped_by_vendor_detail</name>
<definition_loaded>false</definition_loaded>
@@ -52853,6 +52895,27 @@
</translation>
</translations>
</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>
<name>unclaimed_hrs</name>
<definition_loaded>false</definition_loaded>
@@ -59609,6 +59672,589 @@
</folder_node>
</children>
</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>
<name>user</name>
<children>

View File

@@ -18,7 +18,7 @@ import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import UpsellComponent from "../upsell/upsell.component";
import upsellEnum from "../upsell/upsell.enum";
import { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -240,7 +240,7 @@ export function BillsListTableComponent({
onChange={handleTableChange}
locale={{
...(!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 { Button, Card, Col, Row, Table, Tag } from "antd";
import { SyncOutlined } from "@ant-design/icons";
import React from "react";
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 { 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 { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
currentUser: selectCurrentUser,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail);
export function JobAuditTrail({ currentUser, jobId }) {
export function JobAuditTrail({ bodyshop, currentUser, jobId }) {
const { t } = useTranslation();
const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { jobid: jobId },
@@ -137,11 +140,17 @@ export function JobAuditTrail({ currentUser, jobId }) {
)
}
];
const hasAuditAccess = HasFeatureAccess({ bodyshop, featureName: "audit" });
return (
<Row gutter={[16, 16]}>
{
//TODO:Upsell
}
{!hasAuditAccess && (
<Col span={24}>
<Card>
<UpsellComponent upsell={upsellEnum.audit.general} disableMask />
</Card>
</Col>
)}
<Col span={24}>
<Card
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 "./job-bills-total.styles.scss";
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,
bills,
partsOrders,
@@ -120,10 +133,10 @@ export default function JobBillsTotalComponent({
warningCallback({ key: "cm", warning: t("jobs.labels.outstanding_credit_memos") });
}
}
const hasBillsAccess = HasFeatureAccess({ bodyshop, featureName: "bills" });
return (
<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%" }}>
<Space wrap size="large">
<Tooltip
@@ -240,9 +253,6 @@ export default function JobBillsTotalComponent({
</BlurWrapperComponent>
</Tooltip>
</Space>
{
//TODO:Upsell
}
{showWarning &&
(discrepWithCms.getAmount() !== 0 ||
discrepWithLbrAdj.getAmount() !== 0 ||
@@ -321,11 +331,17 @@ export default function JobBillsTotalComponent({
{showWarning && calculatedCreditsNotReceived.getAmount() > 0 && (
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_credit_memos")} />
)}
{
//TODO:Upsell
}
</Card>
</Col>
{!hasBillsAccess && (
<Col span={6}>
<Card style={{ height: "100%" }}>
<UpsellComponent upsell={upsellEnum.bills.autoreconcile} disableMask />
</Card>
</Col>
)}
</Row>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobBillsTotalComponent);

View File

@@ -1,5 +1,5 @@
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 { t } from "i18next";
import React, { useState } from "react";

View File

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

View File

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

View File

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

View File

@@ -20,14 +20,14 @@ import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
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 PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.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 PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,

View File

@@ -14,11 +14,11 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
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 PrintWrapper from "../print-wrapper/print-wrapper.component";
import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
const mapStateToProps = createStructuredSelector({
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";
function arrayWithHoles(arr) {

View File

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

View File

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

View File

@@ -1,21 +1,20 @@
import dayjs from "../../utils/day";
import { Alert, Collapse, Space } from "antd";
import queryString from "query-string";
import React from "react";
import { Calendar } from "react-big-calendar";
import { Trans, useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectProblemJobs } from "../../redux/application/application.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 JobDetailCards from "../job-detail-cards/job-detail-cards.component";
import local from "./localizer";
import HeaderComponent from "./schedule-calendar-header.component";
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({
bodyshop: selectBodyshop,

View File

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

View File

@@ -1,8 +1,9 @@
import { AppstoreAddOutlined } from "@ant-design/icons";
import { Result } from "antd";
import { AppstoreAddOutlined, CalendarOutlined, CarOutlined, MobileOutlined } from "@ant-design/icons";
import { Result, Button, Card } from "antd";
import React, { useEffect, useRef } from "react";
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 }) {
const { t } = useTranslation();
@@ -19,16 +20,17 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
mask.style.left = 0;
mask.style.width = "100%";
mask.style.height = "100%";
mask.style.backgroundColor = "rgba(0, 0, 0, 0.25)";
mask.style.zIndex = 9999;
mask.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
// mask.style.zIndex = 9999;
parentElement.style.position = "relative";
parentElement.appendChild(mask);
parentElement.prepend(mask);
return () => {
parentElement.removeChild(mask);
};
}
}, []);
}, [disableMask]);
if (!resultProps) return <Result status="info" title={t("upsell.messages.generic")} />;
return (
@@ -37,3 +39,102 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
</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. "
},
"fields": {
"accountingid": "Accounting ID",
"accountingid": "Accounting ID",
"address": "Address",
"allow_text_message": "Permission to Text?",
"name": "Name",
@@ -3061,7 +3061,7 @@
"production_not_production_status": "Production not in Production Status",
"production_over_time": "Production Level over Time",
"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_summary": "Purchase & Return Ratio by Vendor (Summary)",
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
@@ -3087,7 +3087,7 @@
"timetickets": "Time Tickets",
"timetickets_employee": "Employee Time Tickets",
"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",
"void_ros": "Void ROs",
"work_in_progress_committed_labour": "Work in Progress - Committed Labor",
@@ -3482,6 +3482,69 @@
"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": {
"actions": {
"changepassword": "Change Password",

View File

@@ -2397,7 +2397,7 @@
"selectexistingornew": ""
},
"fields": {
"accountingid": "",
"accountingid": "",
"address": "Dirección",
"allow_text_message": "Permiso de texto?",
"name": "Nombre",
@@ -3061,7 +3061,7 @@
"production_not_production_status": "",
"production_over_time": "",
"psr_by_make": "",
"purchase_return_ratio_excel": "",
"purchase_return_ratio_excel": "",
"purchase_return_ratio_grouped_by_vendor_detail": "",
"purchase_return_ratio_grouped_by_vendor_summary": "",
"purchases_by_cost_center_detail": "",
@@ -3087,7 +3087,7 @@
"timetickets": "",
"timetickets_employee": "",
"timetickets_summary": "",
"total_loss_jobs": "",
"total_loss_jobs": "",
"unclaimed_hrs": "",
"void_ros": "",
"work_in_progress_committed_labour": "",
@@ -3482,6 +3482,69 @@
"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": {
"actions": {
"changepassword": "",

View File

@@ -2397,7 +2397,7 @@
"selectexistingornew": ""
},
"fields": {
"accountingid": "",
"accountingid": "",
"address": "Adresse",
"allow_text_message": "Autorisation de texte?",
"name": "Prénom",
@@ -3061,7 +3061,7 @@
"production_not_production_status": "",
"production_over_time": "",
"psr_by_make": "",
"purchase_return_ratio_excel": "",
"purchase_return_ratio_excel": "",
"purchase_return_ratio_grouped_by_vendor_detail": "",
"purchase_return_ratio_grouped_by_vendor_summary": "",
"purchases_by_cost_center_detail": "",
@@ -3087,7 +3087,7 @@
"timetickets": "",
"timetickets_employee": "",
"timetickets_summary": "",
"total_loss_jobs": "",
"total_loss_jobs": "",
"unclaimed_hrs": "",
"void_ros": "",
"work_in_progress_committed_labour": "",
@@ -3482,6 +3482,69 @@
"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": {
"actions": {
"changepassword": "",

View File

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

3931
package-lock.json generated

File diff suppressed because it is too large Load Diff