feature/IO-3096-GlobalNotifications - Check-point
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -3761,6 +3761,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"scenario": "",
|
||||||
"notificationscenarios": "",
|
"notificationscenarios": "",
|
||||||
"save": "",
|
"save": "",
|
||||||
"watching-issue": "",
|
"watching-issue": "",
|
||||||
|
|||||||
@@ -3761,6 +3761,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"scenario": "",
|
||||||
"notificationscenarios": "",
|
"notificationscenarios": "",
|
||||||
"save": "",
|
"save": "",
|
||||||
"watching-issue": "",
|
"watching-issue": "",
|
||||||
|
|||||||
@@ -16,6 +16,4 @@ const notificationScenarios = [
|
|||||||
"alternate-transport-changed"
|
"alternate-transport-changed"
|
||||||
];
|
];
|
||||||
|
|
||||||
const notificationChannels = ["app", "email", "fcm"];
|
export { notificationScenarios };
|
||||||
|
|
||||||
export { notificationScenarios, notificationChannels };
|
|
||||||
|
|||||||
@@ -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 },
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user