Compare commits
29 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a1f0e1e42 | ||
|
|
0675f84386 | ||
|
|
6994e44bd3 | ||
|
|
0d6d8e9d7c | ||
|
|
f7c01d5b35 | ||
|
|
acea8d2fee | ||
|
|
a36db7cee7 | ||
|
|
a3c0e25407 | ||
|
|
a6c863f67d | ||
|
|
169b5265c3 | ||
|
|
72ee621303 | ||
|
|
478e5fb569 | ||
|
|
87a55028e1 | ||
|
|
8045c228d6 | ||
|
|
b97bc0df8e | ||
|
|
cf86430aa9 | ||
|
|
aa6fc78aa0 | ||
|
|
77e4d72a54 | ||
|
|
9a5a2c7497 | ||
|
|
774f1fea68 | ||
|
|
57930005b2 | ||
|
|
e7bbb96dc3 | ||
|
|
af6139dcaf | ||
|
|
f120116e52 | ||
|
|
46af401e9b | ||
|
|
1c1f0a16e2 | ||
|
|
8b98206e63 | ||
|
|
14cffd3ad4 | ||
|
|
9567cd88b1 |
@@ -48,6 +48,8 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const scenarioNotificationsOn = client?.getTreatment("Realtime_Notifications_UI") === "on";
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigator.onLine) {
|
||||
setOnline(false);
|
||||
@@ -201,7 +203,12 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/manage/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
||||
<SocketProvider
|
||||
bodyshop={bodyshop}
|
||||
navigate={navigate}
|
||||
currentUser={currentUser}
|
||||
scenarioNotificationsOn={scenarioNotificationsOn}
|
||||
>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
@@ -213,7 +220,12 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/tech/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
||||
<SocketProvider
|
||||
bodyshop={bodyshop}
|
||||
navigate={navigate}
|
||||
currentUser={currentUser}
|
||||
scenarioNotificationsOn={scenarioNotificationsOn}
|
||||
>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { Badge, Layout, Menu, Spin } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import {
|
||||
BankFilled,
|
||||
BarChartOutlined,
|
||||
@@ -38,19 +26,31 @@ import {
|
||||
UnorderedListOutlined,
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Badge, Layout, Menu, Spin } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BsKanban } from "react-icons/bs";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
import { FiLogOut } from "react-icons/fi";
|
||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||
import { IoBusinessOutline } from "react-icons/io5";
|
||||
import { RiSurveyLine } from "react-icons/ri";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import day from "../../utils/day.js";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||
|
||||
// Redux mappings
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -208,25 +208,14 @@ function Header({
|
||||
key: "allpayments",
|
||||
id: "header-accounting-allpayments",
|
||||
icon: <BankFilled />,
|
||||
label: (
|
||||
<Link to="/manage/payments">
|
||||
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
||||
{t("menus.header.allpayments")}
|
||||
</LockWrapper>
|
||||
</Link>
|
||||
)
|
||||
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterpayments",
|
||||
id: "header-accounting-enterpayments",
|
||||
icon: <FaCreditCard />,
|
||||
label: (
|
||||
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
||||
{t("menus.header.enterpayment")}
|
||||
</LockWrapper>
|
||||
),
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () =>
|
||||
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: null
|
||||
|
||||
@@ -28,11 +28,11 @@ import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -775,15 +775,14 @@ export function JobsDetailHeaderActions({
|
||||
key: "enterpayments",
|
||||
id: "job-actions-enterpayments",
|
||||
disabled: !job.converted,
|
||||
label: <LockerWrapperComponent featureName="payments">{t("menus.header.enterpayment")}</LockerWrapperComponent>,
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_payment");
|
||||
|
||||
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from "../../graphql/notifications.queries.js";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const SocketContext = createContext(null);
|
||||
|
||||
@@ -26,10 +25,11 @@ const INITIAL_NOTIFICATIONS = 10;
|
||||
* @param bodyshop
|
||||
* @param navigate
|
||||
* @param currentUser
|
||||
* @param scenarioNotificationsOn
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNotificationsOn }) => {
|
||||
const socketRef = useRef(null);
|
||||
const [clientId, setClientId] = useState(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
@@ -37,14 +37,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
treatments: { Realtime_Notifications_UI }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Realtime_Notifications_UI"],
|
||||
splitKey: bodyshop?.imexshopid
|
||||
});
|
||||
|
||||
const [markNotificationRead] = useMutation(MARK_NOTIFICATION_READ, {
|
||||
update: (cache, { data: { update_notifications } }) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
@@ -217,7 +209,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
|
||||
const handleNotification = (data) => {
|
||||
// Scenario Notifications have been disabled, bail.
|
||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||
if (!scenarioNotificationsOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,7 +329,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
|
||||
const handleSyncNotificationRead = ({ notificationId, timestamp }) => {
|
||||
// Scenario Notifications have been disabled, bail.
|
||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||
if (!scenarioNotificationsOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -379,7 +371,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
|
||||
const handleSyncAllNotificationsRead = ({ timestamp }) => {
|
||||
// Scenario Notifications have been disabled, bail.
|
||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||
if (!scenarioNotificationsOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -470,7 +462,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
markAllNotificationsRead,
|
||||
navigate,
|
||||
currentUser,
|
||||
Realtime_Notifications_UI,
|
||||
scenarioNotificationsOn,
|
||||
t
|
||||
]);
|
||||
|
||||
@@ -482,7 +474,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
isConnected,
|
||||
markNotificationRead,
|
||||
markAllNotificationsRead,
|
||||
scenarioNotificationsOn: Realtime_Notifications_UI?.treatment === "on"
|
||||
scenarioNotificationsOn
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
@@ -10,23 +10,17 @@ import PaymentsListPaginated from "../../components/payments-list-paginated/paym
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import { QUERY_ALL_PAYMENTS_PAGINATED } from "../../graphql/payments.queries";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import UpsellComponent, { upsellEnum } from "../../components/upsell/upsell.component";
|
||||
import { Card } from "antd";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
|
||||
});
|
||||
|
||||
export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { page, sortcolumn, sortorder, searchObj } = searchParams;
|
||||
|
||||
@@ -60,25 +54,15 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
return (
|
||||
<FeatureWrapperComponent
|
||||
featureName="payments"
|
||||
noauth={
|
||||
<Card>
|
||||
<UpsellComponent upsell={upsellEnum().payments.general} />
|
||||
</Card>
|
||||
}
|
||||
z
|
||||
>
|
||||
<RbacWrapper action="payments:list">
|
||||
<PaymentsListPaginated
|
||||
refetch={refetch}
|
||||
loading={loading}
|
||||
searchParams={searchParams}
|
||||
total={data ? data.payments_aggregate.aggregate.count : 0}
|
||||
payments={data ? data.payments : []}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
</FeatureWrapperComponent>
|
||||
<RbacWrapper action="payments:list">
|
||||
<PaymentsListPaginated
|
||||
refetch={refetch}
|
||||
loading={loading}
|
||||
searchParams={searchParams}
|
||||
total={data ? data.payments_aggregate.aggregate.count : 0}
|
||||
payments={data ? data.payments : []}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
DROP INDEX IF EXISTS "public"."notificiations_idx_jobs";
|
||||
@@ -1,2 +0,0 @@
|
||||
CREATE INDEX "notificiations_idx_jobs" on
|
||||
"public"."notifications" using btree ("jobid");
|
||||
@@ -1 +0,0 @@
|
||||
DROP INDEX IF EXISTS "public"."notifications_idx_associations";
|
||||
@@ -1,2 +0,0 @@
|
||||
CREATE INDEX "notifications_idx_associations" on
|
||||
"public"."notifications" using btree ("associationid");
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_notifications_created_at_not_read ON notifications(created_at desc, read) where read is null;
|
||||
@@ -1 +0,0 @@
|
||||
CREATE INDEX idx_notifications_created_at_not_read ON notifications(created_at desc, read) where read is null;
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_notifications_associations_not_read ON notifications(associationid, read) where read is null;
|
||||
@@ -1 +0,0 @@
|
||||
CREATE INDEX idx_notifications_associations_not_read ON notifications(associationid, read) where read is null;
|
||||
@@ -20,11 +20,6 @@ const defaultFooter = () => {
|
||||
|
||||
const now = () => moment().format("MM/DD/YYYY @ hh:mm a");
|
||||
|
||||
/**
|
||||
* Generate the email template
|
||||
* @param strings
|
||||
* @returns {string}
|
||||
*/
|
||||
const generateEmailTemplate = (strings) => {
|
||||
return (
|
||||
`
|
||||
|
||||
@@ -5,7 +5,6 @@ const { InstanceEndpoints } = require("../../utils/instanceMgr");
|
||||
const { registerCleanupTask } = require("../../utils/cleanupManager");
|
||||
const getBullMQPrefix = require("../../utils/getBullMQPrefix");
|
||||
const devDebugLogger = require("../../utils/devDebugLogger");
|
||||
const moment = require("moment-timezone");
|
||||
|
||||
const EMAIL_CONSOLIDATION_DELAY_IN_MINS = (() => {
|
||||
const envValue = process.env?.EMAIL_CONSOLIDATION_DELAY_IN_MINS;
|
||||
@@ -60,7 +59,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
emailAddWorker = new Worker(
|
||||
"emailAdd",
|
||||
async (job) => {
|
||||
const { jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients } = job.data;
|
||||
const { jobId, jobRoNumber, bodyShopName, body, recipients } = job.data;
|
||||
devDebugLogger(`Adding email notifications for jobId ${jobId}`);
|
||||
|
||||
const redisKeyPrefix = `email:${devKey}:notifications:${jobId}`;
|
||||
@@ -73,7 +72,6 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
const detailsKey = `email:${devKey}:recipientDetails:${jobId}:${user}`;
|
||||
await pubClient.hsetnx(detailsKey, "firstName", firstName || "");
|
||||
await pubClient.hsetnx(detailsKey, "lastName", lastName || "");
|
||||
await pubClient.hsetnx(detailsKey, "bodyShopTimezone", bodyShopTimezone);
|
||||
await pubClient.expire(detailsKey, NOTIFICATION_EXPIRATION / 1000);
|
||||
await pubClient.sadd(`email:${devKey}:recipients:${jobId}`, user);
|
||||
devDebugLogger(`Stored message for ${user} under ${userKey}: ${body}`);
|
||||
@@ -84,7 +82,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
if (flagSet) {
|
||||
await emailConsolidateQueue.add(
|
||||
"consolidate-emails",
|
||||
{ jobId, jobRoNumber, bodyShopName, bodyShopTimezone },
|
||||
{ jobId, jobRoNumber, bodyShopName },
|
||||
{
|
||||
jobId: `consolidate:${jobId}`,
|
||||
delay: EMAIL_CONSOLIDATION_DELAY,
|
||||
@@ -127,11 +125,9 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
const firstName = details.firstName || "User";
|
||||
const multipleUpdateString = messages.length > 1 ? "Updates" : "Update";
|
||||
const subject = `${multipleUpdateString} for job ${jobRoNumber || "N/A"} at ${bodyShopName}`;
|
||||
const timezone = moment.tz.zone(details?.bodyShopTimezone) ? details.bodyShopTimezone : "UTC";
|
||||
const emailBody = generateEmailTemplate({
|
||||
header: `${multipleUpdateString} for Job ${jobRoNumber || "N/A"}`,
|
||||
subHeader: `Dear ${firstName},`,
|
||||
dateLine: moment().tz(timezone).format("MM/DD/YYYY hh:mm a"),
|
||||
body: `
|
||||
<p>There have been updates to job ${jobRoNumber || "N/A"} at ${bodyShopName}:</p><br/>
|
||||
<ul>
|
||||
@@ -230,10 +226,10 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
|
||||
const emailAddQueue = getQueue();
|
||||
|
||||
for (const email of emailsToDispatch) {
|
||||
const { jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients } = email;
|
||||
const { jobId, jobRoNumber, bodyShopName, body, recipients } = email;
|
||||
|
||||
if (!jobId || !jobRoNumber || !bodyShopName || !body || !recipients.length) {
|
||||
devDebugLogger(
|
||||
logger.logger.warn(
|
||||
`Skipping email dispatch for jobId ${jobId} due to missing data: ` +
|
||||
`jobRoNumber=${jobRoNumber || "N/A"}, bodyShopName=${bodyShopName}, body=${body}, recipients=${recipients.length}`
|
||||
);
|
||||
@@ -242,7 +238,7 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
|
||||
|
||||
await emailAddQueue.add(
|
||||
"add-email-notification",
|
||||
{ jobId, jobRoNumber, bodyShopName, bodyShopTimezone, body, recipients },
|
||||
{ jobId, jobRoNumber, bodyShopName, body, recipients },
|
||||
{ jobId: `${jobId}:${Date.now()}` }
|
||||
);
|
||||
devDebugLogger(`Added email notification to queue for jobId ${jobId} with ${recipients.length} recipients`);
|
||||
|
||||
@@ -28,7 +28,6 @@ const buildNotification = (data, key, body, variables = {}) => {
|
||||
jobId: data.jobId,
|
||||
jobRoNumber: data.jobRoNumber,
|
||||
bodyShopName: data.bodyShopName,
|
||||
bodyShopTimezone: data.bodyShopTimezone,
|
||||
body,
|
||||
recipients: []
|
||||
},
|
||||
|
||||
@@ -248,7 +248,8 @@ async function OpenSearchSearchHandler(req, res) {
|
||||
"*ownr_fn^8",
|
||||
"*ownr_co_nm^8",
|
||||
"*ownr_ph1^8",
|
||||
"*ownr_ph2^8"
|
||||
"*ownr_ph2^8",
|
||||
"*comment^6"
|
||||
// "*"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user