diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 6c6e96ad2..88e0ac9df 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -462,7 +462,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { logLocal("handlePhoneNumberOptedOut - Error", { error: error.message }); } }; - + // New handler for phone number opt-in const handlePhoneNumberOptedIn = async (data) => { const { bodyshopid, phone_number } = data; diff --git a/client/src/components/phone-number-consent/phone-number-consent.component.jsx b/client/src/components/phone-number-consent/phone-number-consent.component.jsx index 7981a3db2..0df53ca30 100644 --- a/client/src/components/phone-number-consent/phone-number-consent.component.jsx +++ b/client/src/components/phone-number-consent/phone-number-consent.component.jsx @@ -1,11 +1,11 @@ import { useQuery } from "@apollo/client"; import { Input, Table } from "antd"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; -import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries"; +import { GET_PHONE_NUMBER_OPT_OUTS, SEARCH_OWNERS_BY_PHONE_NUMBERS } from "../../graphql/phone-number-opt-out.queries"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter"; @@ -20,11 +20,71 @@ const mapDispatchToProps = () => ({}); function PhoneNumberConsentList({ bodyshop, currentUser }) { const { t } = useTranslation(); const [search, setSearch] = useState(""); - const { loading, data } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, { + + // Fetch opt-out phone numbers + const { loading: optOutLoading, data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, { variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }, fetchPolicy: "network-only" }); + // Prepare phone numbers for owner query + const phoneNumbers = useMemo(() => { + return optOutData?.phone_number_opt_out?.map((item) => item.phone_number) || []; + }, [optOutData?.phone_number_opt_out]); + const allPhoneNumbers = useMemo(() => { + const normalized = phoneNumbers; + const withPlusOne = phoneNumbers.map((num) => `+1${num}`); + return [...normalized, ...withPlusOne].filter(Boolean); + }, [phoneNumbers]); + + // Fetch owners for all phone numbers + const { loading: ownersLoading, data: ownersData } = useQuery(SEARCH_OWNERS_BY_PHONE_NUMBERS, { + variables: { bodyshopid: bodyshop.id, phone_numbers: allPhoneNumbers }, + skip: allPhoneNumbers.length === 0 || !bodyshop.id, + fetchPolicy: "network-only" + }); + + // Format owner names for display + const formatOwnerName = (owner) => { + const parts = []; + if (owner.ownr_fn || owner.ownr_ln) { + parts.push([owner.ownr_fn, owner.ownr_ln].filter(Boolean).join(" ")); + } + if (owner.ownr_co_nm) { + parts.push(owner.ownr_co_nm); + } + return parts.join(", ") || "-"; + }; + + // Map phone numbers to their associated owners and identify phone field + const getAssociatedOwners = (phoneNumber) => { + if (!ownersData?.owners) return []; + const normalizedPhone = phoneNumber.replace(/^\+1/, ""); + return ownersData.owners + .filter( + (owner) => + owner.ownr_ph1 === phoneNumber || + owner.ownr_ph2 === phoneNumber || + owner.ownr_ph1 === normalizedPhone || + owner.ownr_ph2 === normalizedPhone || + owner.ownr_ph1 === `+1${phoneNumber}` || + owner.ownr_ph2 === `+1${phoneNumber}` + ) + .map((owner) => ({ + ...owner, + phoneField: + [owner.ownr_ph1, owner.ownr_ph2].includes(phoneNumber) || + [owner.ownr_ph1, owner.ownr_ph2].includes(normalizedPhone) || + [owner.ownr_ph1, owner.ownr_ph2].includes(`+1${phoneNumber}`) + ? owner.ownr_ph1 === phoneNumber || + owner.ownr_ph1 === normalizedPhone || + owner.ownr_ph1 === `+1${phoneNumber}` + ? t("consent.phone_1") + : t("consent.phone_2") + : null + })); + }; + const columns = [ { title: t("consent.phone_number"), @@ -32,6 +92,28 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) { render: (text) => {text}, sorter: (a, b) => a.phone_number.localeCompare(b.phone_number) }, + { + title: t("consent.associated_owners"), + dataIndex: "phone_number", + render: (phoneNumber) => { + const owners = getAssociatedOwners(phoneNumber); + if (!owners || owners.length === 0) { + return t("consent.no_owners"); + } + return owners.map((owner) => ( +
+ {formatOwnerName(owner)} ({owner.phoneField}) +
+ )); + }, + sorter: (a, b) => { + const aOwners = getAssociatedOwners(a.phone_number); + const bOwners = getAssociatedOwners(b.phone_number); + const aName = aOwners[0] ? formatOwnerName(aOwners[0]) : ""; + const bName = bOwners[0] ? formatOwnerName(bOwners[0]) : ""; + return aName.localeCompare(bName); + } + }, { title: t("consent.created_at"), dataIndex: "created_at", @@ -50,8 +132,8 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) { diff --git a/client/src/graphql/phone-number-opt-out.queries.js b/client/src/graphql/phone-number-opt-out.queries.js index 861bdf64d..123d06744 100644 --- a/client/src/graphql/phone-number-opt-out.queries.js +++ b/client/src/graphql/phone-number-opt-out.queries.js @@ -26,3 +26,25 @@ export const GET_PHONE_NUMBER_OPT_OUTS = gql` } } `; + +export const SEARCH_OWNERS_BY_PHONE_NUMBERS = gql` + query SEARCH_OWNERS_BY_PHONE_NUMBERS($bodyshopid: uuid!, $phone_numbers: [String!]) { + owners( + where: { + shopid: { _eq: $bodyshopid }, + _or: [ + { ownr_ph1: { _in: $phone_numbers } }, + { ownr_ph2: { _in: $phone_numbers } } + ] + } + ) { + id + ownr_fn + ownr_ln + ownr_co_nm + ownr_ph1 + ownr_ph2 + __typename + } + } +`; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index ac5833e86..dca4f62ef 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -3871,7 +3871,11 @@ }, "consent": { "phone_number": "Phone Number", - "created_at": "Created At" + "associated_owners": "Associated Owners", + "created_at": "Opt-Out Date", + "no_owners": "No Associated Owners", + "phone_1": "Phone 1", + "phone_2": "Phone 2" }, "settings": { "title": "Phone Number Opt-Out List" diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 2303f9610..cfc349043 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -3873,7 +3873,11 @@ }, "consent": { "phone_number": "", - "created_at": "" + "associated_owners": "", + "created_at": "", + "no_owners": "", + "phone_1": "", + "phone_2": "" }, "settings": { "title": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index e7bcccbf0..0e49be0e1 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -3872,8 +3872,12 @@ } }, "consent": { - "phone_number": "", - "created_at": "" + "phone_number": "Phone Number", + "associated_owners": "Associated Owners", + "created_at": "Opt-Out Date", + "no_owners": "No Associated Owners", + "phone_1": "Phone 1", + "phone_2": "Phone 2" }, "settings": { "title": ""