From 4bc8ff26d2fd92bb516553809335197215385bd3 Mon Sep 17 00:00:00 2001 From: swtmply Date: Sat, 13 May 2023 00:32:51 +0800 Subject: [PATCH 01/18] IO-2208 Added pagination and subscription to chat --- client/package.json | 1 + .../chat-conversation-list.component.jsx | 127 ++++++++++-------- .../chat-popup/chat-popup.component.jsx | 73 +++++++--- client/src/graphql/conversations.queries.js | 78 ++++++----- client/src/utils/GraphQLClient.js | 15 ++- client/yarn.lock | 5 + 6 files changed, 189 insertions(+), 110 deletions(-) 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..a9d48871e 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"; @@ -18,60 +18,73 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setSelectedConversation(conversationId)), }); -export function ChatConversationListComponent({ - conversationList, - selectedConversation, - setSelectedConversation, -}) { - 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} +const ChatConversationListComponent = React.forwardRef( + function ChatConversationListComponent( + { + conversationList, + selectedConversation, + setSelectedConversation, + subscribeToMoreConversations, + }, + ref + ) { + useEffect( + () => subscribeToMoreConversations(), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + 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.updated_at} -
- - - )} - /> -
- ); -} -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatConversationListComponent); +
+
+ {item.job_conversations.length > 0 + ? item.job_conversations.map((j, idx) => ( + + {j.job.ro_number} + + )) + : null} +
+ {item.updated_at} +
+ + + )} + footer={} + /> +
+ ); + } +); + +export default connect(mapStateToProps, mapDispatchToProps, null, { + forwardRef: true, +})(ChatConversationListComponent); diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 9a4dc33eb..a3fda4843 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -4,7 +4,7 @@ import { ShrinkOutlined, SyncOutlined, } from "@ant-design/icons"; -import { useQuery } from "@apollo/client"; +import { useQuery, useSubscription } 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"; @@ -12,7 +12,8 @@ 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 { @@ -24,6 +25,7 @@ import ChatConversationContainer from "../chat-conversation/chat-conversation.co import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import "./chat-popup.styles.scss"; +import { useInView } from "react-intersection-observer"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation, @@ -40,19 +42,29 @@ export function ChatPopupComponent({ }) { const { t } = useTranslation(); const [pollInterval, setpollInterval] = useState(0); + const { ref, inView } = useInView({}); - 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, { - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - skip: !chatVisible, - ...(pollInterval > 0 ? { pollInterval } : {}), - }); + // const { data: unreadData, refetch: unreadRefetch } = useQuery( + // UNREAD_CONVERSATION_COUNT, + // { + // fetchPolicy: "network-only", + // nextFetchPolicy: "network-only", + // } + // ); + + const { loading, data, called, refetch, fetchMore, subscribeToMore } = + useQuery(CONVERSATION_LIST_QUERY, { + variables: { + offset: 0, + }, + fetchPolicy: "cache-and-network", + nextFetchPolicy: "cache-first", + skip: !chatVisible, + }); const fcmToken = sessionStorage.getItem("fcmtoken"); @@ -68,12 +80,15 @@ export function ChatPopupComponent({ if (called && chatVisible) refetch(); }, [chatVisible, called, refetch]); - // const unreadCount = data - // ? data.conversations.reduce( - // (acc, val) => val.messages_aggregate.aggregate.count + acc, - // 0 - // ) - // : 0; + useEffect(() => { + if (inView && data && data.conversations) { + fetchMore({ + variables: { + offset: data.conversations.length, + }, + }); + } + }, [inView, data, fetchMore]); const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; @@ -110,6 +125,26 @@ export function ChatPopupComponent({ ) : ( { + subscribeToMore({ + document: CONVERSATION_LIST_SUBSCRIPTION, + variables: { offset: 0 }, + updateQuery: (prev, { subscriptionData }) => { + if ( + !subscriptionData.data || + subscriptionData.data.conversations.length === 0 + ) + return prev; + const newConversations = + subscriptionData.data.conversations; + + return Object.assign({}, prev, { + conversations: [...newConversations], + }); + }, + }); + }} + ref={ref} /> )} 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/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/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" From d66fdfb2e04f658018d0245ed95ed6978ce98d57 Mon Sep 17 00:00:00 2001 From: swtmply Date: Thu, 18 May 2023 23:04:10 +0800 Subject: [PATCH 02/18] IO-2208 added virtualization to conversation list --- .../chat-conversation-list.component.jsx | 144 ++++++++++-------- .../chat-conversation-list.styles.scss | 5 +- .../chat-popup/chat-popup.component.jsx | 109 +++++++++---- 3 files changed, 167 insertions(+), 91 deletions(-) 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 a9d48871e..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 @@ -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,73 +20,87 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setSelectedConversation(conversationId)), }); -const ChatConversationListComponent = React.forwardRef( - function ChatConversationListComponent( - { - conversationList, - selectedConversation, - setSelectedConversation, - subscribeToMoreConversations, - }, - ref - ) { - useEffect( - () => subscribeToMoreConversations(), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); +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 - }`} - > -
- {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} + 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.updated_at} -
- - + ))} +
+ ) : ( + {item.phone_num} )} - footer={} - /> -
+
+
+
+ {item.job_conversations.length > 0 + ? item.job_conversations.map((j, idx) => ( + + {j.job.ro_number} + + )) + : null} +
+ {item.updated_at} +
+ +
); - } -); + }; -export default connect(mapStateToProps, mapDispatchToProps, null, { - forwardRef: true, -})(ChatConversationListComponent); + return ( +
+ + {({ height, width }) => ( + { + if (scrollTop + clientHeight === scrollHeight) { + loadMoreConversations(); + } + }} + /> + )} + +
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ChatConversationListComponent); 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 a3fda4843..5ac4baec1 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -4,9 +4,9 @@ import { ShrinkOutlined, SyncOutlined, } from "@ant-design/icons"; -import { useQuery, useSubscription } from "@apollo/client"; +import { useLazyQuery, useQuery, 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"; @@ -25,7 +25,6 @@ import ChatConversationContainer from "../chat-conversation/chat-conversation.co import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import "./chat-popup.styles.scss"; -import { useInView } from "react-intersection-observer"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation, @@ -42,7 +41,6 @@ export function ChatPopupComponent({ }) { const { t } = useTranslation(); const [pollInterval, setpollInterval] = useState(0); - const { ref, inView } = useInView({}); const { data: unreadData } = useSubscription( UNREAD_CONVERSATION_COUNT_SUBSCRIPTION @@ -56,15 +54,14 @@ export function ChatPopupComponent({ // } // ); - const { loading, data, called, refetch, fetchMore, subscribeToMore } = - useQuery(CONVERSATION_LIST_QUERY, { - variables: { - offset: 0, - }, - fetchPolicy: "cache-and-network", - nextFetchPolicy: "cache-first", - skip: !chatVisible, - }); + const [ + getConversations, + { loading, data, called, fetchMore, subscribeToMore }, + ] = useLazyQuery(CONVERSATION_LIST_QUERY, { + fetchPolicy: "cache-and-network", + nextFetchPolicy: "cache-first", + skip: !chatVisible, + }); const fcmToken = sessionStorage.getItem("fcmtoken"); @@ -77,18 +74,60 @@ export function ChatPopupComponent({ }, [fcmToken]); useEffect(() => { - if (called && chatVisible) refetch(); - }, [chatVisible, called, refetch]); + if (called && chatVisible) + getConversations({ + variables: { + offset: 0, + }, + }); + }, [chatVisible, called, getConversations]); - useEffect(() => { - if (inView && data && data.conversations) { + const loadMoreConversations = useCallback(() => { + if (data) fetchMore({ variables: { offset: data.conversations.length, }, }); - } - }, [inView, data, fetchMore]); + }, [data, fetchMore]); + + const subscribeToMoreConversations = useCallback( + () => + subscribeToMore({ + document: CONVERSATION_LIST_SUBSCRIPTION, + variables: { offset: 0 }, + updateQuery: (prev, { subscriptionData }) => { + console.log("Hello"); + 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; + } + + conversation.unshift(conversation); + } + + return Object.assign({}, prev, { + conversations: conversations, + }); + }, + }), + [subscribeToMore] + ); const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; @@ -105,10 +144,10 @@ export function ChatPopupComponent({ - refetch()} - /> + /> */} {pollInterval > 0 && ( {t("messaging.labels.nopush")} )} @@ -125,7 +164,8 @@ export function ChatPopupComponent({ ) : ( { + loadMoreConversations={loadMoreConversations} + subscribeToMoreConversations={() => subscribeToMore({ document: CONVERSATION_LIST_SUBSCRIPTION, variables: { offset: 0 }, @@ -135,16 +175,33 @@ export function ChatPopupComponent({ 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: [...newConversations], + conversations: conversations, }); }, - }); - }} - ref={ref} + }) + } /> )} From c5c47e9bfce96d488be47436a619839945f74101 Mon Sep 17 00:00:00 2001 From: swtmply Date: Fri, 19 May 2023 00:14:28 +0800 Subject: [PATCH 03/18] IO-2208 remove unused variables and imports --- .../chat-popup/chat-popup.component.jsx | 54 ++----------------- 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 5ac4baec1..899fd3058 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -4,7 +4,7 @@ import { ShrinkOutlined, SyncOutlined, } from "@ant-design/icons"; -import { useLazyQuery, useQuery, useSubscription } from "@apollo/client"; +import { useLazyQuery, useSubscription } from "@apollo/client"; import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd"; import React, { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -46,17 +46,9 @@ export function ChatPopupComponent({ UNREAD_CONVERSATION_COUNT_SUBSCRIPTION ); - // const { data: unreadData, refetch: unreadRefetch } = useQuery( - // UNREAD_CONVERSATION_COUNT, - // { - // fetchPolicy: "network-only", - // nextFetchPolicy: "network-only", - // } - // ); - const [ getConversations, - { loading, data, called, fetchMore, subscribeToMore }, + { loading, data, called, refetch, fetchMore, subscribeToMore }, ] = useLazyQuery(CONVERSATION_LIST_QUERY, { fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", @@ -91,44 +83,6 @@ export function ChatPopupComponent({ }); }, [data, fetchMore]); - const subscribeToMoreConversations = useCallback( - () => - subscribeToMore({ - document: CONVERSATION_LIST_SUBSCRIPTION, - variables: { offset: 0 }, - updateQuery: (prev, { subscriptionData }) => { - console.log("Hello"); - 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; - } - - conversation.unshift(conversation); - } - - return Object.assign({}, prev, { - conversations: conversations, - }); - }, - }), - [subscribeToMore] - ); - const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0; return ( @@ -144,10 +98,10 @@ export function ChatPopupComponent({ - {/* refetch()} - /> */} + /> {pollInterval > 0 && ( {t("messaging.labels.nopush")} )} From 5e96ccdd995a37863eb5b5da289666d782c1b317 Mon Sep 17 00:00:00 2001 From: swtmply Date: Wed, 24 May 2023 03:17:41 +0800 Subject: [PATCH 04/18] IO-2297 added optimistic response and click outside function --- .../production-list-columns.date.component.jsx | 10 +++++++++- client/src/graphql/jobs.queries.js | 3 +++ yarn.lock | 11 +++-------- 3 files changed, 15 insertions(+), 9 deletions(-) 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/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/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" From 44f2287b077842f64363dbb0337f54d4c879f2ff Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 23 May 2023 13:38:55 -0700 Subject: [PATCH 05/18] IO-2295 Individual Note Print File Name contained {{ro_number}} --- client/src/translations/en_us/common.json | 3 ++- client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + client/src/utils/TemplateConstants.js | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e35eae607..66db6d693 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", @@ -2394,6 +2394,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..62c621c06 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2394,6 +2394,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..a1c5bb380 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2394,6 +2394,7 @@ }, "subjects": { "jobs": { + "individual_job_note": "", "parts_order": "", "sublet_order": "" } diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 715d511fe..853f57036 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -542,7 +542,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, From ed0575436887670be6e43eefa3b0cd48d1d622a9 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 23 May 2023 17:56:48 -0700 Subject: [PATCH 06/18] IO-2292 MPI Signature Sheets --- client/src/translations/en_us/common.json | 3 ++- client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + client/src/utils/TemplateConstants.js | 11 +++++++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e35eae607..5eb1c2f57 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -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", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index cf0736eb3..ce8dc7313 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": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 542817c76..e1c62295f 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": "", diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index 715d511fe..656c5fb96 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: "", From 8839fc0293cefa7af73ce639765c5b00e98c4abd Mon Sep 17 00:00:00 2001 From: swtmply Date: Wed, 24 May 2023 23:07:49 +0800 Subject: [PATCH 07/18] IO-2208 replaced cache first to network only --- client/src/components/chat-popup/chat-popup.component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx index 899fd3058..1c80d7c4c 100644 --- a/client/src/components/chat-popup/chat-popup.component.jsx +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -50,8 +50,8 @@ export function ChatPopupComponent({ getConversations, { loading, data, called, refetch, fetchMore, subscribeToMore }, ] = useLazyQuery(CONVERSATION_LIST_QUERY, { - fetchPolicy: "cache-and-network", - nextFetchPolicy: "cache-first", + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", skip: !chatVisible, }); From 209245187f713c7e9703deb146d04858edeea1f9 Mon Sep 17 00:00:00 2001 From: swtmply Date: Thu, 25 May 2023 01:28:22 +0800 Subject: [PATCH 08/18] IO-2298 added export buttons on payments --- .../job-payments/job-payments.component.jsx | 2 +- .../payment-modal/payment-modal.container.jsx | 25 +++++++++- .../payment-reexport-button.component.jsx | 49 +++++++++++++++++++ .../payment-list-paginated.component.jsx | 2 +- client/src/translations/en_us/common.json | 5 +- 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 client/src/components/payment-reexport-button/payment-reexport-button.component.jsx diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx index a67ed047b..f13d57bbe 100644 --- a/client/src/components/job-payments/job-payments.component.jsx +++ b/client/src/components/job-payments/job-payments.component.jsx @@ -99,7 +99,7 @@ export function JobPayments({ render: (text, record) => ( + ); +}; + +export default PaymentReexportButton; diff --git a/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx b/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx index d34b56f9c..8337c89f8 100644 --- a/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx +++ b/client/src/components/payments-list-paginated/payment-list-paginated.component.jsx @@ -156,7 +156,7 @@ export function PaymentsListPaginated({ render: (text, record) => (
} id="refund"> {/* {Qb_Multi_Ar.treatment === "on" && ( - Multiple Payers Item
}> + Multiple Payers Item} + id="accountitem" + > {fields.map((field, index) => ( - + + {(fields, { add, remove, move }) => { diff --git a/client/src/components/shop-info/shop-info.scheduling.component.jsx b/client/src/components/shop-info/shop-info.scheduling.component.jsx index 56bf72926..c4160a367 100644 --- a/client/src/components/shop-info/shop-info.scheduling.component.jsx +++ b/client/src/components/shop-info/shop-info.scheduling.component.jsx @@ -92,7 +92,7 @@ export default function ShopInfoSchedulingComponent({ form }) { {t("bodyshop.labels.workingdays")} - + - + {(fields, { add, remove, move }) => { return ( @@ -208,7 +208,7 @@ export default function ShopInfoSchedulingComponent({ form }) { }} - + {(fields, { add, remove, move }) => { return ( diff --git a/client/src/pages/shop/shop.page.component.jsx b/client/src/pages/shop/shop.page.component.jsx index 7c9e4d5b2..47107be43 100644 --- a/client/src/pages/shop/shop.page.component.jsx +++ b/client/src/pages/shop/shop.page.component.jsx @@ -1,5 +1,7 @@ import { Tabs } from "antd"; import React, { useEffect } from "react"; +import { useHistory, useLocation } from "react-router-dom"; +import queryString from "query-string"; import { useTranslation } from "react-i18next"; import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container"; import ShopInfoContainer from "../../components/shop-info/shop-info.container"; @@ -24,6 +26,9 @@ const mapDispatchToProps = (dispatch) => ({ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { const { t } = useTranslation(); + const history = useHistory(); + const search = queryString.parse(useLocation().search); + useEffect(() => { document.title = t("titles.shop"); setSelectedHeader("shop"); @@ -37,7 +42,10 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { return ( - + history.push({ search: `?tab=${key}` })} + > From 27e0f497bb87bf6e051dc8fd7d77370b94e5dac5 Mon Sep 17 00:00:00 2001 From: swtmply Date: Fri, 26 May 2023 20:12:58 +0800 Subject: [PATCH 15/18] IO-2208 added more conversations on initial load --- client/src/graphql/conversations.queries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index 53de38b62..f9feb3f2f 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -60,7 +60,7 @@ export const CONVERSATION_LIST_QUERY = gql` query CONVERSATION_LIST_QUERY($offset: Int!) { conversations( order_by: { updated_at: desc } - limit: 20 + limit: 50 offset: $offset where: { archived: { _eq: false } } ) { From ddb919e2cc3081b18f23d39dbaf79c5cfaa9712d Mon Sep 17 00:00:00 2001 From: swtmply Date: Fri, 26 May 2023 22:56:36 +0800 Subject: [PATCH 16/18] IO-2264 removed filter function for estimators --- .../schedule-calendar.component.jsx | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/client/src/components/schedule-calendar/schedule-calendar.component.jsx b/client/src/components/schedule-calendar/schedule-calendar.component.jsx index 144ef5392..743e0d83a 100644 --- a/client/src/components/schedule-calendar/schedule-calendar.component.jsx +++ b/client/src/components/schedule-calendar/schedule-calendar.component.jsx @@ -53,29 +53,29 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) { ); const filteredData = useMemo(() => { - return data - .filter((d) => { - if (d.__typename === "appointments") { - if (estimatorsFilter.length === 0) return true; + return data.filter((d) => { + const estFilter = + d.__typename === "appointments" + ? estimatorsFilter.length === 0 + ? true + : !!estimatorsFilter.find( + (e) => e === `${d.job.est_ct_fn} ${d.job.est_ct_ln}` + ) + : true; - return !!estimatorsFilter.find( - (e) => e !== `${d.job.est_ct_fn} ${d.job.est_ct_ln}` - ); - } - return true; - }) - .filter( - (d) => - (d.block || - (filter.intake && d.isintake) || - (filter.manual && !d.isintake && d.block === false) || - (d.__typename === "employee_vacation" && - filter.employeevacation && - !!d.employee)) && - (filter.ins_co_nm && filter.ins_co_nm.length > 0 - ? filter.ins_co_nm.includes(d.job?.ins_co_nm) - : true) + return ( + (d.block || + (filter.intake && d.isintake) || + (filter.manual && !d.isintake && d.block === false) || + (d.__typename === "employee_vacation" && + filter.employeevacation && + !!d.employee)) && + (filter.ins_co_nm && filter.ins_co_nm.length > 0 + ? filter.ins_co_nm.includes(d.job?.ins_co_nm) + : true) && + estFilter ); + }); }, [data, filter, estimatorsFilter]); return ( From c15e69f07960e6a0206f31955389552f3f919073 Mon Sep 17 00:00:00 2001 From: swtmply Date: Sat, 27 May 2023 00:17:44 +0800 Subject: [PATCH 17/18] IO-2298 added mark for export button on payment --- .../payment-mark-export-button-component.jsx | 100 ++++++++++++++++++ .../payment-modal/payment-modal.container.jsx | 7 +- .../payment-reexport-button.component.jsx | 4 +- client/src/translations/en_us/common.json | 4 +- 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx diff --git a/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx b/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx new file mode 100644 index 000000000..a5e77d1d3 --- /dev/null +++ b/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx @@ -0,0 +1,100 @@ +import React from "react"; +import { Button, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { useTranslation } from "react-i18next"; +import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { connect } from "react-redux"; +import { UPDATE_PAYMENT } from "../../graphql/payments.queries"; +import { selectCurrentUser } from "../../redux/user/user.selectors"; +import { createStructuredSelector } from "reselect"; + +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser, +}); + +const mapDispatchToProps = (dispatch) => ({ + setPaymentContext: (context) => + dispatch(setModalContext({ context: context, modal: "payment" })), +}); + +const PaymentMarkForExportButton = ({ + bodyshop, + payment, + refetch, + setPaymentContext, + currentUser, +}) => { + const { t } = useTranslation(); + const [insertExportLog, { loading: exportLogLoading }] = + useMutation(INSERT_EXPORT_LOG); + const [updatePayment, { loading: updatePaymentLoading }] = + useMutation(UPDATE_PAYMENT); + + const handleClick = async () => { + const today = new Date(); + + await insertExportLog({ + variables: { + logs: [ + { + bodyshopid: bodyshop.id, + paymentid: payment.id, + successful: true, + useremail: currentUser.email, + }, + ], + }, + }); + + const paymentUpdateResponse = await updatePayment({ + variables: { + paymentId: payment.id, + payment: { + exportedat: today, + }, + }, + }); + + if (!!!paymentUpdateResponse.errors) { + notification.open({ + type: "success", + key: "paymentsuccessmarkforexport", + message: t("payments.successes.markexported"), + }); + + if (refetch) refetch(); + + setPaymentContext({ + actions: { + refetch, + }, + context: { + ...payment, + exportedat: today, + }, + }); + } else { + notification["error"]({ + message: t("payments.errors.exporting", { + error: JSON.stringify(paymentUpdateResponse.error), + }), + }); + } + }; + + return ( + + ); +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(PaymentMarkForExportButton); diff --git a/client/src/components/payment-modal/payment-modal.container.jsx b/client/src/components/payment-modal/payment-modal.container.jsx index 28c47c30c..c72bd224a 100644 --- a/client/src/components/payment-modal/payment-modal.container.jsx +++ b/client/src/components/payment-modal/payment-modal.container.jsx @@ -19,8 +19,8 @@ import { import { GenerateDocument } from "../../utils/RenderTemplate"; import { TemplateList } from "../../utils/TemplateConstants"; import PaymentForm from "../payment-form/payment-form.component"; -import PaymentExportButton from "../payment-export-button/payment-export-button.component"; import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component"; +import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component"; const mapStateToProps = createStructuredSelector({ paymentModal: selectPayment, @@ -180,10 +180,9 @@ function PaymentModalContainer({ > - diff --git a/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx b/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx index c24d2eab1..a3adb5422 100644 --- a/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx +++ b/client/src/components/payment-reexport-button/payment-reexport-button.component.jsx @@ -29,7 +29,7 @@ const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => { notification.open({ type: "success", key: "paymentsuccessexport", - message: t("payments.successes.reexported"), + message: t("payments.successes.markreexported"), }); if (refetch) refetch(); @@ -58,7 +58,7 @@ const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => { loading={loading} disabled={!payment.exportedat} > - {t("bills.labels.markforreexport")} + {t("payments.labels.markforreexport")} ); }; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index d1c6c27ce..2bfecaeb4 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2219,11 +2219,11 @@ "title": "Payments", "totalpayments": "Total Payments", "markforexport": "Mark for Export", - "markforreexport": "Mark for Reexport" + "markforreexport": "Mark for Re-export" }, "successes": { "exported": "Payment(s) exported successfully.", - "reexported": "Payment Re-exported successfully", + "markreexported": "Payment marked for re-export successfully", "markexported": "Payment(s) marked exported.", "payment": "Payment created successfully. ", "stripe": "Credit card transaction charged successfully." From 094160ebf396eb02d81280c636a32783e23b5424 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 26 May 2023 11:18:59 -0700 Subject: [PATCH 18/18] Update labels. --- .../payment-mark-export-button-component.jsx | 2 +- client/src/translations/en_us/common.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx b/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx index a5e77d1d3..be6c80356 100644 --- a/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx +++ b/client/src/components/payment-mark-export-button/payment-mark-export-button-component.jsx @@ -89,7 +89,7 @@ const PaymentMarkForExportButton = ({ loading={exportLogLoading || updatePaymentLoading} disabled={!!payment.exportedat} > - {t("payments.labels.markforexport")} + {t("payments.labels.markexported")} ); }; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 2bfecaeb4..3e4620add 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2218,7 +2218,7 @@ "signup": "Please contact support to sign up for electronic payments.", "title": "Payments", "totalpayments": "Total Payments", - "markforexport": "Mark for Export", + "markexported": "Mark Exported", "markforreexport": "Mark for Re-export" }, "successes": {