Compare commits
30 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8ef681cf1 | ||
|
|
2f267a9f2c | ||
|
|
d6fbf02092 | ||
|
|
7a88dd1aae | ||
|
|
521aa81591 | ||
|
|
d70fee6125 | ||
|
|
1a8fad26e5 | ||
|
|
d69050f006 | ||
|
|
abe1e80844 | ||
|
|
58e897db31 | ||
|
|
b7ed6734a0 | ||
|
|
7d5a866a5c | ||
|
|
23becf6494 | ||
|
|
64ee2c1526 | ||
|
|
c033c0fbc5 | ||
|
|
f8ddfeb7d0 | ||
|
|
c4f7c57c24 | ||
|
|
acc6633271 | ||
|
|
836e9b846a | ||
|
|
fd01746f7d | ||
|
|
4ec171d93b | ||
|
|
608988c67c | ||
|
|
8da4d0b0f1 | ||
|
|
a54668e030 | ||
|
|
2386457cf5 | ||
|
|
45944ae8c9 | ||
|
|
2c32a4891b | ||
|
|
2b9fe61d79 | ||
|
|
95751103a2 | ||
|
|
8ca4c5d7fa |
@@ -1,6 +1,6 @@
|
||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
import { connect } from "react-redux";
|
||||
@@ -17,8 +17,7 @@ import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-
|
||||
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.component";
|
||||
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -61,7 +60,6 @@ export function BillsListTableComponent({
|
||||
// const search = queryString.parse(useLocation().search);
|
||||
// const selectedBill = search.billid;
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const Templates = TemplateList("bill");
|
||||
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
||||
|
||||
@@ -148,7 +148,7 @@ function Header({
|
||||
label: (
|
||||
<Space>
|
||||
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||
{t(t("menus.header.enterbills"))}
|
||||
{t("menus.header.enterbills")}
|
||||
</LockWrapper>
|
||||
</Space>
|
||||
),
|
||||
|
||||
@@ -9,14 +9,25 @@ 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";
|
||||
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
// show text on bar if text can fit
|
||||
export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
export function JobLifecycleComponent({ bodyshop, job, statuses, ...rest }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [lifecycleData, setLifecycleData] = useState(null);
|
||||
const { t } = useTranslation(); // Used for tracking external state changes.
|
||||
|
||||
const hasLifeCycleAccess = HasFeatureAccess({ bodyshop, featureName: "lifecycle" });
|
||||
const { data } = useQuery(
|
||||
gql`
|
||||
query get_job_test($id: uuid!) {
|
||||
@@ -143,9 +154,11 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Card type="inner" style={{ marginTop: "10px" }}>
|
||||
<UpsellComponent upsell={upsellEnum().lifecycle.general} />
|
||||
</Card>
|
||||
{!hasLifeCycleAccess && (
|
||||
<Card type="inner" style={{ marginTop: "10px" }}>
|
||||
<UpsellComponent upsell={upsellEnum().lifecycle.general} />
|
||||
</Card>
|
||||
)}
|
||||
<BlurWrapperComponent featureName="lifecycle" bypass>
|
||||
<div
|
||||
id="bar-container"
|
||||
@@ -302,5 +315,4 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default JobLifecycleComponent;
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent);
|
||||
|
||||
@@ -13,12 +13,12 @@ import { selectScheduleLoad, selectScheduleLoadCalculating } from "../../redux/a
|
||||
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 { HasFeatureAccess } from "../feature-wrapper/feature-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 UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
||||
import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component";
|
||||
import UpsellComponent, { upsellEnum, UpsellMaskWrapper } from "../upsell/upsell.component";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -13,6 +13,9 @@ import { Button, Card, Result } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { store } from "../../redux/store.js";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import "./upsell.styles.scss";
|
||||
|
||||
export default function UpsellComponent({ featureName, subFeatureName, upsell, disableMask }) {
|
||||
@@ -64,174 +67,275 @@ export function UpsellMaskWrapper({ children, upsell, featureName, subFeatureNam
|
||||
</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
|
||||
export const upsellEnum = () => {
|
||||
const { currentUser, bodyshop } = store.getState().user;
|
||||
|
||||
const [first_name, ...last_name] = currentUser?.displayName ? currentUser.displayName.split(" ") : [];
|
||||
const LearnMoreLink = encodeURI(
|
||||
InstanceRenderManager({
|
||||
imex: `https://imexsystems.ca/schedule-a-demo/`,
|
||||
rome: `https://forms.zohopublic.com/rometech/form/ROLearnMore/formperma/0G29z8LgLlvKK8nno-b7s-GHgNXwIFlrMeE0mC394L4?first_name=${first_name || ""}&last_name=${last_name.join(" ") || ""}&shop_name=${bodyshop?.shopname || ""}&email=${currentUser?.email || ""}&shop_phone=${bodyshop?.phone || ""}`
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
bills: {
|
||||
autoreconcile: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.bills.autoreconcile.title"),
|
||||
subTitle: i18n.t("upsell.messages.bills.autoreconcile.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null
|
||||
},
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.bills.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.bills.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//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
|
||||
}
|
||||
},
|
||||
checklist: {
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.checklist.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.checklist.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
//status: null
|
||||
}
|
||||
},
|
||||
payments: {
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.payments.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.payments.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>
|
||||
}
|
||||
},
|
||||
accounting: {
|
||||
payables: {
|
||||
icon: <DollarOutlined />,
|
||||
title: i18n.t("upsell.messages.accounting.payables.title"),
|
||||
subTitle: i18n.t("upsell.messages.accounting.payables.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
},
|
||||
receivables: {
|
||||
icon: <DollarOutlined />,
|
||||
title: i18n.t("upsell.messages.accounting.receivables.title"),
|
||||
subTitle: i18n.t("upsell.messages.accounting.receivables.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
checklist: {
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.checklist.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.checklist.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null
|
||||
}
|
||||
},
|
||||
payments: {
|
||||
icon: <DollarOutlined />,
|
||||
title: i18n.t("upsell.messages.accounting.payments.title"),
|
||||
subTitle: i18n.t("upsell.messages.accounting.payments.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.payments.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.payments.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null
|
||||
}
|
||||
},
|
||||
audit: {
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.audit.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.audit.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null
|
||||
}
|
||||
},
|
||||
lifecycle: {
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.lifecycle.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.lifecycle.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null }
|
||||
}
|
||||
},
|
||||
media: {
|
||||
general: {
|
||||
//icon: null,
|
||||
title: i18n.t("upsell.messages.media.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.media.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null }
|
||||
},
|
||||
mobile: {
|
||||
icon: <MobileOutlined />,
|
||||
title: i18n.t("upsell.messages.media.mobile.title"),
|
||||
subTitle: i18n.t("upsell.messages.media.mobile.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
//status: null }
|
||||
}
|
||||
},
|
||||
timetickets: {
|
||||
allocations: {
|
||||
title: i18n.t("upsell.messages.timetickets.allocations.title"),
|
||||
subTitle: i18n.t("upsell.messages.timetickets.allocations.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
general: {
|
||||
title: i18n.t("upsell.messages.timetickets.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.timetickets.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
smartscheduling: {
|
||||
general: {
|
||||
icon: <CalendarOutlined />,
|
||||
title: i18n.t("upsell.messages.smartscheduling.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.smartscheduling.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
hrsdelta: {
|
||||
icon: <CarOutlined />,
|
||||
title: i18n.t("upsell.messages.smartscheduling.hrsdelta.title"),
|
||||
subTitle: i18n.t("upsell.messages.smartscheduling.hrsdelta.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
datepicker: {
|
||||
icon: <CarOutlined />,
|
||||
title: i18n.t("upsell.messages.smartscheduling.datepicker.title"),
|
||||
subTitle: i18n.t("upsell.messages.smartscheduling.datepicker.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
accounting: {
|
||||
payables: {
|
||||
icon: <DollarOutlined />,
|
||||
title: i18n.t("upsell.messages.accounting.payables.title"),
|
||||
subTitle: i18n.t("upsell.messages.accounting.payables.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
receivables: {
|
||||
icon: <DollarOutlined />,
|
||||
title: i18n.t("upsell.messages.accounting.receivables.title"),
|
||||
subTitle: i18n.t("upsell.messages.accounting.receivables.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
payments: {
|
||||
icon: <DollarOutlined />,
|
||||
title: i18n.t("upsell.messages.accounting.payments.title"),
|
||||
subTitle: i18n.t("upsell.messages.accounting.payments.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
courtesycars: {
|
||||
general: {
|
||||
icon: <CarOutlined />,
|
||||
title: i18n.t("upsell.messages.courtesycars.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.courtesycars.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
dashboard: {
|
||||
general: {
|
||||
icon: <DashboardOutlined />,
|
||||
title: i18n.t("upsell.messages.dashboard.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.dashboard.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
visualboard: {
|
||||
general: {
|
||||
icon: <BuildOutlined />,
|
||||
title: i18n.t("upsell.messages.visualboard.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.visualboard.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
scoreboard: {
|
||||
general: {
|
||||
icon: <LineChartOutlined />,
|
||||
title: i18n.t("upsell.messages.scoreboard.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.scoreboard.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
techconsole: {
|
||||
general: {
|
||||
icon: <LineChartOutlined />,
|
||||
title: i18n.t("upsell.messages.techconsole.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.techconsole.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
csi: {
|
||||
general: {
|
||||
icon: <StarOutlined />,
|
||||
title: i18n.t("upsell.messages.csi.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.csi.general.subtitle"),
|
||||
extra: (
|
||||
<Link to={LearnMoreLink} target="_blank">
|
||||
<Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
courtesycars: {
|
||||
general: {
|
||||
icon: <CarOutlined />,
|
||||
title: i18n.t("upsell.messages.courtesycars.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.courtesycars.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
}
|
||||
},
|
||||
dashboard: {
|
||||
general: {
|
||||
icon: <DashboardOutlined />,
|
||||
title: i18n.t("upsell.messages.dashboard.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.dashboard.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
}
|
||||
},
|
||||
visualboard: {
|
||||
general: {
|
||||
icon: <BuildOutlined />,
|
||||
title: i18n.t("upsell.messages.visualboard.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.visualboard.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
}
|
||||
},
|
||||
scoreboard: {
|
||||
general: {
|
||||
icon: <LineChartOutlined />,
|
||||
title: i18n.t("upsell.messages.scoreboard.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.scoreboard.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
}
|
||||
},
|
||||
techconsole: {
|
||||
general: {
|
||||
icon: <LineChartOutlined />,
|
||||
title: i18n.t("upsell.messages.techconsole.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.techconsole.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
}
|
||||
},
|
||||
csi: {
|
||||
general: {
|
||||
icon: <StarOutlined />,
|
||||
title: i18n.t("upsell.messages.csi.general.title"),
|
||||
subTitle: i18n.t("upsell.messages.csi.general.subtitle"),
|
||||
extra: <Button type="primary">{i18n.t("upsell.cta.learnmore")}</Button>
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@ import { FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks } from "react-icons/fa
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||
@@ -42,6 +41,7 @@ import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-to
|
||||
import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
import LockWrapperComponent from "../../components/lock-wrapper/lock-wrapper.component.jsx";
|
||||
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
|
||||
@@ -54,9 +54,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { DateTimeFormat } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import LockWrapperComponent from "../../components/lock-wrapper/lock-wrapper.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -403,7 +401,9 @@ export function JobsDetailPage({
|
||||
icon: <BarsOutlined />,
|
||||
id: "job-details-lifecycle",
|
||||
label: (
|
||||
<LockWrapperComponent featureName="lifecycle" bypass>{t("menus.jobsdetail.lifecycle")}</LockWrapperComponent>
|
||||
<LockWrapperComponent featureName="lifecycle" bypass>
|
||||
{t("menus.jobsdetail.lifecycle")}
|
||||
</LockWrapperComponent>
|
||||
),
|
||||
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
|
||||
},
|
||||
@@ -438,7 +438,11 @@ export function JobsDetailPage({
|
||||
key: "audit",
|
||||
icon: <HistoryOutlined />,
|
||||
id: "job-details-audit",
|
||||
label: <LockWrapperComponent featureName="audit" bypass>{t("jobs.labels.audit")}</LockWrapperComponent>,
|
||||
label: (
|
||||
<LockWrapperComponent featureName="audit" bypass>
|
||||
{t("jobs.labels.audit")}
|
||||
</LockWrapperComponent>
|
||||
),
|
||||
children: <JobAuditTrail jobId={job.id} />
|
||||
},
|
||||
{
|
||||
|
||||
@@ -329,7 +329,8 @@ const main = async () => {
|
||||
main().catch((error) => {
|
||||
logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, {
|
||||
error: error.message,
|
||||
errorjson: JSON.stringify(error)
|
||||
errorjson: JSON.stringify(error),
|
||||
stack: error.stack
|
||||
});
|
||||
// Note: If we want the app to crash on all uncaught async operations, we would
|
||||
// need to put a `process.exit(1);` here
|
||||
|
||||
@@ -3,3 +3,4 @@ exports.autohouse = require("./autohouse").default;
|
||||
exports.chatter = require("./chatter").default;
|
||||
exports.claimscorp = require("./claimscorp").default;
|
||||
exports.kaizen = require("./kaizen").default;
|
||||
exports.usageReport = require("./usageReport").default;
|
||||
90
server/data/usageReport.js
Normal file
90
server/data/usageReport.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||
});
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const emailer = require("../email/sendemail");
|
||||
const moment = require("moment-timezone");
|
||||
const converter = require("json-2-csv");
|
||||
const logger = require("../utils/logger");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const InstanceMgr = require("../utils/instanceMgr").default;
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
try {
|
||||
logger.log("usage-report-email-start", "debug", req?.user?.email, null, {});
|
||||
|
||||
if (InstanceMgr({ rome: false, imex: true })) {
|
||||
//Disable for ImEX at the moment.
|
||||
res.sendStatus(403);
|
||||
logger.log("usage-report-email-forbidden", "warn", req?.user?.email, null, {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
// Validate using autohouse token header.
|
||||
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
||||
res.sendStatus(401);
|
||||
logger.log("usage-report-email-forbidden", "warn", req?.user?.email, null, {});
|
||||
return;
|
||||
}
|
||||
|
||||
//Query the usage data.
|
||||
const queryResults = await client.request(queries.STATUS_UPDATE, {
|
||||
today: moment().startOf("day").subtract(7, "days"),
|
||||
period: moment().subtract(90, "days").startOf("day")
|
||||
});
|
||||
|
||||
//Massage the data.
|
||||
const shopList = queryResults.bodyshops.map((shop) => ({
|
||||
"Shop Name": shop.shopname,
|
||||
"Days Since Creation": moment().diff(moment(shop.created_at), "days"),
|
||||
"Jobs Created": shop.jobs_created.aggregate.count,
|
||||
"Jobs Updated": shop.jobs_updated.aggregate.count,
|
||||
"Owners Created": shop.owners_created.aggregate.count,
|
||||
"Owners Updated": shop.owners_updated.aggregate.count,
|
||||
"Vehicles Created": shop.vehicles_created.aggregate.count,
|
||||
"Vehicles Updated": shop.vehicles_updated.aggregate.count,
|
||||
"Tasks Created": shop.tasks_created.aggregate.count,
|
||||
"Tasks Updated": shop.tasks_updated.aggregate.count
|
||||
}));
|
||||
|
||||
const csv = converter.json2csv(shopList, { emptyFieldValue: "" });
|
||||
emailer
|
||||
.sendTaskEmail({
|
||||
to: ["patrick.fic@convenient-brands.com", "bradley.rhoades@convenient-brands.com"],
|
||||
subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`,
|
||||
text: `
|
||||
Usage Report for ${moment().format("MM/DD/YYYY")} for Rome Online Customers.
|
||||
|
||||
Notes:
|
||||
- Days Since Creation: The number of days since the shop was created. Only shops created in the last 90 days are included.
|
||||
- Updated values should be higher than created values.
|
||||
- Counts are inclusive of the last 7 days of data.
|
||||
`,
|
||||
attachments: [{ filename: `RO Usage Report ${moment().format("MM/DD/YYYY")}.csv`, content: csv }]
|
||||
})
|
||||
.then(() => {
|
||||
logger.log("usage-report-email-success", "debug", req?.user?.email, null, {
|
||||
csv
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.log("usage-report-email-send-error", "ERROR", req?.user?.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
});
|
||||
res.sendStatus(200);
|
||||
return;
|
||||
} catch (error) {
|
||||
logger.log("usage-report-email-error", "ERROR", req?.user?.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
res.status(500).json({ error: error.message, stack: error.stack });
|
||||
}
|
||||
};
|
||||
@@ -2617,3 +2617,76 @@ exports.CREATE_CONVERSATION = `mutation CREATE_CONVERSATION($conversation: [conv
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
exports.STATUS_UPDATE = `query STATUS_UPDATE($period: timestamptz!, $today: timestamptz!) {
|
||||
bodyshops(where: { created_at: { _gte: $period } }) {
|
||||
shopname
|
||||
id
|
||||
created_at
|
||||
jobs_created: jobs_aggregate(where: { created_at: { _gte: $today } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
jobs_updated: jobs_aggregate(where: { updated_at: { _gte: $today } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
owners_created: owners_aggregate(where: { created_at: { _gte: $today } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
owners_updated: owners_aggregate(where: { updated_at: { _gte: $today } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
vehicles_created: vehicles_aggregate(
|
||||
where: { created_at: { _gte: $today } }
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
vehicles_updated: vehicles_aggregate(
|
||||
where: { updated_at: { _gte: $today } }
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
tasks_created: tasks_aggregate(where: { created_at: { _gte: $today } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
tasks_updated: tasks_aggregate(where: { updated_at: { _gte: $today } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
jobs {
|
||||
parts_orders_created: parts_orders_aggregate(
|
||||
where: { created_at: { _gte: $today } }
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
parts_orders_updated: parts_orders_aggregate(
|
||||
where: { updated_at: { _gte: $today } }
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,10 +1,11 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { autohouse, claimscorp, chatter, kaizen } = require("../data/data");
|
||||
const { autohouse, claimscorp, chatter, kaizen, usageReport } = require("../data/data");
|
||||
|
||||
router.post("/ah", autohouse);
|
||||
router.post("/cc", claimscorp);
|
||||
router.post("/chatter", chatter);
|
||||
router.post("/kaizen", kaizen);
|
||||
router.post("/usagereport", usageReport);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -41,7 +41,7 @@ exports.taskHandler = async (req, res) => {
|
||||
|
||||
return res.status(200).send(csv);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message, stack: error.stackTrace });
|
||||
res.status(500).json({ error: error.message, stack: error.stack });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user