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

This commit is contained in:
Dave Richer
2025-05-20 16:04:36 -04:00
parent 9d81c68a4d
commit 83860152a9
12 changed files with 540 additions and 43 deletions

View File

@@ -0,0 +1,131 @@
import { useMutation, useQuery } from "@apollo/client";
import { Table, Switch, Input, Tooltip, Upload, Button } from "antd";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import {
GET_PHONE_NUMBER_CONSENTS,
SET_PHONE_NUMBER_CONSENT,
BULK_SET_PHONE_NUMBER_CONSENT
} from "../../graphql/consent.queries.js";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import { UploadOutlined } from "@ant-design/icons";
import { phone } from "phone";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = () => ({});
function PhoneNumberConsentList({ bodyshop }) {
const { t } = useTranslation();
const [search, setSearch] = useState("");
const { loading, data } = useQuery(GET_PHONE_NUMBER_CONSENTS, {
variables: { bodyshopid: bodyshop.id, search },
fetchPolicy: "network-only"
});
const [setConsent] = useMutation(SET_PHONE_NUMBER_CONSENT);
const [bulkSetConsent] = useMutation(BULK_SET_PHONE_NUMBER_CONSENT);
const columns = [
{
title: t("consent.phone_number"),
dataIndex: "phone_number",
render: (text) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>
},
{
title: t("consent.status"),
dataIndex: "consent_status",
render: (status, record) => (
<Tooltip title={record.history?.[0]?.reason || "No audit history"}>
<Switch
checked={status}
onChange={(checked) =>
setConsent({
variables: {
bodyshopid: bodyshop.id,
phone_number: record.phone_number,
consent_status: checked,
reason: "Manual override in app",
changed_by: "user" // Replace with actual user email from context
},
optimisticResponse: {
insert_phone_number_consent_one: {
__typename: "phone_number_consent",
id: record.id,
bodyshopid: bodyshop.id,
phone_number: record.phone_number,
consent_status: checked,
created_at: record.created_at,
updated_at: new Date().toISOString(),
consent_updated_at: new Date().toISOString()
}
}
})
}
/>
</Tooltip>
)
},
{
title: t("consent.updated_at"),
dataIndex: "consent_updated_at",
render: (text) => <TimeAgoFormatter>{text}</TimeAgoFormatter>
}
];
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 objects = lines
.filter((line) => line.trim())
.map((line) => {
const [phone_number, consent_status] = line.split(",");
return {
bodyshopid: bodyshop.id,
phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""),
consent_status: consent_status.trim().toLowerCase() === "true"
};
});
try {
await bulkSetConsent({
variables: { objects },
context: { headers: { "x-reason": "System update via bulk upload", "x-changed-by": "system" } }
});
} catch (error) {
console.error("Bulk upload failed:", error);
}
};
reader.readAsText(file);
return false;
};
return (
<div>
<Input.Search
placeholder={t("general.labels.search")}
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}
loading={loading}
rowKey="id"
style={{ marginTop: 16 }}
/>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(PhoneNumberConsentList);