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

This commit is contained in:
Dave Richer
2025-05-21 14:32:35 -04:00
parent 7bd5190bf2
commit 8ee52598e8
31 changed files with 128 additions and 991 deletions

View File

@@ -1,6 +1,6 @@
import { useQuery, useApolloClient } from "@apollo/client";
import { Table, Switch, Input, Tooltip, Upload, Button } from "antd";
import { useState, useEffect } from "react";
import { useApolloClient, useQuery } from "@apollo/client";
import { Input, Table } from "antd";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -8,9 +8,7 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
import { GET_PHONE_NUMBER_CONSENTS } from "../../graphql/consent.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import { UploadOutlined } from "@ant-design/icons";
import { phone } from "phone";
import axios from "axios";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
@@ -32,216 +30,6 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
const client = useApolloClient();
const { socket } = useSocket();
useEffect(() => {
if (!socket || !socket.connected) return;
const handleConsentChanged = ({ bodyshopId, phone_number, consent_status, reason }) => {
if (bodyshopId !== bodyshop.id) return;
try {
const cacheData = client.readQuery({
query: GET_PHONE_NUMBER_CONSENTS,
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }
});
if (!cacheData?.phone_number_consent) {
console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in WebSocket handler");
return;
}
const updatedConsents = cacheData.phone_number_consent.map((consent) =>
consent.phone_number === phone_number
? {
...consent,
consent_status,
consent_updated_at: new Date().toISOString(),
phone_number_consent_history: [
{
__typename: "phone_number_consent_history",
id: `temp-${Date.now()}`,
reason,
changed_at: new Date().toISOString(),
old_value: consent.consent_status,
new_value: consent_status,
changed_by: currentUser.email
},
...(consent.phone_number_consent_history || [])
]
}
: consent
);
client.writeQuery(
{
query: GET_PHONE_NUMBER_CONSENTS,
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }
},
{
phone_number_consent: updatedConsents
}
);
console.log("WebSocket cache update:", { phone_number, consent_status, updatedConsents });
} catch (error) {
console.error("Error updating consent cache (WebSocket):", error.message, error.stack);
}
};
socket.on("consent-changed", handleConsentChanged);
return () => {
socket.off("consent-changed", handleConsentChanged);
};
}, [socket, client, bodyshop.id, search, currentUser.email]);
const handleSetConsent = async (phone_number, consent_status) => {
try {
const response = await axios.post("/sms/setConsent", {
bodyshopid: bodyshop.id,
phone_number,
consent_status,
reason: "Manual override in app",
changed_by: currentUser.email
});
const updatedConsent = {
...response.data.consent,
phone_number_consent_history: response.data.consent.phone_number_consent_history.map((history) => ({
...history,
__typename: "phone_number_consent_history"
}))
};
// Update Apollo cache
const cacheData = client.readQuery({
query: GET_PHONE_NUMBER_CONSENTS,
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }
});
let cacheUpdated = false;
if (cacheData?.phone_number_consent) {
const isPhoneNumberInCache = cacheData.phone_number_consent.some(
(consent) => consent.phone_number === phone_number
);
const updatedConsents = isPhoneNumberInCache
? cacheData.phone_number_consent.map((consent) =>
consent.phone_number === phone_number ? updatedConsent : consent
)
: [...cacheData.phone_number_consent, updatedConsent];
cacheUpdated = client.writeQuery(
{
query: GET_PHONE_NUMBER_CONSENTS,
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }
},
{
phone_number_consent: updatedConsents
}
);
console.log("Cache update in handleSetConsent:", {
phone_number,
consent_status,
updatedConsents,
search
});
} else {
console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in handleSetConsent");
}
// Always refetch to ensure UI updates
await refetch();
notification.success({
message: t("consent.update_success")
});
} catch (error) {
notification.error({
message: t("consent.update_failed")
});
console.error("Error updating consent:", error.message, error.stack);
}
};
const handleBulkUpload = async (file) => {
const reader = new FileReader();
reader.onload = async (e) => {
const text = e.target.result;
const lines = text.split("\n").slice(1); // Skip header
const consents = lines
.filter((line) => line.trim())
.map((line) => {
const [phone_number, consent_status] = line.split(",");
return {
phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""),
consent_status: consent_status.trim().toLowerCase() === "true"
};
});
try {
const response = await axios.post("/sms/bulkSetConsent", {
bodyshopid: bodyshop.id,
consents
});
const updatedConsents = response.data.consents.map((consent) => ({
...consent,
phone_number_consent_history: consent.phone_number_consent_history.map((history) => ({
...history,
__typename: "phone_number_consent_history"
}))
}));
// Update Apollo cache
const cacheData = client.readQuery({
query: GET_PHONE_NUMBER_CONSENTS,
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }
});
if (cacheData?.phone_number_consent) {
const updatedConsentsMap = new Map(updatedConsents.map((consent) => [consent.phone_number, consent]));
const mergedConsents = cacheData.phone_number_consent.map((consent) =>
updatedConsentsMap.has(consent.phone_number) ? updatedConsentsMap.get(consent.phone_number) : consent
);
updatedConsents.forEach((consent) => {
if (!mergedConsents.some((c) => c.phone_number === consent.phone_number)) {
mergedConsents.push(consent);
}
});
client.writeQuery(
{
query: GET_PHONE_NUMBER_CONSENTS,
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }
},
{
phone_number_consent: mergedConsents
}
);
console.log("Cache update in handleBulkUpload:", { updatedConsents, mergedConsents });
} else {
console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in handleBulkUpload");
}
// Refetch to ensure UI updates
await refetch();
} catch (error) {
notification.error({
message: t("consent.bulk_update_failed")
});
console.error("Bulk upload failed:", error.message, error.stack);
}
};
reader.readAsText(file);
return false;
};
if (!bodyshop?.enforce_sms_consent) return null;
const columns = [
{
title: t("consent.phone_number"),
@@ -249,15 +37,6 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
render: (text) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>,
sorter: (a, b) => a.phone_number.localeCompare(b.phone_number)
},
{
title: t("consent.status"),
dataIndex: "consent_status",
render: (status, record) => (
<Tooltip title={record.phone_number_consent_history?.[0]?.reason || "No audit history"}>
<Switch checked={status} onChange={(checked) => handleSetConsent(record.phone_number, checked)} />
</Tooltip>
)
},
{
title: t("consent.updated_at"),
dataIndex: "consent_updated_at",
@@ -272,9 +51,7 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
onSearch={(value) => setSearch(value)}
style={{ marginBottom: 16 }}
/>
<Upload beforeUpload={handleBulkUpload} accept=".csv" showUploadList={false}>
<Button icon={<UploadOutlined />}>{t("consent.bulk_upload")}</Button>
</Upload>
<Table
columns={columns}
dataSource={data?.phone_number_consent}