diff --git a/client/package.json b/client/package.json
index 6f31ce130..24b997708 100644
--- a/client/package.json
+++ b/client/package.json
@@ -51,6 +51,7 @@
"react-i18next": "^12.2.0",
"react-icons": "^4.7.1",
"react-image-lightbox": "^5.1.4",
+ "react-intersection-observer": "^9.4.3",
"react-number-format": "^5.1.3",
"react-redux": "^8.0.5",
"react-resizable": "^3.0.4",
diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx
index 816772291..ffc087ae2 100644
--- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx
+++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx
@@ -1,5 +1,5 @@
import { Badge, List, Tag } from "antd";
-import React from "react";
+import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
@@ -7,6 +7,8 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
+import { List as VirtualizedList, AutoSizer } from "react-virtualized";
+
import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -18,59 +20,86 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setSelectedConversation(conversationId)),
});
-export function ChatConversationListComponent({
+function ChatConversationListComponent({
conversationList,
selectedConversation,
setSelectedConversation,
+ subscribeToMoreConversations,
+ loadMoreConversations,
}) {
+ useEffect(
+ () => subscribeToMoreConversations(),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
+ const rowRenderer = ({ index, key, style }) => {
+ const item = conversationList[index];
+
+ return (
+ setSelectedConversation(item.id)}
+ className={`chat-list-item ${
+ item.id === selectedConversation
+ ? "chat-list-selected-conversation"
+ : null
+ }`}
+ style={style}
+ >
+
+ {item.label &&
{item.label}
}
+ {item.job_conversations.length > 0 ? (
+
+ {item.job_conversations.map((j, idx) => (
+
+
+
+ ))}
+
+ ) : (
+
{item.phone_num}
+ )}
+
+
+
+ {item.job_conversations.length > 0
+ ? item.job_conversations.map((j, idx) => (
+
+ {j.job.ro_number}
+
+ ))
+ : null}
+
+
{item.updated_at}
+
+
+
+ );
+ };
+
return (
-
(
- setSelectedConversation(item.id)}
- className={`chat-list-item ${
- item.id === selectedConversation
- ? "chat-list-selected-conversation"
- : null
- }`}
- >
-
- {item.label &&
{item.label}
}
- {item.job_conversations.length > 0 ? (
-
- {item.job_conversations.map((j, idx) => (
-
-
-
- ))}
-
- ) : (
-
{item.phone_num}
- )}
-
-
-
- {item.job_conversations.length > 0
- ? item.job_conversations.map((j, idx) => (
-
- {j.job.ro_number}
-
- ))
- : null}
-
-
{item.updated_at}
-
-
-
+
+ {({ height, width }) => (
+ {
+ if (scrollTop + clientHeight === scrollHeight) {
+ loadMoreConversations();
+ }
+ }}
+ />
)}
- />
+
);
}
+
export default connect(
mapStateToProps,
mapDispatchToProps
diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss
index bfc83d947..20cf8f4ef 100644
--- a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss
+++ b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss
@@ -3,8 +3,9 @@
}
.chat-list-container {
flex: 1;
- overflow: auto;
+ overflow: hidden;
height: 100%;
+ border: 1px solid gainsboro;
}
.chat-list-item {
@@ -21,4 +22,6 @@
.ro-number-tag {
align-self: baseline;
}
+ padding: 12px 24px;
+ border-bottom: 1px solid gainsboro;
}
diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx
index 9a4dc33eb..1c80d7c4c 100644
--- a/client/src/components/chat-popup/chat-popup.component.jsx
+++ b/client/src/components/chat-popup/chat-popup.component.jsx
@@ -4,15 +4,16 @@ import {
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
-import { useQuery } from "@apollo/client";
+import { useLazyQuery, useSubscription } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
-import React, { useEffect, useState } from "react";
+import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
CONVERSATION_LIST_QUERY,
- UNREAD_CONVERSATION_COUNT,
+ CONVERSATION_LIST_SUBSCRIPTION,
+ UNREAD_CONVERSATION_COUNT_SUBSCRIPTION,
} from "../../graphql/conversations.queries";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import {
@@ -41,17 +42,17 @@ export function ChatPopupComponent({
const { t } = useTranslation();
const [pollInterval, setpollInterval] = useState(0);
- const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
- fetchPolicy: "network-only",
- nextFetchPolicy: "network-only",
- ...(pollInterval > 0 ? { pollInterval } : {}),
- });
+ const { data: unreadData } = useSubscription(
+ UNREAD_CONVERSATION_COUNT_SUBSCRIPTION
+ );
- const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
+ const [
+ getConversations,
+ { loading, data, called, refetch, fetchMore, subscribeToMore },
+ ] = useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !chatVisible,
- ...(pollInterval > 0 ? { pollInterval } : {}),
});
const fcmToken = sessionStorage.getItem("fcmtoken");
@@ -65,15 +66,22 @@ export function ChatPopupComponent({
}, [fcmToken]);
useEffect(() => {
- if (called && chatVisible) refetch();
- }, [chatVisible, called, refetch]);
+ if (called && chatVisible)
+ getConversations({
+ variables: {
+ offset: 0,
+ },
+ });
+ }, [chatVisible, called, getConversations]);
- // const unreadCount = data
- // ? data.conversations.reduce(
- // (acc, val) => val.messages_aggregate.aggregate.count + acc,
- // 0
- // )
- // : 0;
+ const loadMoreConversations = useCallback(() => {
+ if (data)
+ fetchMore({
+ variables: {
+ offset: data.conversations.length,
+ },
+ });
+ }, [data, fetchMore]);
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
@@ -110,6 +118,44 @@ export function ChatPopupComponent({
) : (
+ subscribeToMore({
+ document: CONVERSATION_LIST_SUBSCRIPTION,
+ variables: { offset: 0 },
+ updateQuery: (prev, { subscriptionData }) => {
+ if (
+ !subscriptionData.data ||
+ subscriptionData.data.conversations.length === 0
+ )
+ return prev;
+
+ let conversations = [...prev.conversations];
+ const newConversations =
+ subscriptionData.data.conversations;
+
+ for (const conversation of newConversations) {
+ const index = conversations.findIndex(
+ (prevConversation) =>
+ prevConversation.id === conversation.id
+ );
+
+ if (index !== -1) {
+ conversations.splice(index, 1);
+ conversations.unshift(conversation);
+
+ continue;
+ }
+
+ conversations.unshift(conversation);
+ }
+
+ return Object.assign({}, prev, {
+ conversations: conversations,
+ });
+ },
+ })
+ }
/>
)}
diff --git a/client/src/components/production-list-columns/production-list-columns.date.component.jsx b/client/src/components/production-list-columns/production-list-columns.date.component.jsx
index 4944d628b..06a5c04d3 100644
--- a/client/src/components/production-list-columns/production-list-columns.date.component.jsx
+++ b/client/src/components/production-list-columns/production-list-columns.date.component.jsx
@@ -25,6 +25,7 @@ export default function ProductionListDate({
// }
//e.stopPropagation();
+
updateAlert({
variables: {
jobId: record.id,
@@ -32,6 +33,11 @@ export default function ProductionListDate({
[field]: date,
},
},
+ optimisticResponse: {
+ update_jobs: {
+ [field]: date,
+ },
+ },
}).then(() => {
if (record.refetch) record.refetch();
if (!time) {
@@ -49,9 +55,11 @@ export default function ProductionListDate({
(moment().add(1, "day").isSame(moment(record[field]), "day") &&
"production-completion-soon"));
}
+
return (
setVisible(v)}
visible={visible}
style={{
height: "19px",
diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
index a72322851..6df9f2b9f 100644
--- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
+++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
@@ -96,8 +96,9 @@ export function TimeTicketModalComponent({
]}
>
)}
diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js
index ef73f546b..53de38b62 100644
--- a/client/src/graphql/conversations.queries.js
+++ b/client/src/graphql/conversations.queries.js
@@ -1,35 +1,36 @@
import { gql } from "@apollo/client";
-// 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_SUBSCRIPTION = gql`
+ subscription CONVERSATION_LIST_SUBSCRIPTION($offset: Int!) {
+ conversations(
+ order_by: { updated_at: desc }
+ limit: 1
+ offset: $offset
+ 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 UNREAD_CONVERSATION_COUNT = gql`
query UNREAD_CONVERSATION_COUNT {
@@ -43,11 +44,24 @@ export const UNREAD_CONVERSATION_COUNT = gql`
}
`;
+export const UNREAD_CONVERSATION_COUNT_SUBSCRIPTION = gql`
+ subscription UNREAD_CONVERSATION_COUNT_SUBSCRIPTION {
+ messages_aggregate(
+ where: { read: { _eq: false }, isoutbound: { _eq: false } }
+ ) {
+ aggregate {
+ count
+ }
+ }
+ }
+`;
+
export const CONVERSATION_LIST_QUERY = gql`
- query CONVERSATION_LIST_QUERY {
+ query CONVERSATION_LIST_QUERY($offset: Int!) {
conversations(
order_by: { updated_at: desc }
- limit: 50
+ limit: 20
+ offset: $offset
where: { archived: { _eq: false } }
) {
phone_num
diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js
index 103e21b16..9619d5173 100644
--- a/client/src/graphql/jobs.queries.js
+++ b/client/src/graphql/jobs.queries.js
@@ -1075,6 +1075,9 @@ export const UPDATE_JOB = gql`
lbr_adjustments
suspended
queued_for_parts
+ scheduled_completion
+ actual_in
+ date_repairstarted
}
}
}
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index e35eae607..c32b21d7b 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -2316,7 +2316,7 @@
"folder_label_multiple": "Folder Label - Multi",
"glass_express_checklist": "Glass Express Checklist",
"guarantee": "Repair Guarantee",
- "individual_job_note": "Job Note RO # {{ro_number}}",
+ "individual_job_note": "RO Job Note",
"invoice_customer_payable": "Invoice (Customer Payable)",
"invoice_total_payable": "Invoice (Total Payable)",
"iou_form": "IOU Form",
@@ -2332,7 +2332,8 @@
"mechanical_authorization": "Mechanical Authorization",
"mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth",
- "mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
+ "mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)",
+ "mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet",
"paint_grid": "Paint Grid",
"parts_invoice_label_single": "Parts Label Single",
"parts_label_multiple": "Parts Label - Multi",
@@ -2394,6 +2395,7 @@
},
"subjects": {
"jobs": {
+ "individual_job_note": "Job Note RO: {{ro_number}}",
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}",
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
}
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index cf0736eb3..0ead918b2 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -2333,6 +2333,7 @@
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
+ "mpi_final_repair_acct_sheet": "",
"paint_grid": "",
"parts_invoice_label_single": "",
"parts_label_multiple": "",
@@ -2394,6 +2395,7 @@
},
"subjects": {
"jobs": {
+ "individual_job_note": "",
"parts_order": "",
"sublet_order": ""
}
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 542817c76..040080a88 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -2333,6 +2333,7 @@
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
+ "mpi_final_repair_acct_sheet": "",
"paint_grid": "",
"parts_invoice_label_single": "",
"parts_label_multiple": "",
@@ -2394,6 +2395,7 @@
},
"subjects": {
"jobs": {
+ "individual_job_note": "",
"parts_order": "",
"sublet_order": ""
}
diff --git a/client/src/utils/GraphQLClient.js b/client/src/utils/GraphQLClient.js
index 1861b8b7b..b6c2c0091 100644
--- a/client/src/utils/GraphQLClient.js
+++ b/client/src/utils/GraphQLClient.js
@@ -3,7 +3,10 @@ import { setContext } from "@apollo/client/link/context";
import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http";
import { RetryLink } from "@apollo/client/link/retry";
import { WebSocketLink } from "@apollo/client/link/ws";
-import { getMainDefinition } from "@apollo/client/utilities";
+import {
+ getMainDefinition,
+ offsetLimitPagination,
+} from "@apollo/client/utilities";
//import { split } from "apollo-link";
import apolloLogger from "apollo-link-logger";
//import axios from "axios";
@@ -140,7 +143,15 @@ middlewares.push(
)
);
-const cache = new InMemoryCache({});
+const cache = new InMemoryCache({
+ typePolicies: {
+ Query: {
+ fields: {
+ conversations: offsetLimitPagination(),
+ },
+ },
+ },
+});
const client = new ApolloClient({
link: ApolloLink.from(middlewares),
diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js
index 715d511fe..78f2f12f2 100644
--- a/client/src/utils/TemplateConstants.js
+++ b/client/src/utils/TemplateConstants.js
@@ -421,6 +421,17 @@ export const TemplateList = (type, context) => {
CA_MB: true,
},
},
+ mpi_final_repair_acct_sheet: {
+ title: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"),
+ description: "",
+ key: "mpi_final_repair_acct_sheet",
+ subject: i18n.t("printcenter.jobs.mpi_final_repair_acct_sheet"),
+ disabled: false,
+ group: "post",
+ regions: {
+ CA_MB: true,
+ },
+ },
mpi_eglass_auth: {
title: i18n.t("printcenter.jobs.mpi_eglass_auth"),
description: "",
@@ -542,7 +553,7 @@ export const TemplateList = (type, context) => {
title: i18n.t("printcenter.jobs.individual_job_note"),
description: "",
key: "individual_job_note",
- subject: i18n.t("printcenter.jobs.individual_job_note", {
+ subject: i18n.t("printcenter.subjects.jobs.individual_job_note", {
ro_number: (context && context.ro_number) || "",
}),
disabled: false,
diff --git a/client/yarn.lock b/client/yarn.lock
index 04749e444..d972ced66 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -11168,6 +11168,11 @@ react-image-lightbox@^5.1.4:
prop-types "^15.7.2"
react-modal "^3.11.1"
+react-intersection-observer@^9.4.3:
+ version "9.4.3"
+ resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz#ec84ce0c25cad548075130ea045ac5c7adf908f3"
+ integrity sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==
+
react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
diff --git a/server/cdk/cdk-job-export.js b/server/cdk/cdk-job-export.js
index 3987d21bc..37265a0a7 100644
--- a/server/cdk/cdk-job-export.js
+++ b/server/cdk/cdk-job-export.js
@@ -224,7 +224,7 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
} finally {
//Ensure we always insert logEvents
//GQL to insert logevents.
-
+
CdkBase.createLogEvent(
socket,
"DEBUG",
@@ -432,7 +432,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
async function QueryDmsCustomerByName(socket, JobData) {
const ownerName = (
- JobData.ownr_co_nm && JobData.ownr_co_nm !== ""
+ JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
? JobData.ownr_co_nm
: `${JobData.ownr_ln},${JobData.ownr_fn}`
).replace(replaceSpecialRegex, "");
@@ -642,7 +642,8 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
.toUpperCase(),
middleName: null,
nameType:
- socket.JobData.ownr_co_nm && socket.JobData.ownr_co_nm
+ socket.JobData.ownr_co_nm &&
+ String(socket.JobData.ownr_co_nm).trim() !== ""
? "Business"
: "Person",
suffix: null,
@@ -728,7 +729,9 @@ async function InsertDmsVehicle(socket) {
deliveryDate: moment()
// .tz(socket.JobData.bodyshop.timezone)
.format("YYYYMMDD"),
- licensePlateNo: socket.JobData.plate_no,
+ licensePlateNo: String(socket.JobData.plate_no)
+ .replace(/([^\w]|_)/g, "")
+ .toUpperCase(),
make: socket.txEnvelope.dms_make,
modelAbrev: socket.txEnvelope.dms_model,
modelYear: socket.JobData.v_model_yr,
diff --git a/yarn.lock b/yarn.lock
index 9659256b2..d859830cb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4312,14 +4312,9 @@ tslib@^1.11.1:
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.1, tslib@^2.1.0:
-
-tslib@^2.3.1, tslib@^2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
- integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
- integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
+ integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
tslib@^2.3.1, tslib@^2.5.0:
version "2.5.0"