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 React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button, Card, Col, Form, Row, Switch } from "antd";
|
import { Button, Card, Col, Form, Row } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -8,9 +8,34 @@ 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 from "../../utils/jobNotificationScenarios.js";
|
import { notificationScenarios, notificationChannels } 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 }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const toggleMethod = (method) => {
|
||||||
|
const newValue = { ...value, [method]: !value[method] };
|
||||||
|
if (onChange) {
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button.Group>
|
||||||
|
{notificationChannels.map((method) => (
|
||||||
|
<Button
|
||||||
|
disabled={method === "fcm"}
|
||||||
|
key={method}
|
||||||
|
type={value[method] ? "primary" : "default"}
|
||||||
|
onClick={() => toggleMethod(method)}
|
||||||
|
>
|
||||||
|
{t(`notifications.channels.${method}`)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Button.Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function NotificationSettingsForm({ currentUser }) {
|
function NotificationSettingsForm({ currentUser }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -31,8 +56,10 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.associations?.length > 0) {
|
if (data?.associations?.length > 0) {
|
||||||
const settings = data.associations[0].notification_settings || {};
|
const settings = data.associations[0].notification_settings || {};
|
||||||
|
// For each scenario, expect an object with keys { app, email, fcm }.
|
||||||
|
// If not present in the fetched data, default to all false.
|
||||||
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
|
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
|
||||||
acc[scenario] = settings[scenario] ?? false;
|
acc[scenario] = settings[scenario] ?? { app: false, email: false, fcm: false };
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
@@ -45,6 +72,7 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
const handleSave = async (values) => {
|
const handleSave = async (values) => {
|
||||||
if (data?.associations?.length > 0) {
|
if (data?.associations?.length > 0) {
|
||||||
const userId = data.associations[0].id;
|
const userId = data.associations[0].id;
|
||||||
|
// `values` now contains, for each scenario, an object with keys { app, email, fcm }
|
||||||
await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
||||||
setInitialValues(values);
|
setInitialValues(values);
|
||||||
setIsDirty(false);
|
setIsDirty(false);
|
||||||
@@ -88,8 +116,8 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{notificationScenarios.map((scenario) => (
|
{notificationScenarios.map((scenario) => (
|
||||||
<Col xs={24} sm={12} md={8} key={scenario}>
|
<Col xs={24} sm={12} md={8} key={scenario}>
|
||||||
<Form.Item name={scenario} label={t(`notifications.scenarios.${scenario}`)} valuePropName="checked">
|
<Form.Item name={scenario} label={t(`notifications.scenarios.${scenario}`)}>
|
||||||
<Switch />
|
<NotificationMethodButtonGroup />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -3761,7 +3761,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"notificationscenarios": "Notification Scenarios",
|
"notificationscenarios": "Job Notification Scenarios",
|
||||||
"save": "Save Scenarios",
|
"save": "Save Scenarios",
|
||||||
"watching-issue": "Watching",
|
"watching-issue": "Watching",
|
||||||
"add-watchers": "Add Watchers",
|
"add-watchers": "Add Watchers",
|
||||||
@@ -3795,6 +3795,11 @@
|
|||||||
"job-status-change": "Job Status Changed",
|
"job-status-change": "Job Status Changed",
|
||||||
"payment-collected-completed": "Payment Collected / Completed",
|
"payment-collected-completed": "Payment Collected / Completed",
|
||||||
"alternate-transport-changed": "Alternate Transport Changed"
|
"alternate-transport-changed": "Alternate Transport Changed"
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"app": "App",
|
||||||
|
"email": "Email",
|
||||||
|
"fcm": "Push"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3795,6 +3795,11 @@
|
|||||||
"job-status-change": "",
|
"job-status-change": "",
|
||||||
"payment-collected-completed": "",
|
"payment-collected-completed": "",
|
||||||
"alternate-transport-changed": ""
|
"alternate-transport-changed": ""
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"app": "",
|
||||||
|
"email": "",
|
||||||
|
"fcm": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3795,6 +3795,11 @@
|
|||||||
"job-status-change": "",
|
"job-status-change": "",
|
||||||
"payment-collected-completed": "",
|
"payment-collected-completed": "",
|
||||||
"alternate-transport-changed": ""
|
"alternate-transport-changed": ""
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"app": "",
|
||||||
|
"email": "",
|
||||||
|
"fcm": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,6 @@ const notificationScenarios = [
|
|||||||
"alternate-transport-changed"
|
"alternate-transport-changed"
|
||||||
];
|
];
|
||||||
|
|
||||||
export default notificationScenarios;
|
const notificationChannels = ["app", "email", "fcm"];
|
||||||
|
|
||||||
|
export { notificationScenarios, notificationChannels };
|
||||||
|
|||||||
@@ -62,11 +62,27 @@ const scenarioParser = async (req) => {
|
|||||||
if (isEmpty(associationsData?.associations)) return;
|
if (isEmpty(associationsData?.associations)) return;
|
||||||
|
|
||||||
// Step 6: For each matching scenario, add a scenarioWatchers property
|
// Step 6: For each matching scenario, add a scenarioWatchers property
|
||||||
// that includes only the jobWatchers with the notification setting enabled
|
// that includes only the jobWatchers with at least one notification method enabled.
|
||||||
|
// Each watcher object is formatted as: { user, email, app, fcm }
|
||||||
finalScenarioData.matchingScenarios.forEach((scenario) => {
|
finalScenarioData.matchingScenarios.forEach((scenario) => {
|
||||||
scenario.scenarioWatchers = associationsData.associations
|
scenario.scenarioWatchers = associationsData.associations
|
||||||
.filter((assoc) => assoc.notification_settings && assoc.notification_settings[scenario.key] === true)
|
.filter((assoc) => {
|
||||||
.map((assoc) => assoc.useremail);
|
// Retrieve the settings object for this scenario (it now contains app, email, and fcm)
|
||||||
|
const settings = assoc.notification_settings && assoc.notification_settings[scenario.key];
|
||||||
|
// Only include this association if at least one notification channel is enabled
|
||||||
|
return settings && (settings.app || settings.email || settings.fcm);
|
||||||
|
})
|
||||||
|
.map((assoc) => {
|
||||||
|
const settings = assoc.notification_settings[scenario.key];
|
||||||
|
return {
|
||||||
|
// Use assoc.user if available, otherwise fallback to assoc.useremail as the identifier
|
||||||
|
user: assoc.user || assoc.useremail,
|
||||||
|
// The email field here is the user's email notification setting (boolean)
|
||||||
|
email: settings.email,
|
||||||
|
app: settings.app,
|
||||||
|
fcm: settings.fcm
|
||||||
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 7: Call builder functions for each matching scenario (fire-and-forget)
|
// Step 7: Call builder functions for each matching scenario (fire-and-forget)
|
||||||
|
|||||||
Reference in New Issue
Block a user