Merged in release/2021-12-10 (pull request #300)

Release/2021 12 10
This commit is contained in:
Patrick Fic
2021-12-10 21:51:05 +00:00
41 changed files with 1395 additions and 877 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -14199,6 +14199,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>tryagain</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>view</name>
<definition_loaded>false</definition_loaded>
@@ -14246,6 +14267,27 @@
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>fcm</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>notfound</name>
<definition_loaded>false</definition_loaded>
@@ -14634,6 +14676,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>help</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>hours</name>
<definition_loaded>false</definition_loaded>
@@ -29864,6 +29927,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>nopush</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>phonenumber</name>
<definition_loaded>false</definition_loaded>

View File

@@ -4,21 +4,21 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.5.5",
"@apollo/client": "^3.4.17",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.2",
"@craco/craco": "^6.4.0",
"@fingerprintjs/fingerprintjs": "^3.3.0",
"@openreplay/tracker": "^3.4.10",
"@openreplay/tracker-assist": "^3.4.9",
"@openreplay/tracker": "^3.4.7",
"@openreplay/tracker-assist": "^3.4.4",
"@openreplay/tracker-graphql": "^3.0.0",
"@openreplay/tracker-redux": "^3.4.8",
"@sentry/react": "^6.15.0",
"@sentry/tracing": "^6.15.0",
"@openreplay/tracker-redux": "^3.0.0",
"@sentry/react": "^6.14.3",
"@sentry/tracing": "^6.14.3",
"@splitsoftware/splitio-react": "^1.3.0",
"@stripe/react-stripe-js": "^1.6.0",
"@stripe/stripe-js": "^1.21.2",
"@stripe/stripe-js": "^1.21.1",
"@tanem/react-nprogress": "^3.0.82",
"antd": "^4.17.3",
"antd": "^4.16.13",
"apollo-link-logger": "^2.0.0",
"axios": "^0.24.0",
"craco-less": "^1.20.0",
@@ -27,17 +27,17 @@
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.6.0",
"firebase": "^9.4.1",
"graphql": "^16.0.1",
"i18next": "^21.5.4",
"i18next": "^21.4.2",
"i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.7",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.44",
"logrocket": "^2.1.2",
"markerjs2": "^2.17.2",
"libphonenumber-js": "^1.9.42",
"logrocket": "^2.1.1",
"markerjs2": "^2.17.0",
"moment-business-days": "^1.2.0",
"phone": "^3.1.10",
"phone": "^3.1.9",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",
"query-string": "^7.0.1",
@@ -51,7 +51,7 @@
"react-drag-listview": "^0.1.8",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.0",
"react-i18next": "^11.14.3",
"react-i18next": "^11.14.2",
"react-icons": "^4.3.1",
"react-number-format": "^4.8.0",
"react-redux": "^7.2.6",
@@ -65,24 +65,24 @@
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.1.5",
"sass": "^1.44.0",
"socket.io-client": "^4.4.0",
"reselect": "^4.1.2",
"sass": "^1.43.4",
"socket.io-client": "^4.3.2",
"styled-components": "^5.3.3",
"subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.2",
"workbox-background-sync": "^6.4.2",
"workbox-broadcast-update": "^6.4.2",
"workbox-cacheable-response": "^6.4.2",
"workbox-core": "^6.4.2",
"workbox-expiration": "^6.4.2",
"workbox-google-analytics": "^6.4.2",
"workbox-navigation-preload": "^6.4.2",
"workbox-precaching": "^6.4.2",
"workbox-range-requests": "^6.4.2",
"workbox-routing": "^6.4.2",
"workbox-strategies": "^6.4.2",
"workbox-streams": "^6.4.2"
"workbox-background-sync": "^6.3.0",
"workbox-broadcast-update": "^6.3.0",
"workbox-cacheable-response": "^6.3.0",
"workbox-core": "^6.3.0",
"workbox-expiration": "^6.3.0",
"workbox-google-analytics": "^6.3.0",
"workbox-navigation-preload": "^6.3.0",
"workbox-precaching": "^6.3.0",
"workbox-range-requests": "^6.3.0",
"workbox-routing": "^6.3.0",
"workbox-strategies": "^6.3.0",
"workbox-streams": "^6.3.0"
},
"scripts": {
"postinstall": "patch-package",

View File

@@ -1,53 +1,56 @@
importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js");
importScripts(
"https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js"
);
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
firebase.initializeApp({
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
authDomain: "imex-prod.firebaseapp.com",
databaseURL: "https://imex-prod.firebaseio.com",
projectId: "imex-prod",
storageBucket: "imex-prod.appspot.com",
messagingSenderId: "253497221485",
appId: "1:253497221485:web:3c81c483b94db84b227a64",
measurementId: "G-NTWBKG2L0M",
});
// Initialize the Firebase app in the service worker by passing the generated config
let firebaseConfig;
switch (this.location.hostname) {
case "localhost":
firebaseConfig = {
apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
authDomain: "imex-dev.firebaseapp.com",
databaseURL: "https://imex-dev.firebaseio.com",
projectId: "imex-dev",
storageBucket: "imex-dev.appspot.com",
messagingSenderId: "759548147434",
appId: "1:759548147434:web:e8239868a48ceb36700993",
measurementId: "G-K5XRBVVB4S",
};
break;
case "test.imex.online":
firebaseConfig = {
apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c",
authDomain: "imex-test.firebaseapp.com",
projectId: "imex-test",
storageBucket: "imex-test.appspot.com",
messagingSenderId: "991923618608",
appId: "1:991923618608:web:633437569cdad78299bef5",
// measurementId: "${config.measurementId}",
};
break;
case "imex.online":
default:
firebaseConfig = {
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
authDomain: "imex-prod.firebaseapp.com",
databaseURL: "https://imex-prod.firebaseio.com",
projectId: "imex-prod",
storageBucket: "imex-prod.appspot.com",
messagingSenderId: "253497221485",
appId: "1:253497221485:web:3c81c483b94db84b227a64",
measurementId: "G-NTWBKG2L0M",
};
}
// firebase.initializeApp({
// apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
// authDomain: "imex-dev.firebaseapp.com",
// databaseURL: "https://imex-dev.firebaseio.com",
// projectId: "imex-dev",
// storageBucket: "imex-dev.appspot.com",
// messagingSenderId: "759548147434",
// appId: "1:759548147434:web:e8239868a48ceb36700993",
// measurementId: "G-K5XRBVVB4S",
// });
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
self.addEventListener("fetch", (fetch) => {
//required for installation as a PWA. Can ignore for now.
//console.log("fetch", fetch);
});
messaging.onBackgroundMessage(function (payload) {
// Customize notification here
const channel = new BroadcastChannel("imex-sw-messages");
channel.postMessage(payload);
messaging.setBackgroundMessageHandler(function (payload) {
return self.registration.showNotification(
"[SW]" + payload.notification.title,
payload.notification
);
});
//Handles the notification getting clicked.
self.addEventListener("notificationclick", function (event) {
console.log("SW notificationclick", event);
// event.notification.close();
if (event.action === "archive") {
// Archive action was clicked
archiveEmail();
} else {
// Main body of notification was clicked
clients.openWindow("/inbox");
}
//self.registration.showNotification(notificationTitle, notificationOptions);
});

View File

@@ -1,7 +1,4 @@
import { ApolloProvider } from "@apollo/client";
//import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker";
import trackerGraphQL from "@openreplay/tracker-graphql";
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
@@ -10,30 +7,13 @@ import moment from "moment";
import React from "react";
import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
//import trackerAssist from "@openreplay/tracker-assist";
import { getCurrentUser } from "../firebase/firebase.utils";
import client from "../utils/GraphQLClient";
import App from "./App";
moment.locale("en-US");
export const tracker = new Tracker({
projectKey: "trDmOZlEXUpjGsMtHroA",
ingestPoint: "https://replay.imex.online/ingest",
...(process.env.NODE_ENV === null || process.env.NODE_ENV === "development"
? { __DISABLE_SECURE_MODE: true }
: {}),
// beaconSize: 10485760,
onStart: async ({ sessionID }) => {
const user = await getCurrentUser();
if (user) tracker.setUserID(user.email);
},
});
// tracker.use(
// trackerAssist({ confirmText: "Technical support is about to assist you." })
// ); // check the list of available options below
export const recordGraphQL = tracker.use(trackerGraphQL());
//tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");

View File

@@ -1,43 +0,0 @@
import { MessageOutlined } from "@ant-design/icons";
import { Badge, Card } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import ChatPopupComponent from '../chat-popup/chat-popup.component'
const mapStateToProps = createStructuredSelector({
chatVisible: selectChatVisible,
});
const mapDispatchToProps = (dispatch) => ({
toggleChatVisible: () => dispatch(toggleChatVisible()),
});
export function ChatAffixComponent({
chatVisible,
toggleChatVisible,
conversationList,
unreadCount,
}) {
const { t } = useTranslation();
return (
<Badge count={unreadCount}>
<Card size='small'>
{chatVisible ? (
<ChatPopupComponent conversationList={conversationList} />
) : (
<div
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}>
<MessageOutlined />
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}
</Card>
</Badge>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatAffixComponent);

View File

@@ -1,13 +1,16 @@
import { useSubscription } from "@apollo/client";
import React from "react";
import { useApolloClient } from "@apollo/client";
import { getToken, onMessage } from "@firebase/messaging";
import { Button, notification, Space } from "antd";
import axios from "axios";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERSATION_LIST_SUBSCRIPTION } from "../../graphql/conversations.queries";
import { messaging, requestForToken } from "../../firebase/firebase.utils";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import ChatAffixComponent from "./chat-affix.component";
import FcmHandler from "../../utils/fcm-handler";
import ChatPopupComponent from "../chat-popup/chat-popup.component";
import "./chat-affix.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -16,32 +19,87 @@ const mapStateToProps = createStructuredSelector({
});
export function ChatAffixContainer({ bodyshop, chatVisible }) {
const { loading, error, data } = useSubscription(
CONVERSATION_LIST_SUBSCRIPTION,
{
skip: !bodyshop || (bodyshop && !bodyshop.messagingservicesid),
}
);
const { t } = useTranslation();
const client = useApolloClient();
useEffect(() => {
if (!bodyshop || !bodyshop.messagingservicesid) return;
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
async function SubscribeToTopic() {
try {
const r = await axios.post("/notifications/subscribe", {
fcm_tokens: await getToken(messaging, {
vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY,
}),
type: "messaging",
imexshopid: bodyshop.imexshopid,
});
console.log("FCM Topic Subscription", r.data);
} catch (error) {
console.log(
"Error attempting to subscribe to messaging topic: ",
error
);
notification.open({
type: "warning",
message: t("general.errors.fcm"),
btn: (
<Space>
<Button
onClick={async () => {
await requestForToken();
SubscribeToTopic();
}}
>
{t("general.actions.tryagain")}
</Button>
<Button
onClick={() => {
const win = window.open(
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
"_blank"
);
win.focus();
}}
>
{t("general.labels.help")}
</Button>
</Space>
),
});
}
}
SubscribeToTopic();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bodyshop]);
useEffect(() => {
function handleMessage(payload) {
FcmHandler({
client,
payload: (payload && payload.data && payload.data.data) || payload.data,
});
}
let stopMessageListenr, channel;
try {
stopMessageListenr = onMessage(messaging, handleMessage);
channel = new BroadcastChannel("imex-sw-messages");
channel.addEventListener("message", handleMessage);
} catch (error) {
console.log("Unable to set event listeners.");
}
return () => {
stopMessageListenr && stopMessageListenr();
channel && channel.removeEventListener("message", handleMessage);
};
}, [client]);
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
return (
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
{bodyshop && bodyshop.messagingservicesid ? (
<ChatAffixComponent
conversationList={(data && data.conversations) || []}
unreadCount={
(data &&
data.conversations.reduce((acc, val) => {
return (acc = acc + val.messages_aggregate.aggregate.count);
}, 0)) ||
0
}
/>
) : null}
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
</div>
);
}

View File

@@ -1,14 +1,12 @@
import { Badge, List, Tag, Tooltip } from "antd";
import { AlertFilled } from "@ant-design/icons";
import { Badge, List, Tag } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import "./chat-conversation-list.styles.scss";
import { useTranslation } from "react-i18next";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
@@ -24,8 +22,6 @@ export function ChatConversationListComponent({
selectedConversation,
setSelectedConversation,
}) {
const { t } = useTranslation();
return (
<div className="chat-list-container">
<List
@@ -33,6 +29,7 @@ export function ChatConversationListComponent({
dataSource={conversationList}
renderItem={(item) => (
<List.Item
key={item.id}
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
@@ -43,19 +40,9 @@ export function ChatConversationListComponent({
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx} style={{ display: "flex" }}>
{j.job.owner && !j.job.owner.allow_text_message && (
<Tooltip title={t("messaging.labels.noallowtxt")}>
<AlertFilled
className="production-alert"
style={{ marginRight: ".3rem", alignItems: "center" }}
/>
</Tooltip>
)}
<div>{`${j.job.ownr_fn || ""} ${j.job.ownr_ln || ""} ${
j.job.ownr_co_nm || ""
} `}</div>
</div>
<div key={idx}>{`${j.job.ownr_fn || ""} ${
j.job.ownr_ln || ""
} ${j.job.ownr_co_nm || ""} `}</div>
))}
</div>
) : (

View File

@@ -9,6 +9,7 @@ import "./chat-conversation.styles.scss";
export default function ChatConversationComponent({
subState,
conversation,
messages,
handleMarkConversationAsRead,
}) {
const [loading, error] = subState;
@@ -16,8 +17,6 @@ export default function ChatConversationComponent({
if (loading) return <LoadingSkeleton />;
if (error) return <AlertComponent message={error.message} type="error" />;
const messages = (conversation && conversation.messages) || [];
return (
<div
className="chat-conversation"

View File

@@ -1,18 +1,32 @@
import { useMutation, useSubscription } from "@apollo/client";
import { useMutation, useQuery, useSubscription } from "@apollo/client";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
import {
CONVERSATION_SUBSCRIPTION_BY_PK,
GET_CONVERSATION_DETAILS,
} from "../../graphql/conversations.queries";
import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import ChatConversationComponent from "./chat-conversation.component";
import axios from "axios";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
bodyshop: selectBodyshop,
});
export default connect(mapStateToProps, null)(ChatConversationContainer);
export function ChatConversationContainer({ selectedConversation }) {
export function ChatConversationContainer({ bodyshop, selectedConversation }) {
const {
loading: convoLoading,
error: convoError,
data: convoData,
} = useQuery(GET_CONVERSATION_DETAILS, {
variables: { conversationId: selectedConversation },
});
const { loading, error, data } = useSubscription(
CONVERSATION_SUBSCRIPTION_BY_PK,
{
@@ -26,30 +40,46 @@ export function ChatConversationContainer({ selectedConversation }) {
MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{
variables: { conversationId: selectedConversation },
update(cache) {
cache.modify({
id: cache.identify({
__typename: "conversations",
id: selectedConversation,
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: 0 } };
},
},
});
},
}
);
const unreadCount =
(data &&
data.conversations_by_pk &&
data.conversations_by_pk &&
data.conversations_by_pk.messages_aggregate &&
data.conversations_by_pk.messages_aggregate.aggregate &&
data.conversations_by_pk.messages_aggregate.aggregate.count) ||
0;
data &&
data.messages &&
data.messages.reduce((acc, val) => {
return !val.read && !val.isoutbound ? acc + 1 : acc;
}, 0);
const handleMarkConversationAsRead = async () => {
if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) {
setMarkingAsReadInProgress(true);
await markConversationRead();
await markConversationRead({});
await axios.post("/sms/markConversationRead", {
conversationid: selectedConversation,
imexshopid: bodyshop.imexshopid,
});
setMarkingAsReadInProgress(false);
}
};
return (
<ChatConversationComponent
subState={[loading, error]}
conversation={data ? data.conversations_by_pk : {}}
subState={[loading || convoLoading, error || convoError]}
conversation={convoData ? convoData.conversations_by_pk : {}}
messages={data ? data.messages : []}
handleMarkConversationAsRead={handleMarkConversationAsRead}
/>
);

View File

@@ -46,7 +46,7 @@ export function ChatNewConversation({ openChatByPhone }) {
return (
<Popover trigger="click" content={popContent}>
<PlusCircleFilled style={{ margin: "1rem" }} />
<PlusCircleFilled />
</Popover>
);
}

View File

@@ -1,54 +1,119 @@
import { ShrinkOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { Col, Row, Tooltip, Typography } from "antd";
import React from "react";
import {
InfoCircleOutlined,
MessageOutlined,
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import {
selectChatVisible,
selectSelectedConversation,
} from "../../redux/messaging/messaging.selectors";
import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component";
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import "./chat-popup.styles.scss";
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import "./chat-popup.styles.scss";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
chatVisible: selectChatVisible,
});
const mapDispatchToProps = (dispatch) => ({
toggleChatVisible: () => dispatch(toggleChatVisible()),
});
export function ChatPopupComponent({
conversationList,
chatVisible,
selectedConversation,
toggleChatVisible,
}) {
const { t } = useTranslation();
return (
<div className="chat-popup">
<div style={{ display: "flex", alignItems: "center" }}>
<Typography.Title level={4}>
{t("messaging.labels.messaging")}
</Typography.Title>
<ChatNewConversation />
<Tooltip title={t("messaging.labels.recentonly")}>
<InfoCircleOutlined />
</Tooltip>
</div>
<ShrinkOutlined
onClick={() => toggleChatVisible()}
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
/>
const [pollInterval, setpollInterval] = useState(0);
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
...(pollInterval > 0 ? { pollInterval } : {}),
});
<Row gutter={[8, 8]} className="chat-popup-content">
<Col span={8}>
<ChatConversationListComponent conversationList={conversationList} />
</Col>
<Col span={16}>
{selectedConversation ? <ChatConversationContainer /> : null}
</Col>
</Row>
</div>
const fcmToken = sessionStorage.getItem("fcmtoken");
useEffect(() => {
if (fcmToken) {
setpollInterval(0);
} else {
setpollInterval(60000);
}
}, [fcmToken]);
useEffect(() => {
if (called && chatVisible) refetch();
}, [chatVisible, called, refetch]);
const unreadCount = data
? data.conversations.reduce(
(acc, val) => val.messages_aggregate.aggregate.count + acc,
0
)
: 0;
return (
<Badge count={unreadCount}>
<Card size="small">
{chatVisible ? (
<div className="chat-popup">
<Space align="center">
<Typography.Title level={4}>
{t("messaging.labels.messaging")}
</Typography.Title>
<ChatNewConversation />
<Tooltip title={t("messaging.labels.recentonly")}>
<InfoCircleOutlined />
</Tooltip>
<SyncOutlined
style={{ cursor: "pointer" }}
onClick={() => refetch()}
/>
{pollInterval > 0 && (
<Tag color="yellow">{t("messaging.labels.nopush")}</Tag>
)}
</Space>
<ShrinkOutlined
onClick={() => toggleChatVisible()}
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
/>
<Row gutter={[8, 8]} className="chat-popup-content">
<Col span={8}>
{loading ? (
<LoadingSpinner />
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
/>
)}
</Col>
<Col span={16}>
{selectedConversation ? <ChatConversationContainer /> : null}
</Col>
</Row>
</div>
) : (
<div
onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }}
>
<MessageOutlined />
<strong>{t("messaging.labels.messaging")}</strong>
</div>
)}
</Card>
</Badge>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent);

View File

@@ -9,7 +9,6 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { tracker } from "../../App/App.container";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -37,7 +36,6 @@ class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
console.log("Exception Caught by Error Boundary.", error, info);
this.setState({ ...this.state, error, info });
tracker.event("error_boundary", error, true);
}
handleErrorSubmit = () => {

View File

@@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
QUERY_EXACT_JOBS_IN_PRODUCTION,
QUERY_EXACT_JOB_IN_PRODUCTION,
QUERY_JOBS_IN_PRODUCTION,
SUBSCRIPTION_JOBS_IN_PRODUCTION,
@@ -42,16 +43,23 @@ export function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
if (!updatedJobs || joblist.length === 0) return;
const jobDiff = _.differenceWith(
updatedJobs.jobs,
joblist,
updatedJobs.jobs,
(a, b) => a.id === b.id && a.updated_at === b.updated_at
);
jobDiff.forEach((job) => {
getUpdatedJobData(job.id);
});
if (jobDiff.length > 1) {
getUpdatedJobsData(jobDiff.map((j) => j.id));
} else if (jobDiff.length === 1) {
jobDiff.forEach((job) => {
getUpdatedJobData(job.id);
});
}
setJoblist(updatedJobs);
setJoblist(updatedJobs.jobs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [updatedJobs]);
@@ -61,6 +69,12 @@ export function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
variables: { id: jobId },
});
};
const getUpdatedJobsData = async (jobIds) => {
client.query({
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
variables: { ids: jobIds },
});
};
const { loading: associationSettingsLoading, data: associationSettings } =
useQuery(QUERY_KANBAN_SETTINGS, {

View File

@@ -1,6 +1,7 @@
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
import React, { useEffect, useState } from "react";
import {
QUERY_EXACT_JOBS_IN_PRODUCTION,
QUERY_EXACT_JOB_IN_PRODUCTION,
QUERY_JOBS_IN_PRODUCTION,
SUBSCRIPTION_JOBS_IN_PRODUCTION,
@@ -31,16 +32,22 @@ export default function ProductionListTableContainer() {
if (!updatedJobs || joblist.length === 0) return;
const jobDiff = _.differenceWith(
updatedJobs.jobs,
joblist,
updatedJobs.jobs,
(a, b) => a.id === b.id && a.updated_at === b.updated_at
);
console.log(jobDiff);
if (jobDiff.length > 1) {
getUpdatedJobsData(jobDiff.map((j) => j.id));
} else if (jobDiff.length === 1) {
console.log("length was 1");
jobDiff.forEach((job) => {
console.log("Job ", job);
getUpdatedJobData(job.id);
});
}
jobDiff.forEach((job) => {
getUpdatedJobData(job.id);
});
setJoblist(updatedJobs);
setJoblist(updatedJobs.jobs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [updatedJobs]);
@@ -50,6 +57,12 @@ export default function ProductionListTableContainer() {
variables: { id: jobId },
});
};
const getUpdatedJobsData = async (jobIds) => {
client.query({
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
variables: { ids: jobIds },
});
};
return (
<ProductionListTable

View File

@@ -2,8 +2,7 @@ import { getAnalytics, logEvent } from "firebase/analytics";
import { initializeApp } from "firebase/app";
import { getAuth, updatePassword, updateProfile } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { tracker } from "../App/App.container";
//import { getMessaging } from "firebase/messaging";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { store } from "../redux/store";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
@@ -39,18 +38,42 @@ export const updateCurrentPassword = async (password) => {
return updatePassword(currentUser, password);
};
let messaging;
try {
messaging = getMessaging();
} catch (error) {
console.log(error);
}
//let messaging;
// try {
// messaging = getMessaging();
// // Project Settings => Cloud Messaging => Web Push certificates
// messaging.usePublicVapidKey(process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY);
// console.log("[FCM UTIL] FCM initialized successfully.");
// } catch {
// console.log("[FCM UTIL] Firebase Messaging is likely unsupported.");
// }
export { messaging };
// export { messaging };
export const requestForToken = () => {
return getToken(messaging, {
vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY,
})
.then((currentToken) => {
if (currentToken) {
console.log("current token for client: ", currentToken);
window.sessionStorage.setItem("fcmtoken", currentToken);
// Perform any other necessary action with the token
} else {
// Show permission request UI
console.log(
"No registration token available. Request permission to generate one."
);
}
})
.catch((err) => {
console.log("An error occurred while retrieving token. ", err);
});
};
export const onMessageListener = () =>
new Promise((resolve) => {
onMessage(messaging, (payload) => {
console.log("Inbound FCM Message", payload);
resolve(payload);
});
});
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
const state = stateProp || store.getState();
@@ -70,9 +93,6 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
// eventParams
// );
logEvent(analytics, eventName, eventParams);
//Log event to OpenReplay server.
tracker.event(eventName, eventParams);
};
// if (messaging) {

View File

@@ -1,15 +1,54 @@
import { gql } from "@apollo/client";
export const CONVERSATION_LIST_SUBSCRIPTION = gql`
subscription CONVERSATION_LIST_SUBSCRIPTION {
// export const CONVERSATION_LIST_SUBSCRIPTION = gql`
// subscription CONVERSATION_LIST_SUBSCRIPTION {
// conversations(
// order_by: { updated_at: desc }
// limit: 50
// where: { archived: { _eq: false } }
// ) {
// phone_num
// id
// updated_at
// unreadcnt
// messages_aggregate(
// where: { read: { _eq: false }, isoutbound: { _eq: false } }
// ) {
// aggregate {
// count
// }
// }
// job_conversations {
// job {
// id
// ro_number
// ownr_fn
// ownr_ln
// ownr_co_nm
// }
// }
// }
// }
// `;
export const CONVERSATION_LIST_QUERY = gql`
query CONVERSATION_LIST_QUERY {
conversations(
order_by: { updated_at: desc }
limit: 100
limit: 50
where: { archived: { _eq: false } }
) {
phone_num
id
updated_at
unreadcnt
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
}
}
job_conversations {
job {
id
@@ -17,17 +56,6 @@ export const CONVERSATION_LIST_SUBSCRIPTION = gql`
ownr_fn
ownr_ln
ownr_co_nm
owner {
id
allow_text_message
}
}
}
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
}
}
}
@@ -36,24 +64,26 @@ export const CONVERSATION_LIST_SUBSCRIPTION = gql`
export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
subscription CONVERSATION_SUBSCRIPTION_BY_PK($conversationId: uuid!) {
messages(
order_by: { created_at: asc_nulls_first }
where: { conversationid: { _eq: $conversationId } }
) {
id
status
text
isoutbound
image
image_path
userid
created_at
read
}
}
`;
export const GET_CONVERSATION_DETAILS = gql`
query GET_CONVERSATION_DETAILS($conversationId: uuid!) {
conversations_by_pk(id: $conversationId) {
messages(order_by: { created_at: asc_nulls_first }) {
id
status
text
isoutbound
image
image_path
userid
created_at
}
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
aggregate {
count
}
}
id
phone_num
archived

View File

@@ -185,6 +185,77 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
}
`;
export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
query QUERY_EXACT_JOBS_IN_PRODUCTION($ids: [uuid!]!) {
jobs(where: { id: { _in: $ids } }) {
id
status
ro_number
ownr_fn
ownr_ln
ownr_co_nm
v_model_yr
v_model_desc
clm_no
v_make_desc
v_color
plate_no
actual_in
scheduled_completion
scheduled_delivery
date_last_contacted
date_next_contact
ins_co_nm
clm_total
ownr_ph1
ownr_ph2
special_coverage_policy
owner_owing
production_vars
kanbanparent
alt_transport
employee_body
employee_refinish
employee_prep
employee_csr
labhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
}
) {
aggregate {
sum {
mod_lb_hrs
}
}
}
larhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }]
}
) {
aggregate {
sum {
mod_lb_hrs
}
}
}
subletLines: joblines(
where: {
_and: { part_type: { _in: ["PAS", "PASL"] }, removed: { _eq: false } }
}
order_by: { line_no: asc }
) {
id
line_desc
sublet_ignored
sublet_completed
jobid
}
}
}
`;
export const QUERY_JOBS_IN_PRODUCTION = gql`
query QUERY_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) {

View File

@@ -8,6 +8,8 @@ export const MARK_MESSAGES_AS_READ_BY_CONVERSATION = gql`
) {
returning {
id
read
isoutbound
}
}
}

View File

@@ -146,11 +146,13 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
{t("general.actions.close")}
</Button>
</Popconfirm>
<Link to={`/manage/dms?jobId=${job.id}`}>
<Button disabled={job.date_exported || !jobRO}>
{t("jobs.actions.sendtodms")}
</Button>
</Link>
{(bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid) && (
<Link to={`/manage/dms?jobId=${job.id}`}>
<Button disabled={job.date_exported || !jobRO}>
{t("jobs.actions.sendtodms")}
</Button>
</Link>
)}
<JobsScoreboardAdd job={job} disabled={false} />
</Space>
}

View File

@@ -5,13 +5,12 @@ import preval from "preval.macro";
import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import { Link, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
import ConflictComponent from "../../components/conflict/conflict.component";
import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
//import FooterComponent from "../../components/footer/footer.component";
//Component Imports
import HeaderContainer from "../../components/header/header.container";
@@ -20,12 +19,11 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import TestComponent from "../../components/_test/test.component";
import { QUERY_STRIPE_ID } from "../../graphql/bodyshop.queries";
import { requestForToken } from "../../firebase/firebase.utils";
import {
selectBodyshop,
selectInstanceConflict,
} from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
import "./manage.page.styles.scss";
const ManageRootPage = lazy(() =>
@@ -166,16 +164,15 @@ const Dms = lazy(() => import("../dms/dms.container"));
const { Content, Footer } = Layout;
const stripePromise = new Promise((resolve, reject) => {
client.query({ query: QUERY_STRIPE_ID }).then((resp) => {
if (resp.data.bodyshops[0])
resolve(
loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY, {
stripeAccount:
resp.data.bodyshops[0].stripe_acct_id || "No Stripe Id Resolve",
})
);
reject();
});
// client.query({ query: QUERY_STRIPE_ID }).then((resp) => {
// if (resp.data.bodyshops[0])
resolve(
loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY, {
stripeAccount: "No Stripe Id Resolve",
})
);
// reject();
// });
});
const mapStateToProps = createStructuredSelector({
@@ -188,7 +185,13 @@ export function Manage({ match, conflict, bodyshop }) {
useEffect(() => {
const widgetId = "IABVNO4scRKY11XBQkNr";
window.noticeable.render("widget", widgetId);
try {
requestForToken();
} catch (error) {
console.log("Unable to request for token.", error);
}
}, []);
useEffect(() => {
document.title = t("titles.app");
}, [t]);
@@ -395,7 +398,6 @@ export function Manage({ match, conflict, bodyshop }) {
<HeaderContainer />
<Content className="content-container">
<FcmNotification />
<PartnerPingComponent />
<ErrorBoundary>{PageContent}</ErrorBoundary>
<BackTop />

View File

@@ -5,7 +5,7 @@ import { connect } from "react-redux";
import { Redirect, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import TechHeader from "../../components/tech-header/tech-header.component";
import TechSider from "../../components/tech-sider/tech-sider.component";
@@ -58,7 +58,6 @@ export function TechPage({ technician, match }) {
{technician ? null : <Redirect to={`${match.path}/login`} />}
<TechHeader />
<Content className="tech-content-container">
<FcmNotification />
<ErrorBoundary>
<Suspense
fallback={

View File

@@ -1,7 +1,6 @@
import Fingerprint2 from "@fingerprintjs/fingerprintjs";
import * as Sentry from "@sentry/browser";
import { notification } from "antd";
import { auth, analytics, firestore } from "../../firebase/firebase.utils";
import { setUserId, setUserProperties } from "firebase/analytics";
import {
checkActionCode,
@@ -13,8 +12,11 @@ import { doc } from "firebase/firestore";
import i18next from "i18next";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { factory, tracker } from "../../App/App.container";
import { factory } from "../../App/App.container";
import {
analytics,
auth,
firestore,
getCurrentUser,
logImEXEvent,
updateCurrentUser,
@@ -75,7 +77,7 @@ export function* isUserAuthenticated() {
}
LogRocket.identify(user.email);
tracker.setUserID(user.email);
yield put(
signInSuccess({
uid: user.uid,
@@ -174,7 +176,6 @@ export function* onSignInSuccess() {
export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email);
tracker.setUserID(payload.email);
try {
// window.$crisp.push(["set", "user:email", [payload.email]]);

View File

@@ -899,10 +899,12 @@
"selectall": "Select All",
"senderrortosupport": "Send Error to Support",
"submit": "Submit",
"tryagain": "Try Again",
"view": "View",
"viewreleasenotes": "See What's Changed"
},
"errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
"notfound": "No record was found."
},
"itemtypes": {
@@ -925,6 +927,7 @@
"exceptiontitle": "An error has occurred.",
"friday": "Friday",
"globalsearch": "Global Search",
"help": "Help",
"hours": "hrs",
"in": "In",
"instanceconflictext": "Your $t(titles.app) account can only be used on one device at any given time. Refresh your session to take control.",
@@ -1763,6 +1766,7 @@
"messaging": "Messaging",
"noallowtxt": "This customer has not indicated their permission to be messaged.",
"nojobs": "Not associated to any job.",
"nopush": "Polling Mode Enabled",
"phonenumber": "Phone #",
"presets": "Presets",
"recentonly": "Only your most recent 50 conversations will be shown here. If you are looking for an older conversation, find the related contact and click their phone number to view the conversation.",

View File

@@ -899,10 +899,12 @@
"selectall": "",
"senderrortosupport": "",
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": ""
},
"errors": {
"fcm": "",
"notfound": ""
},
"itemtypes": {
@@ -925,6 +927,7 @@
"exceptiontitle": "",
"friday": "",
"globalsearch": "",
"help": "",
"hours": "",
"in": "en",
"instanceconflictext": "",
@@ -1763,6 +1766,7 @@
"messaging": "Mensajería",
"noallowtxt": "",
"nojobs": "",
"nopush": "",
"phonenumber": "",
"presets": "",
"recentonly": "",

View File

@@ -899,10 +899,12 @@
"selectall": "",
"senderrortosupport": "",
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": ""
},
"errors": {
"fcm": "",
"notfound": ""
},
"itemtypes": {
@@ -925,6 +927,7 @@
"exceptiontitle": "",
"friday": "",
"globalsearch": "",
"help": "",
"hours": "",
"in": "dans",
"instanceconflictext": "",
@@ -1763,6 +1766,7 @@
"messaging": "Messagerie",
"noallowtxt": "",
"nojobs": "",
"nopush": "",
"phonenumber": "",
"presets": "",
"recentonly": "",

View File

@@ -91,13 +91,11 @@ export default function CiecaSelect(parts = true, labor = true) {
}
export function GetPartTypeName(part_type) {
console.log(part_type);
if (!part_type) return null;
return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`);
}
export function Get(part_type) {
console.log(part_type);
if (!part_type) return null;
return i18n.t(`joblines.fields.part_types.${part_type.toUpperCase()}`);
}

View File

@@ -50,13 +50,18 @@ const roundTripLink = new ApolloLink((operation, forward) => {
const TrackExecutionTime = async (operationName, time) => {
const rdxStore = store.getState();
try {
console.log("trying");
axios.post("/ioevent", {
operationName,
time,
dbevent: true,
user: rdxStore.user.currentUser.email,
imexshopid: rdxStore.user.bodyshop.imexshopid,
user:
rdxStore.user &&
rdxStore.user.currentUser &&
rdxStore.user.currentUser.email,
imexshopid:
rdxStore.user &&
rdxStore.user.bodyshop &&
rdxStore.user.bodyshop.imexshopid,
});
} catch (error) {
console.log("IOEvent Error", error);

View File

@@ -148,8 +148,6 @@ export async function RenderTemplates(
},
};
console.log("reportRequest", reportRequest);
try {
const render = await jsreport.renderAsync(reportRequest);
if (!renderAsHtml) {

View File

@@ -0,0 +1,34 @@
export default async function FcmHandler({ client, payload }) {
console.log("Handling payload type", payload);
switch (payload.type) {
case "messaging-inbound":
client.cache.modify({
id: client.cache.identify({
__typename: "conversations",
id: payload.conversationid,
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: cached.aggregate.count + 1 } };
},
},
});
break;
case "messaging-mark-conversation-read":
client.cache.modify({
id: client.cache.identify({
__typename: "conversations",
id: payload.conversationid,
}),
fields: {
messages_aggregate(cached) {
return { aggregate: { count: 0 } };
},
},
});
break;
default:
console.log("No payload type set.");
break;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1172,11 +1172,12 @@
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- bodyshopid
- created_at
- id
- phone_num
- unreadcnt
- updated_at
select_permissions:
- role: user
permission:
@@ -1186,6 +1187,7 @@
- created_at
- id
- phone_num
- unreadcnt
- updated_at
filter:
bodyshop:
@@ -1206,6 +1208,7 @@
- created_at
- id
- phone_num
- unreadcnt
- updated_at
filter:
bodyshop:

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."conversations" add column "unreadcnt" numeric
-- not null default '0';

View File

@@ -0,0 +1,2 @@
alter table "public"."conversations" add column "unreadcnt" numeric
not null default '0';

View File

@@ -66,7 +66,6 @@ app.get("/test", async function (req, res) {
"git rev-parse --short HEAD"
);
logger.log("test-api-status", "DEBUG", "api", { commit });
res.status(200).send(`OK - ${commit}`);
});
@@ -114,6 +113,7 @@ app.post(
twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }),
smsStatus.status
);
app.post("/sms/markConversationRead", smsStatus.markConversationRead);
var job = require("./server/job/job");
app.post("/job/totals", fb.validateFirebaseIdToken, job.totals);
@@ -133,9 +133,10 @@ app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
app.post(
"/notifications/send",
fb.validateFirebaseIdToken,
fb.sendNotification
);
app.post("/notifications/subscribe", fb.validateFirebaseIdToken, fb.subscribe);
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
//Stripe Processing

View File

@@ -15,7 +15,7 @@ const CalculateAllocations =
require("../../cdk/cdk-calculate-allocations").default;
const CdkBase = require("../../web-sockets/web-socket");
const moment = require("moment");
const Dinero = require("dinero.js");
const axios = AxiosLib.create();
axios.interceptors.request.use((x) => {
const socket = x.socket;
@@ -116,8 +116,9 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(
);
await UpsertVehicleData(socket, ownerRef.ReferenceId);
CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
return;
await InsertAccountPostingData(socket);
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
await MarkJobExported(socket, socket.JobData.id);
@@ -535,7 +536,7 @@ async function InsertAccountPostingData(socket) {
const item = {
Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.0"),
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
@@ -549,7 +550,7 @@ async function InsertAccountPostingData(socket) {
const item = {
Account: alloc.costCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.cost.toFormat("0.0"),
Amount: alloc.cost.toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
@@ -560,7 +561,7 @@ async function InsertAccountPostingData(socket) {
const itemWip = {
Account: alloc.costCenter.dms_wip_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.cost.multiply(-1).toFormat("0.0"),
Amount: alloc.cost.multiply(-1).toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
@@ -575,7 +576,7 @@ async function InsertAccountPostingData(socket) {
const item2 = {
Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.0"),
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
@@ -585,6 +586,21 @@ async function InsertAccountPostingData(socket) {
}
}
});
socket.txEnvelope.payers.forEach((payer) => {
const item = {
Account: payer.dms_acctnumber,
ControlNumber: payer.controlnumber,
Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat(
"0.0"
),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
};
wips.push(item);
});
const { data: AccountPostingChange } = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange,

View File

@@ -74,7 +74,7 @@ exports.default = async (req, res) => {
{
version: "1.0",
encoding: "UTF-8",
keepNullNodes: true,
//keepNullNodes: true,
},
autoHouseObject
)
@@ -110,7 +110,8 @@ exports.default = async (req, res) => {
});
}
}
res.json(allxmlsToUpload);
return;
if (process.env.NODE_ENV !== "production") {
res.sendStatus(200);
return;
@@ -184,31 +185,31 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
},
CustomerInformation: {
FirstName: job.ownr_fn,
LastName: job.ownr_ln,
Street: job.ownr_addr1,
City: job.ownr_city,
State: job.ownr_st,
Zip: job.ownr_zip,
Phone1: job.ownr_ph1,
FirstName: job.ownr_fn || "",
LastName: job.ownr_ln || "",
Street: job.ownr_addr1 || "",
City: job.ownr_city || "",
State: job.ownr_st || "",
Zip: job.ownr_zip || "",
Phone1: job.ownr_ph1 || "",
Phone2: null,
Phone2Extension: null,
Phone3: null,
Phone3Extension: null,
FileComments: null,
Source: null,
Email: job.ownr_ea,
Email: job.ownr_ea || "",
RetWhsl: null,
Cat: null,
InsuredorClaimantFlag: null,
},
VehicleInformation: {
Year: job.v_model_yr,
Make: job.v_make_desc,
Model: job.v_model_desc,
VIN: job.v_vin,
Year: job.v_model_yr || "",
Make: job.v_make_desc || "",
Model: job.v_model_desc || "",
VIN: job.v_vin || "",
License: job.plate_no,
MileageIn: job.kmin,
MileageIn: job.kmin || 0,
Vehiclecolor: job.v_color,
VehicleProductionDate: null,
VehiclePaintCode: null,
@@ -218,18 +219,18 @@ const CreateRepairOrderTag = (job, errorCallback) => {
},
InsuranceInformation: {
InsuranceCo: job.ins_co_nm,
CompanyName: job.ins_co_nm,
Address: job.ins_addr1,
City: job.ins_addr1,
State: job.ins_city,
Zip: job.ins_zip,
Phone: job.ins_ph1,
Fax: null,
InsuranceCo: job.ins_co_nm || "",
CompanyName: job.ins_co_nm || "",
Address: job.ins_addr1 || "",
City: job.ins_addr1 || "",
State: job.ins_city || "",
Zip: job.ins_zip || "",
Phone: job.ins_ph1 || "",
Fax: job.ins_fax || "",
ClaimType: null,
LossType: null,
Policy: null,
Claim: job.clm_no,
LossType: job.loss_type || "",
Policy: job.policy_no || "",
Claim: job.clm_no || "",
InsuredLastName: null,
InsuredFirstName: null,
ClaimantLastName: null,
@@ -242,23 +243,28 @@ const CreateRepairOrderTag = (job, errorCallback) => {
OutsideAdjuster: null,
},
Dates: {
DateofLoss: job.loss_date && moment(job.loss_date).format(AhDateFormat),
DateofLoss:
(job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
InitialCustomerContactDate: null,
FirstFollowUpDate: null,
ReferralDate: null,
EstimateAppointmentDate: null,
SecondFollowUpDate: null,
AssignedDate:
job.asgn_date && moment(job.asgn_date).format(AhDateFormat),
(job.asgn_date && moment(job.asgn_date).format(AhDateFormat)) || "",
EstComplete: null,
CustomerAuthorizationDate: null,
InsuranceAuthorizationDate: null,
DateOpened: job.date_open && moment(job.date_open).format(AhDateFormat),
DateOpened:
(job.date_open && moment(job.date_open).format(AhDateFormat)) || "",
ScheduledArrivalDate:
job.scheduled_in && moment(job.scheduled_in).format(AhDateFormat),
CarinShop: job.actual_in && moment(job.actual_in).format(AhDateFormat),
(job.scheduled_in && moment(job.scheduled_in).format(AhDateFormat)) ||
"",
CarinShop:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
InsInspDate: null,
StartDate: job.actual_in && moment(job.actual_in).format(AhDateFormat),
StartDate:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
PartsOrder: null,
TeardownHold: null,
SupplementSubmittedDate: null,
@@ -268,22 +274,28 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AssntoPaint: null,
AssntoDetail: null,
PromiseDate:
job.scheduled_completion &&
moment(job.scheduled_completion).format(AhDateFormat),
InsuranceTargetOut: null,
(job.scheduled_completion &&
moment(job.scheduled_completion).format(AhDateFormat)) ||
"",
//InsuranceTargetOut: null,
CarComplete:
job.actual_completion &&
moment(job.actual_completion).format(AhDateFormat),
(job.actual_completion &&
moment(job.actual_completion).format(AhDateFormat)) ||
"",
DeliveryAppointmentDate:
job.scheduled_delivery &&
moment(job.scheduled_delivery).format(AhDateFormat),
(job.scheduled_delivery &&
moment(job.scheduled_delivery).format(AhDateFormat)) ||
"",
DateClosed:
job.date_invoiced && moment(job.date_invoiced).format(AhDateFormat),
(job.date_invoiced &&
moment(job.date_invoiced).format(AhDateFormat)) ||
"",
CustomerPaidInFullDate: null,
InsurancePaidInFullDate: null,
CustPickup:
job.actual_delivery &&
moment(job.actual_delivery).format(AhDateFormat),
(job.actual_delivery &&
moment(job.actual_delivery).format(AhDateFormat)) ||
"",
AccountPostedDate:
job.date_exported && moment(job.date_exported).format(AhDateFormat),
CSIProcessedDate: null,
@@ -291,85 +303,86 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AdditionalFollowUpDate: null,
},
Rates: {
BodyRate: job.rate_lab,
RefinishRate: job.rate_lar,
MechanicalRate: job.rate_lam,
StructuralRate: job.rate_las,
PMRate: job.rate_mapa,
BMRate: job.rate_mash,
BodyRate: job.rate_lab || 0,
RefinishRate: job.rate_lar || 0,
MechanicalRate: job.rate_lam || 0,
StructuralRate: job.rate_las || 0,
PMRate: job.rate_mapa || 0,
BMRate: job.rate_mash || 0,
TaxRate:
job.parts_tax_rates &&
job.parts_tax_rates.PAN &&
job.parts_tax_rates.PAN.prt_tax_rt,
StorageRateperDay: null,
DaysStored: null,
},
EstimateTotals: {
BodyHours: null,
RefinishHours: null,
MechanicalHours: null,
StructuralHours: null,
PartsTotal: null,
PartsOEM: null,
PartsAM: null,
PartsReconditioned: null,
PartsRecycled: null,
PartsOther: null,
SubletTotal: null,
BodyLaborTotal: null,
RefinishLaborTotal: null,
MechanicalLaborTotal: null,
StructuralLaborTotal: null,
MiscellaneousChargeTotal: null,
PMTotal: null,
BMTotal: null,
MiscTotal: null,
TowingTotal: null,
StorageTotal: null,
DetailTotal: null,
SalesTaxTotal: null,
GrossTotal: null,
DeductibleTotal: null,
DepreciationTotal: null,
Discount: null,
CustomerPay: null,
InsurancePay: null,
Deposit: null,
AmountDue: null,
},
SupplementTotals: {
BodyHours: null,
RefinishHours: null,
MechanicalHours: null,
StructuralHours: null,
PartsTotal: null,
PartsOEM: null,
PartsAM: null,
PartsReconditioned: null,
PartsRecycled: null,
PartsOther: null,
SubletTotal: null,
BodyLaborTotal: null,
RefinishLaborTotal: null,
MechanicalLaborTotal: null,
StructuralLaborTotal: null,
MiscellaneousChargeTotal: null,
PMTotal: null,
BMTotal: null,
MiscTotal: null,
TowingTotal: null,
StorageTotal: null,
DetailTotal: null,
SalesTaxTotal: null,
GrossTotal: null,
DeductibleTotal: null,
DepreciationTotal: null,
Discount: null,
CustomerPay: null,
InsurancePay: null,
Deposit: null,
AmountDue: null,
(job.parts_tax_rates &&
job.parts_tax_rates.PAN &&
job.parts_tax_rates.PAN.prt_tax_rt) ||
0,
StorageRateperDay: 0,
DaysStored: 0,
},
// EstimateTotals: {
// BodyHours: null,
// RefinishHours: null,
// MechanicalHours: null,
// StructuralHours: null,
// PartsTotal: null,
// PartsOEM: null,
// PartsAM: null,
// PartsReconditioned: null,
// PartsRecycled: null,
// PartsOther: null,
// SubletTotal: null,
// BodyLaborTotal: null,
// RefinishLaborTotal: null,
// MechanicalLaborTotal: null,
// StructuralLaborTotal: null,
// MiscellaneousChargeTotal: null,
// PMTotal: null,
// BMTotal: null,
// MiscTotal: null,
// TowingTotal: null,
// StorageTotal: null,
// DetailTotal: null,
// SalesTaxTotal: null,
// GrossTotal: null,
// DeductibleTotal: null,
// DepreciationTotal: null,
// Discount: null,
// CustomerPay: null,
// InsurancePay: null,
// Deposit: null,
// AmountDue: null,
// },
// SupplementTotals: {
// BodyHours: null,
// RefinishHours: null,
// MechanicalHours: null,
// StructuralHours: null,
// PartsTotal: null,
// PartsOEM: null,
// PartsAM: null,
// PartsReconditioned: null,
// PartsRecycled: null,
// PartsOther: null,
// SubletTotal: null,
// BodyLaborTotal: null,
// RefinishLaborTotal: null,
// MechanicalLaborTotal: null,
// StructuralLaborTotal: null,
// MiscellaneousChargeTotal: null,
// PMTotal: null,
// BMTotal: null,
// MiscTotal: null,
// TowingTotal: null,
// StorageTotal: null,
// DetailTotal: null,
// SalesTaxTotal: null,
// GrossTotal: null,
// DeductibleTotal: null,
// DepreciationTotal: null,
// Discount: null,
// CustomerPay: null,
// InsurancePay: null,
// Deposit: null,
// AmountDue: null,
// },
RevisedTotals: {
BodyHours: job.job_totals.rates.lab.hours,
BodyRepairHours: job.joblines
@@ -441,8 +454,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
),
StructuralLaborTotalCost:
repairCosts.StructuralLaborTotalCost.toFormat(AHDineroFormat),
MiscellaneousChargeTotal: null,
MiscellaneousChargeTotalCost: null,
MiscellaneousChargeTotal: 0,
MiscellaneousChargeTotalCost: 0,
PMTotal: Dinero(job.job_totals.rates.mapa.total).toFormat(
AHDineroFormat
),
@@ -461,17 +474,17 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AHDineroFormat
),
StorageTotalCost: repairCosts.StorageTotalCost.toFormat(AHDineroFormat),
DetailTotal: null,
DetailTotalCost: null,
DetailTotal: 0,
DetailTotalCost: 0,
SalesTaxTotal: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
.add(Dinero(job.job_totals.totals.federal_tax))
.toFormat(AHDineroFormat),
SalesTaxTotalCost: null,
SalesTaxTotalCost: 0,
GrossTotal: Dinero(job.job_totals.totals.net_repairs).toFormat(
AHDineroFormat
),
DeductibleTotal: job.ded_amt,
DeductibleTotal: job.ded_amt || 0,
DepreciationTotal: Dinero(
job.job_totals.totals.custPayable.dep_taxes
).toFormat(AHDineroFormat),
@@ -496,8 +509,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
InsScreenCommentsLine2: null,
AssignmentCaller: null,
AssignmentDivision: null,
LocationofPrimaryImpact: "12",
LocationofSecondaryImpact: null,
LocationofPrimaryImpact:
(job.area_of_damage && job.area_of_damage.impact1) || 0,
LocationofSecondaryImpact:
(job.area_of_damage && job.area_of_damage.impact2) || 0,
PaintTechID: null,
PaintTechName: null,
ImportType: null,
@@ -517,7 +532,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
RentalDueDate: null,
RentalActRetDate: null,
RentalCompanyID: null,
CSIID: null,
// CSIID: null,
InsGroupCode: null,
},
@@ -669,36 +684,39 @@ const GenerateDetailLines = (line, statuses) => {
const ret = {
BackOrdered: line.status === statuses.default_bo ? "1" : "0",
Cost:
line.billlines[0] &&
(line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed(2),
Critical: null,
Description: line.line_desc,
DiscountMarkup: null,
(line.billlines[0] &&
(line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed(
2
)) ||
0,
//Critical: null,
Description: line.line_desc || "",
DiscountMarkup: line.prt_dsmk_m || "",
InvoiceNumber: line.billlines[0] && line.billlines[0].bill.invoice_number,
IOUPart: null,
LineNumber: line.line_no,
IOUPart: 0,
LineNumber: line.line_no || 0,
MarkUp: null,
OrderedOn: null,
OriginalCost: null,
OriginalInvoiceNumber: null,
PriceEach: line.billlines[0] && line.billlines[0].actual_cost,
PriceEach: (line.billlines[0] && line.billlines[0].actual_cost) || 0,
PartNumber: _.escape(line.oem_partno),
ProfitPercent: null,
PurchaseOrderNumber: null,
Qty: line.part_qty,
Status: line.status,
SupplementNumber: null,
Type: line.part_type,
Vendor: line.billlines[0] && line.billlines[0].bill.vendor.name,
Qty: line.part_qty || 0,
Status: line.status || "",
SupplementNumber: line.line_ind || "",
Type: line.part_type || "",
Vendor: (line.billlines[0] && line.billlines[0].bill.vendor.name) || "",
VendorPaid: null,
VendorPrice: line.billlines[0] && line.billlines[0].actual_price,
VendorPrice: (line.billlines[0] && line.billlines[0].actual_price) || 0,
Deleted: null,
ExpectedOn: null,
ReceivedOn: null,
OrderedBy: null,
ShipVia: null,
VendorContact: null,
EstimateAmount: line.act_price,
EstimateAmount: line.act_price || 0,
};
return ret;
};
@@ -709,9 +727,9 @@ const generateNullDetailLine = () => {
Cost: 0,
Critical: null,
Description: "No Lines on Estimate",
DiscountMarkup: null,
DiscountMarkup: 0,
InvoiceNumber: null,
IOUPart: null,
IOUPart: 0,
LineNumber: 0,
MarkUp: null,
OrderedOn: null,
@@ -722,12 +740,12 @@ const generateNullDetailLine = () => {
ProfitPercent: null,
PurchaseOrderNumber: null,
Qty: 0,
Status: null,
SupplementNumber: null,
Type: null,
Vendor: null,
Status: "",
SupplementNumber: 0,
Type: "",
Vendor: "",
VendorPaid: null,
VendorPrice: null,
VendorPrice: 0,
Deleted: null,
ExpectedOn: null,
ReceivedOn: null,

View File

@@ -74,31 +74,48 @@ exports.updateUser = (req, res) => {
});
};
exports.sendNotification = (req, res) => {
var registrationToken =
"fqIWg8ENDFyrRrMWJ1sItR:APA91bHirdZ05Zo66flMlvala97SMXoiQGwP4oCvMwd-vVrSauD_WoNim3kXHGqyP-bzENjkXwA5icyUAReFbeHn6dIaPcbpcsXuY73-eJAXvZiu1gIsrd1BOsnj3dEMT7Q4F6mTPth1";
var message = {
notification: { title: "The Title", body: "The Body" },
data: {
jobid: "1234",
},
token: registrationToken,
};
exports.sendNotification = async (req, res) => {
setTimeout(() => {
// Send a message to the device corresponding to the provided
// registration token.
admin
.messaging()
.send({
topic: "PRD_PATRICK-messaging",
notification: {
title: `ImEX Online Message - +16049992002`,
body: "Test Noti.",
//imageUrl: "https://thinkimex.com/img/io-fcm.png",
},
data: {
type: "messaging-inbound",
conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297",
text: "Hello. ",
image_path: "",
phone_num: "+16049992002",
},
})
.then((response) => {
// Response is a message ID string.
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});
// Send a message to the device corresponding to the provided
// registration token.
admin
res.sendStatus(200);
}, 500);
};
exports.subscribe = async (req, res) => {
const result = await admin
.messaging()
.send(message)
.then((response) => {
// Response is a message ID string.
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});
.subscribeToTopic(
req.body.fcm_tokens,
`${req.body.imexshopid}-${req.body.type}`
);
res.sendStatus(200);
res.json(result);
};
exports.validateFirebaseIdToken = async (req, res, next) => {

View File

@@ -22,48 +22,74 @@ mutation UNARCHIVE_CONVERSATION($id: uuid!) {
exports.RECEIVE_MESSAGE = `
mutation RECEIVE_MESSAGE($msg: [messages_insert_input!]!) {
insert_messages(objects: $msg) {
insert_messages(objects: $msg) {
returning {
conversation {
id
archived
bodyshop {
associations(where: {active: {_eq: true}}) {
user {
fcmtokens
}
}
bodyshop{
imexshopid
}
created_at
updated_at
unreadcnt
phone_num
}
conversationid
created_at
id
image_path
image
isoutbound
msid
read
text
updated_at
status
userid
}
}
}
`;
exports.INSERT_MESSAGE = `
mutation INSERT_MESSAGE($msg: [messages_insert_input!]!, $conversationid: uuid!) {
update_conversations_by_pk(pk_columns: {id: $conversationid}, _set: {archived: false}) {
id
archived
}
insert_messages(objects: $msg) {
insert_messages(objects: $msg) {
returning {
conversation {
id
archived
bodyshop {
associations(where: {active: {_eq: true}}) {
user {
fcmtokens
}
}
bodyshop{
imexshopid
}
created_at
updated_at
unreadcnt
phone_num
}
conversationid
created_at
id
image_path
image
isoutbound
msid
read
text
updated_at
status
userid
}
}
}
`;
exports.UPDATE_MESSAGE_STATUS = `
@@ -499,7 +525,7 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
}`;
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!) {
jobs(where: {_and: [{converted :{_eq: true}},{updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) {
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}, limit: 50) {
id
ro_number
status
@@ -507,6 +533,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
est_ct_ln
ownr_zip
referral_source
loss_type
v_model_yr
v_model_desc
v_make_desc
@@ -581,6 +608,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
joblines(where: {removed: {_eq: false}}) {
id
line_no
line_ind
status
line_ind
db_price
@@ -650,6 +678,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
}
}
`;
exports.ENTEGRAL_EXPORT = `

View File

@@ -9,7 +9,7 @@ require("dotenv").config({
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const { phone } = require("phone");
const admin = require("../firebase/firebase-handler").admin;
const { admin } = require("../firebase/firebase-handler");
const logger = require("../utils/logger");
exports.receive = async (req, res) => {
//Perform request validation
@@ -78,21 +78,43 @@ exports.receive = async (req, res) => {
});
}
try {
let insertresp;
if (response.bodyshops[0].conversations[0]) {
const r3 = await client.request(queries.INSERT_MESSAGE, {
insertresp = await client.request(queries.INSERT_MESSAGE, {
msg: newMessage,
conversationid:
response.bodyshops[0].conversations[0] &&
response.bodyshops[0].conversations[0].id,
});
} else {
const r2 = await client.request(queries.RECEIVE_MESSAGE, {
insertresp = await client.request(queries.RECEIVE_MESSAGE, {
msg: newMessage,
});
}
const message = insertresp.insert_messages.returning[0];
const data = {
type: "messaging-inbound",
conversationid: message.conversationid || "",
text: message.text || "",
image_path: message.image_path || "",
image: (message.image && message.image.toString()) || "",
messageid: message.id || "",
phone_num: message.conversation.phone_num || "",
};
const fcmresp = await admin.messaging().send({
topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
notification: {
title: `ImEX Online Message - ${data.phone_num}`,
body: message.image_path ? `Image ${message.text}` : message.text,
//imageUrl: "https://thinkimex.com/img/io-fcm.png",
},
data,
});
logger.log("sms-inbound-success", "DEBUG", "api", null, {
newMessage,
fcmresp,
});
res.status(200).send("");
} catch (e2) {

View File

@@ -10,6 +10,7 @@ const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const { phone } = require("phone");
const logger = require("../utils/logger");
const { admin } = require("../firebase/firebase-handler");
exports.status = (req, res) => {
const { SmsSid, SmsStatus } = req.body;
@@ -34,6 +35,23 @@ exports.status = (req, res) => {
res.sendStatus(200);
};
exports.markConversationRead = async (req, res) => {
const { conversationid, imexshopid } = req.body;
admin.messaging().send({
topic: `${imexshopid}-messaging`,
// notification: {
// title: `ImEX Online Message - ${data.phone_num}`,
// body: message.image_path ? `Image ${message.text}` : message.text,
// imageUrl: "https://thinkimex.com/img/logo512.png",
// },
data: {
type: "messaging-mark-conversation-read",
conversationid: conversationid || "",
},
});
res.send(200);
};
// Inbound Sample
// {
// "SmsSid": "SM5205ea340e06437799d9345e7283457c",

View File

@@ -5,13 +5,14 @@ const logger = new graylog2.graylog({
});
function log(message, type, user, record, object) {
console.log(message, {
type,
env: process.env.NODE_ENV || "development",
user,
record,
...object,
});
if (type !== "ioevent")
console.log(message, {
type,
env: process.env.NODE_ENV || "development",
user,
record,
...object,
});
logger.log(message, {
type,
env: process.env.NODE_ENV || "development",