diff --git a/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx b/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx index 6d296edd1..c48875f4c 100644 --- a/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx +++ b/client/src/components/ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component.jsx @@ -3,6 +3,7 @@ import { Button, Form, InputNumber, Popover, Space } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; + export default function CABCpvrtCalculator({ disabled, form }) { const [visibility, setVisibility] = useState(false); @@ -39,7 +40,7 @@ export default function CABCpvrtCalculator({ disabled, form }) { ); return ( - + diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index f2d9d1ef9..6c6e96ad2 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -202,8 +202,6 @@ export const registerMessagingHandlers = ({ socket, client }) => { text: message.text }; - // Add cases for other known message types as needed - default: // Log a warning for unhandled message types logLocal("handleMessageChanged - Unhandled message type", { type: message.type }); @@ -211,7 +209,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { } } - return messageRef; // Keep other messages unchanged + return messageRef; }); } } @@ -245,11 +243,8 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); const updatedList = existingList?.conversations - ? [ - newConversation, - ...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates - ] - : [newConversation]; + ? [newConversation, ...existingList.conversations.filter((conv) => conv.id !== newConversation.id)] + : [newConversation]; // Prevent duplicates client.cache.writeQuery({ query: CONVERSATION_LIST_QUERY, @@ -403,6 +398,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { } break; + default: logLocal("handleConversationChanged - Unhandled type", { type }); client.cache.modify({ @@ -419,10 +415,95 @@ export const registerMessagingHandlers = ({ socket, client }) => { } }; + // Existing handler for phone number opt-out + const handlePhoneNumberOptedOut = async (data) => { + const { bodyshopid, phone_number } = data; + logLocal("handlePhoneNumberOptedOut - Start", data); + + try { + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + phone_number_opt_out(existing = [], { readField }) { + const phoneNumberExists = existing.some( + (ref) => readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid + ); + + if (phoneNumberExists) { + logLocal("handlePhoneNumberOptedOut - Phone number already in cache", { phone_number, bodyshopid }); + return existing; + } + + const newOptOut = { + __typename: "phone_number_opt_out", + id: `temporary-${phone_number}-${Date.now()}`, + bodyshopid, + phone_number, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }; + + return [...existing, newOptOut]; + } + }, + broadcast: true + }); + + client.cache.evict({ + id: "ROOT_QUERY", + fieldName: "phone_number_opt_out", + args: { bodyshopid, search: phone_number } + }); + client.cache.gc(); + + logLocal("handlePhoneNumberOptedOut - Cache updated successfully", data); + } catch (error) { + console.error("Error updating cache for phone number opt-out:", error); + logLocal("handlePhoneNumberOptedOut - Error", { error: error.message }); + } + }; + + // New handler for phone number opt-in + const handlePhoneNumberOptedIn = async (data) => { + const { bodyshopid, phone_number } = data; + logLocal("handlePhoneNumberOptedIn - Start", data); + + try { + // Update the Apollo cache for GET_PHONE_NUMBER_OPT_OUTS by removing the phone number + client.cache.modify({ + id: "ROOT_QUERY", + fields: { + phone_number_opt_out(existing = [], { readField }) { + // Filter out the phone number from the opt-out list + return existing.filter( + (ref) => !(readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid) + ); + } + }, + broadcast: true // Trigger UI updates + }); + + // Evict the cache entry to force a refetch on next query + client.cache.evict({ + id: "ROOT_QUERY", + fieldName: "phone_number_opt_out", + args: { bodyshopid, search: phone_number } + }); + client.cache.gc(); + + logLocal("handlePhoneNumberOptedIn - Cache updated successfully", data); + } catch (error) { + console.error("Error updating cache for phone number opt-in:", error); + logLocal("handlePhoneNumberOptedIn - Error", { error: error.message }); + } + }; + socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); socket.on("message-changed", handleMessageChanged); socket.on("conversation-changed", handleConversationChanged); + socket.on("phone-number-opted-out", handlePhoneNumberOptedOut); + socket.on("phone-number-opted-in", handlePhoneNumberOptedIn); }; export const unregisterMessagingHandlers = ({ socket }) => { @@ -431,4 +512,6 @@ export const unregisterMessagingHandlers = ({ socket }) => { socket.off("new-message-detailed"); socket.off("message-changed"); socket.off("conversation-changed"); + socket.off("phone-number-opted-out"); + socket.off("phone-number-opted-in"); }; diff --git a/client/src/components/chat-messages-list/renderMessage.jsx b/client/src/components/chat-messages-list/renderMessage.jsx index b94e69ee0..c572d77e3 100644 --- a/client/src/components/chat-messages-list/renderMessage.jsx +++ b/client/src/components/chat-messages-list/renderMessage.jsx @@ -2,7 +2,7 @@ import Icon from "@ant-design/icons"; import { Tooltip } from "antd"; import i18n from "i18next"; import dayjs from "../../utils/day"; -import { MdDone, MdDoneAll } from "react-icons/md"; +import { MdClose, MdDone, MdDoneAll } from "react-icons/md"; import { DateTimeFormatter } from "../../utils/DateFormatter"; export const renderMessage = (messages, index) => { @@ -31,13 +31,16 @@ export const renderMessage = (messages, index) => { {/* Message status icons */} - {message.status && (message.status === "sent" || message.status === "delivered") && ( -
- -
- )} + {message.status && + (message.status === "sent" || message.status === "delivered" || message.status === "failed") && ( +
+ +
+ )} - {/* Outbound message metadata */} {message.isoutbound && (
diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 798532a3e..c3e91b2b1 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -1,6 +1,6 @@ import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; -import { Alert, Input, Spin } from "antd"; -import React, { useEffect, useRef, useState } from "react"; +import { Alert, Input, Space, Spin } from "antd"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -68,48 +68,58 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi }; return ( -
- {isOptedOut && } - - - - setMessage(e.target.value)} - onPressEnter={(event) => { - event.preventDefault(); - if (!event.shiftKey && !isOptedOut) handleEnter(); - }} - /> - - - + {isOptedOut && } +
+ {!isOptedOut && ( + <> + + + + )} + + setMessage(e.target.value)} + onPressEnter={(event) => { + event.preventDefault(); + if (!event.shiftKey && !isOptedOut) handleEnter(); }} - spin /> - } - /> -
+ + {!isOptedOut && ( + + )} + + + } + /> +
+ ); } diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index d65f78496..826edea54 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -650,7 +650,7 @@ function Header({ icon: , label: t("menus.header.remoteassist"), children: [ - ...(InstanceRenderManager({ imex: true, rome: true }) + ...(InstanceRenderManager({ imex: true, rome: false }) ? [ { key: "rescue", @@ -662,7 +662,7 @@ function Header({ ] : []), { - key: "rescue", + key: "rescue-zoho", id: "header-rescue-zoho", icon: , label: t("menus.header.rescuemezoho"), diff --git a/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx b/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx index 85e037e86..680428a57 100644 --- a/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx +++ b/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx @@ -80,7 +80,7 @@ export function JobEmployeeAssignments({ ); return ( - + {body ? ( diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx index 338e873dd..5f0c3a2dc 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.predefined.component.jsx @@ -72,7 +72,7 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) { open={open} placement="left" onOpenChange={handleOpenChange} - destroyTooltipOnHide + destroyOnHidden > diff --git a/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx b/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx index d8895803b..7fd2cf2e4 100644 --- a/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx +++ b/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx @@ -75,7 +75,7 @@ export function PartsOrderBackorderEta({ ); return ( - + {backordered_eta} {isAlreadyBackordered && } {loading && } diff --git a/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx b/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx index 26910643c..e38b30ecb 100644 --- a/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx +++ b/client/src/components/parts-order-line-backorder-button/parts-order-line-backorder-button.component.jsx @@ -84,7 +84,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j ); return ( - + diff --git a/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx b/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx index 3e7cdbdcd..e326a455f 100644 --- a/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx @@ -140,7 +140,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]); return ( - + {record[type] ? (
diff --git a/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx b/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx index c5b174f26..be74b1f99 100644 --- a/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx +++ b/client/src/components/time-ticket-calculator/time-ticket-calculator.component.jsx @@ -107,7 +107,7 @@ export default function TimeTicketCalculatorComponent({ open={visible} onOpenChange={handleOpenChange} placement="right" - destroyTooltipOnHide + destroyOnHidden >