feature/IO-3096-GlobalNotifications - Check-point

This commit is contained in:
Dave Richer
2025-02-11 12:33:13 -05:00
parent 2ee582bfa2
commit 142617bc3d
8 changed files with 93 additions and 38 deletions

View File

@@ -1,6 +1,6 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Button, Card, Col, Form, Row } from "antd"; import { Button, Card, Form, Checkbox, Table } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -8,31 +8,44 @@ import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors"; import { selectCurrentUser } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../../graphql/user.queries.js"; import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../../graphql/user.queries.js";
import { notificationScenarios, notificationChannels } from "../../utils/jobNotificationScenarios.js"; import { notificationScenarios } from "../../utils/jobNotificationScenarios.js";
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx"; import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
const NotificationMethodButtonGroup = ({ value = {}, onChange }) => { /**
* ColumnHeaderCheckbox
*
* A header checkbox for a given channel that toggles the state for all scenarios.
*
* Props:
* - channel: The notification channel (e.g. "app", "email", "fcm").
* - form: The Ant Design form instance.
* - disabled: (Optional) If true, the checkbox will be disabled.
*/
const ColumnHeaderCheckbox = ({ channel, form, disabled = false }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const toggleMethod = (method) => { // Watch the entire form values so that this component re-renders on changes.
const newValue = { ...value, [method]: !value[method] }; const formValues = Form.useWatch([], form) || {};
if (onChange) {
onChange(newValue); // Use the known scenarios list to decide if every row has this channel enabled.
} const allChecked =
notificationScenarios.length > 0 && notificationScenarios.every((scenario) => formValues[scenario]?.[channel]);
const onChange = (e) => {
const checked = e.target.checked;
// Get current form values.
const currentValues = form.getFieldsValue();
// Update each scenario for this channel.
const newValues = { ...currentValues };
notificationScenarios.forEach((scenario) => {
newValues[scenario] = { ...newValues[scenario], [channel]: checked };
});
form.setFieldsValue(newValues);
}; };
return ( return (
<Button.Group> <Checkbox onChange={onChange} checked={allChecked} disabled={disabled}>
{notificationChannels.map((method) => ( {t(`notifications.channels.${channel}`)}
<Button </Checkbox>
disabled={method === "fcm"}
key={method}
type={value[method] ? "primary" : "default"}
onClick={() => toggleMethod(method)}
>
{t(`notifications.channels.${method}`)}
</Button>
))}
</Button.Group>
); );
}; };
@@ -91,6 +104,52 @@ function NotificationSettingsForm({ currentUser }) {
if (error) return <AlertComponent type="error" message={error.message} />; if (error) return <AlertComponent type="error" message={error.message} />;
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
const columns = [
{
title: t("notifications.labels.scenario"),
dataIndex: "scenarioLabel",
key: "scenario",
render: (_, record) => t(`notifications.scenarios.${record.key}`),
width: "90%"
},
{
title: <ColumnHeaderCheckbox channel="app" form={form} />,
dataIndex: "app",
key: "app",
align: "center", // Center the cell content
render: (_, record) => (
<Form.Item name={[record.key, "app"]} valuePropName="checked" noStyle>
<Checkbox />
</Form.Item>
)
},
{
title: <ColumnHeaderCheckbox channel="email" form={form} />,
dataIndex: "email",
key: "email",
align: "center", // Center the cell content
render: (_, record) => (
<Form.Item name={[record.key, "email"]} valuePropName="checked" noStyle>
<Checkbox />
</Form.Item>
)
},
{
title: <ColumnHeaderCheckbox channel="fcm" form={form} disabled />,
dataIndex: "fcm",
key: "fcm",
align: "center", // Center the cell content
render: (_, record) => (
<Form.Item name={[record.key, "fcm"]} valuePropName="checked" noStyle>
<Checkbox disabled />
</Form.Item>
)
}
];
// Create dataSource from the list of scenarios.
const dataSource = notificationScenarios.map((scenario) => ({ key: scenario }));
return ( return (
<Form <Form
form={form} form={form}
@@ -113,15 +172,7 @@ function NotificationSettingsForm({ currentUser }) {
</> </>
} }
> >
<Row gutter={[16, 16]}> <Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
{notificationScenarios.map((scenario) => (
<Col xs={24} sm={12} md={8} key={scenario}>
<Form.Item name={scenario} label={t(`notifications.scenarios.${scenario}`)}>
<NotificationMethodButtonGroup />
</Form.Item>
</Col>
))}
</Row>
</Card> </Card>
</Form> </Form>
); );

View File

@@ -81,10 +81,6 @@ export default connect(
</Card> </Card>
</Form> </Form>
</Col> </Col>
<Col span={24}>
<NotificationSettingsForm />
</Col>
<Col span={24}> <Col span={24}>
<Form onFinish={handleChangePassword} autoComplete={"no"} initialValues={currentUser} layout="vertical"> <Form onFinish={handleChangePassword} autoComplete={"no"} initialValues={currentUser} layout="vertical">
<Card <Card
@@ -124,6 +120,9 @@ export default connect(
</Card> </Card>
</Form> </Form>
</Col> </Col>
<Col span={24}>
<NotificationSettingsForm />
</Col>
</> </>
); );
}); });

View File

@@ -34,7 +34,6 @@ export const QUERY_BODYSHOP = gql`
authlevel authlevel
useremail useremail
default_prod_list_view default_prod_list_view
notification_settings
user { user {
authid authid
email email

View File

@@ -3761,6 +3761,7 @@
}, },
"notifications": { "notifications": {
"labels": { "labels": {
"scenario": "Scenario",
"notificationscenarios": "Job Notification Scenarios", "notificationscenarios": "Job Notification Scenarios",
"save": "Save Scenarios", "save": "Save Scenarios",
"watching-issue": "Watching", "watching-issue": "Watching",

View File

@@ -3761,6 +3761,7 @@
}, },
"notifications": { "notifications": {
"labels": { "labels": {
"scenario": "",
"notificationscenarios": "", "notificationscenarios": "",
"save": "", "save": "",
"watching-issue": "", "watching-issue": "",

View File

@@ -3761,6 +3761,7 @@
}, },
"notifications": { "notifications": {
"labels": { "labels": {
"scenario": "",
"notificationscenarios": "", "notificationscenarios": "",
"save": "", "save": "",
"watching-issue": "", "watching-issue": "",

View File

@@ -16,6 +16,4 @@ const notificationScenarios = [
"alternate-transport-changed" "alternate-transport-changed"
]; ];
const notificationChannels = ["app", "email", "fcm"]; export { notificationScenarios };
export { notificationScenarios, notificationChannels };

View File

@@ -6,7 +6,12 @@
const tasksUpdatedCreatedBuilder = require("../scenarioBuilders/tasksUpdatedCreatedBuilder"); const tasksUpdatedCreatedBuilder = require("../scenarioBuilders/tasksUpdatedCreatedBuilder");
const notificationScenarios = [ const notificationScenarios = [
{ key: "job-assigned-to-me", table: "jobs", fields: ["scheduled_in", "scheduled_completion", "scheduled_delivery"] }, {
key: "job-assigned-to-me",
table: "jobs",
fields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"],
matchEmployee: true
},
{ key: "bill-posted", table: "bills" }, { key: "bill-posted", table: "bills" },
{ key: "new-note-added", table: "notes", onNew: true }, { key: "new-note-added", table: "notes", onNew: true },
{ {