feature/IO-3182-Phone-Number-Consent - Checkpoint

This commit is contained in:
Dave Richer
2025-05-28 12:17:43 -04:00
parent f6c63bbd74
commit 2e95fa25af
13 changed files with 303 additions and 157 deletions

View File

@@ -1,4 +1,4 @@
import { Badge, Card, List, Space, Tag } from "antd";
import { Badge, Card, List, Space, Tag, Tooltip } from "antd";
import { useEffect, useMemo, useState } from "react";
import { connect } from "react-redux";
import { Virtuoso } from "react-virtuoso";
@@ -9,6 +9,7 @@ import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import _ from "lodash";
import { ExclamationCircleOutlined } from "@ant-design/icons";
import "./chat-conversation-list.styles.scss";
import { useQuery } from "@apollo/client";
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries.js";
@@ -88,7 +89,13 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
const cardExtra = (
<>
<Badge count={item.messages_aggregate.aggregate.count} />
{hasOptOutEntry && <Tag color="red">{t("messaging.labels.no_consent")}</Tag>}
{hasOptOutEntry && (
<Tooltip title={t("consent.text_body")}>
<Tag color="red" icon={<ExclamationCircleOutlined />}>
{t("messaging.labels.no_consent")}
</Tag>
</Tooltip>
)}
</>
);

View File

@@ -58,6 +58,7 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
userid
created_at
read
is_system
}
`,
data: message

View File

@@ -4,13 +4,16 @@
height: 100%;
width: 100%;
}
.archive-button {
height: 20px;
border-radius: 4px;
}
.chat-title {
margin-bottom: 5px;
}
.messages {
display: flex;
flex-direction: column;
@@ -37,11 +40,13 @@
gap: 8px;
}
}
.chat-send-message-button{
.chat-send-message-button {
margin: 0.3rem;
padding-left: 0.5rem;
}
.message-icon {
position: absolute;
bottom: 0.1rem;
@@ -125,6 +130,37 @@
}
}
.system {
align-items: center;
margin: 0.5rem 10%;
.message {
background-color: #f5f5f5;
border-radius: 10px;
padding: 0.5rem 1rem;
text-align: center;
font-style: italic;
color: #555;
width: fit-content;
max-width: 80%;
}
.system-label {
font-size: 0.75rem;
color: #888;
margin-bottom: 0.2rem;
display: block;
}
.system-date {
font-size: 0.75rem;
color: #888;
margin-top: 0.2rem;
text-align: center;
}
}
.virtuoso-container {
flex: 1;
overflow: auto;

View File

@@ -7,12 +7,24 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
export const renderMessage = (messages, index) => {
const message = messages[index];
const isSystem = message.is_system;
// Determine message class
const messageClass = isSystem ? "system messages" : message.isoutbound ? "mine messages" : "yours messages";
// Tooltip content based on message type
const tooltipTitle = isSystem ? (
i18n.t("consent.text_body")
) : (
<DateTimeFormatter>{message.created_at}</DateTimeFormatter>
);
return (
<div key={index} className={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
<div key={index} className={messageClass}>
<div className="message msgmargin">
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
<Tooltip title={tooltipTitle}>
<div>
{isSystem && <span className="system-label">System</span>}
{/* Render images if available */}
{message.image && message.image_path?.length > 0 && (
<div className="message-images">
@@ -26,23 +38,31 @@ export const renderMessage = (messages, index) => {
</div>
)}
{/* Render text if available */}
{message.text && <div>{message.text}</div>}
{message.text && <div className="message-text">{message.text}</div>}
{/* Render date for system messages */}
{isSystem && (
<div className="system-date">
<DateTimeFormatter>{message.created_at}</DateTimeFormatter>
</div>
)}
</div>
</Tooltip>
{/* Message status icons */}
{message.status &&
{/* Message status icons for non-system messages */}
{!isSystem &&
message.status &&
(message.status === "sent" || message.status === "delivered" || message.status === "failed") && (
<div className="message-status">
<Icon
component={message.status === "sent" ? MdDone : message.status === "delivered" ? MdDoneAll : MdClose}
className="message-icon"
style={message.status === "failed" ? { color: "#ff0000" } : undefined}
/>
</div>
)}
</div>
{/* Outbound message metadata */}
{message.isoutbound && (
{/* Outbound message metadata for non-system messages */}
{!isSystem && message.isoutbound && (
<div style={{ fontSize: 10 }}>
{i18n.t("messaging.labels.sentby", {
by: message.userid,

View File

@@ -1,5 +1,5 @@
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Alert, Input, Space, Spin } from "antd";
import { ExclamationCircleOutlined, LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Alert, Input, Space, Spin, Tooltip } from "antd";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -69,7 +69,16 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
return (
<Space direction="vertical" style={{ width: "100%" }} size="middle">
{isOptedOut && <Alert message={t("messaging.errors.no_consent")} type="error" />}
{isOptedOut && (
<Tooltip title={t("consent.text_body")}>
<Alert
showIcon={true}
icon={<ExclamationCircleOutlined />}
message={t("messaging.errors.no_consent")}
type="error"
/>
</Tooltip>
)}
<div className="imex-flex-row" style={{ width: "100%" }}>
{!isOptedOut && (
<>

View File

@@ -1,17 +1,20 @@
import { useQuery } from "@apollo/client";
import { Input, Space, Table, Typography } from "antd";
import { Link } from "react-router-dom";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Input, Table, Typography } from "antd";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { GET_PHONE_NUMBER_OPT_OUTS, SEARCH_OWNERS_BY_PHONE_NUMBERS } from "../../graphql/phone-number-opt-out.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import { useTranslation } from "react-i18next";
import { useState } from "react";
const { Paragraph } = Typography; // Destructure Paragraph from Typography
const { Paragraph } = Typography;
// Commented out Associated Owners section for now
//import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
//import { Link } from "react-router-dom";
//import { useMemo, useState } from "react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -30,7 +33,8 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
fetchPolicy: "network-only"
});
// Prepare phone numbers for owner query
// Commented out Associated Owners section for now
/*// 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]);
@@ -74,16 +78,17 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
: t("consent.phone_2")
: null
}));
};
};*/
const columns = [
{
title: t("consent.phone_number"),
dataIndex: "phone_number",
render: (text) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>,
render: (text) => <ChatOpenButton phone={text} />,
sorter: (a, b) => a.phone_number.localeCompare(b.phone_number)
},
{
// Commented out Associated Owners section for now
/*{
title: t("consent.associated_owners"),
dataIndex: "phone_number",
render: (phoneNumber) => {
@@ -109,7 +114,7 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
const bName = bOwners[0] ? `${bOwners[0].ownr_fn} ${bOwners[0].ownr_ln}` : "";
return aName.localeCompare(bName);
}
},
},*/
{
title: t("consent.created_at"),
dataIndex: "created_at",
@@ -130,7 +135,7 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
<Table
columns={columns}
dataSource={optOutData?.phone_number_opt_out}
loading={optOutLoading || ownersLoading}
loading={optOutLoading /* || ownersLoading*/}
rowKey="id"
style={{ marginTop: 16 }}
/>