feature/IO-3096-GlobalNotifications - Checkpoint
This commit is contained in:
@@ -11,22 +11,12 @@ import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../..
|
|||||||
import { notificationScenarios } 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 ColumnHeaderCheckbox = ({ channel, form, disabled = false, onHeaderChange }) => {
|
||||||
* 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();
|
||||||
// Watch the entire form values so that this component re-renders on changes.
|
// Subscribe to all form values so that this component re-renders on changes.
|
||||||
const formValues = Form.useWatch([], form) || {};
|
const formValues = Form.useWatch([], form) || {};
|
||||||
|
|
||||||
// Use the known scenarios list to decide if every row has this channel enabled.
|
// Determine if all scenarios for this channel are checked.
|
||||||
const allChecked =
|
const allChecked =
|
||||||
notificationScenarios.length > 0 && notificationScenarios.every((scenario) => formValues[scenario]?.[channel]);
|
notificationScenarios.length > 0 && notificationScenarios.every((scenario) => formValues[scenario]?.[channel]);
|
||||||
|
|
||||||
@@ -39,7 +29,12 @@ const ColumnHeaderCheckbox = ({ channel, form, disabled = false }) => {
|
|||||||
notificationScenarios.forEach((scenario) => {
|
notificationScenarios.forEach((scenario) => {
|
||||||
newValues[scenario] = { ...newValues[scenario], [channel]: checked };
|
newValues[scenario] = { ...newValues[scenario], [channel]: checked };
|
||||||
});
|
});
|
||||||
|
// Update form values.
|
||||||
form.setFieldsValue(newValues);
|
form.setFieldsValue(newValues);
|
||||||
|
// Manually mark the form as dirty.
|
||||||
|
if (onHeaderChange) {
|
||||||
|
onHeaderChange();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -55,7 +50,7 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
const [initialValues, setInitialValues] = useState({});
|
const [initialValues, setInitialValues] = useState({});
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
|
||||||
// Fetch notification settings
|
// Fetch notification settings.
|
||||||
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
|
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
@@ -65,12 +60,11 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
|
|
||||||
const [updateNotificationSettings, { loading: saving }] = useMutation(UPDATE_NOTIFICATION_SETTINGS);
|
const [updateNotificationSettings, { loading: saving }] = useMutation(UPDATE_NOTIFICATION_SETTINGS);
|
||||||
|
|
||||||
// Populate form with fetched data
|
// Populate form with fetched data.
|
||||||
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 }.
|
// Ensure each scenario has an object with { 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] ?? { app: false, email: false, fcm: false };
|
acc[scenario] = settings[scenario] ?? { app: false, email: false, fcm: false };
|
||||||
return acc;
|
return acc;
|
||||||
@@ -78,20 +72,21 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
|
|
||||||
setInitialValues(formattedValues);
|
setInitialValues(formattedValues);
|
||||||
form.setFieldsValue(formattedValues);
|
form.setFieldsValue(formattedValues);
|
||||||
setIsDirty(false); // Reset dirty state when new data loads
|
setIsDirty(false); // Reset dirty state when new data loads.
|
||||||
}
|
}
|
||||||
}, [data, form]);
|
}, [data, form]);
|
||||||
|
|
||||||
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 }
|
// Save the updated notification settings.
|
||||||
await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
||||||
setInitialValues(values);
|
setInitialValues(values);
|
||||||
setIsDirty(false);
|
setIsDirty(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mark the form as dirty on any manual change.
|
||||||
const handleFormChange = () => {
|
const handleFormChange = () => {
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
};
|
};
|
||||||
@@ -113,10 +108,10 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
width: "90%"
|
width: "90%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <ColumnHeaderCheckbox channel="app" form={form} />,
|
title: <ColumnHeaderCheckbox channel="app" form={form} onHeaderChange={() => setIsDirty(true)} />,
|
||||||
dataIndex: "app",
|
dataIndex: "app",
|
||||||
key: "app",
|
key: "app",
|
||||||
align: "center", // Center the cell content
|
align: "center",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Form.Item name={[record.key, "app"]} valuePropName="checked" noStyle>
|
<Form.Item name={[record.key, "app"]} valuePropName="checked" noStyle>
|
||||||
<Checkbox />
|
<Checkbox />
|
||||||
@@ -124,10 +119,10 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <ColumnHeaderCheckbox channel="email" form={form} />,
|
title: <ColumnHeaderCheckbox channel="email" form={form} onHeaderChange={() => setIsDirty(true)} />,
|
||||||
dataIndex: "email",
|
dataIndex: "email",
|
||||||
key: "email",
|
key: "email",
|
||||||
align: "center", // Center the cell content
|
align: "center",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Form.Item name={[record.key, "email"]} valuePropName="checked" noStyle>
|
<Form.Item name={[record.key, "email"]} valuePropName="checked" noStyle>
|
||||||
<Checkbox />
|
<Checkbox />
|
||||||
@@ -135,10 +130,10 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <ColumnHeaderCheckbox channel="fcm" form={form} disabled />,
|
title: <ColumnHeaderCheckbox channel="fcm" form={form} disabled onHeaderChange={() => setIsDirty(true)} />,
|
||||||
dataIndex: "fcm",
|
dataIndex: "fcm",
|
||||||
key: "fcm",
|
key: "fcm",
|
||||||
align: "center", // Center the cell content
|
align: "center",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Form.Item name={[record.key, "fcm"]} valuePropName="checked" noStyle>
|
<Form.Item name={[record.key, "fcm"]} valuePropName="checked" noStyle>
|
||||||
<Checkbox disabled />
|
<Checkbox disabled />
|
||||||
@@ -147,7 +142,6 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create dataSource from the list of scenarios.
|
|
||||||
const dataSource = notificationScenarios.map((scenario) => ({ key: scenario }));
|
const dataSource = notificationScenarios.map((scenario) => ({ key: scenario }));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -178,7 +172,6 @@ function NotificationSettingsForm({ currentUser }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redux connection
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Button, Card, Col, Form, Input } from "antd";
|
import { Button, Card, Col, Form, Input } from "antd";
|
||||||
import { LockOutlined } from "@ant-design/icons";
|
import { LockOutlined } from "@ant-design/icons";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -47,8 +46,6 @@ export default connect(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScenarios = async ({ values }) => {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
|
|||||||
@@ -2706,6 +2706,8 @@ query GET_JOB_WATCHERS($jobid: uuid!) {
|
|||||||
}
|
}
|
||||||
job: jobs_by_pk(id: $jobid) {
|
job: jobs_by_pk(id: $jobid) {
|
||||||
id,
|
id,
|
||||||
|
ro_number
|
||||||
|
clm_no
|
||||||
bodyshop {
|
bodyshop {
|
||||||
id
|
id
|
||||||
shopname
|
shopname
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const consoleDir = require("../../utils/consoleDir");
|
||||||
|
const jobStatusChangeBuilder = (data) => {
|
||||||
|
consoleDir(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = jobStatusChangeBuilder;
|
||||||
@@ -5,15 +5,25 @@
|
|||||||
// Builder: function to handle the scenario
|
// Builder: function to handle the scenario
|
||||||
|
|
||||||
const tasksUpdatedCreatedBuilder = require("../scenarioBuilders/tasksUpdatedCreatedBuilder");
|
const tasksUpdatedCreatedBuilder = require("../scenarioBuilders/tasksUpdatedCreatedBuilder");
|
||||||
|
const jobStatusChangeBuilder = require("../scenarioBuilders/jobStatusChangeBuilder");
|
||||||
|
const jobAssignedToMeBuilder = require("../scenarioBuilders/jobAssignedToMeBuilder");
|
||||||
const notificationScenarios = [
|
const notificationScenarios = [
|
||||||
{
|
{
|
||||||
key: "job-assigned-to-me",
|
key: "job-assigned-to-me",
|
||||||
table: "jobs",
|
table: "jobs",
|
||||||
fields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"],
|
fields: ["employee_pre", "employee_body", "employee_csr", "employee_refinish"],
|
||||||
matchEmployee: true
|
matchEmployee: true,
|
||||||
|
builder: jobAssignedToMeBuilder
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bill-posted",
|
||||||
|
table: "bills"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "new-note-added",
|
||||||
|
table: "notes",
|
||||||
|
onNew: true
|
||||||
},
|
},
|
||||||
{ key: "bill-posted", table: "bills" },
|
|
||||||
{ key: "new-note-added", table: "notes", onNew: true },
|
|
||||||
{
|
{
|
||||||
key: "schedule-dates-changed",
|
key: "schedule-dates-changed",
|
||||||
table: "jobs",
|
table: "jobs",
|
||||||
@@ -26,16 +36,22 @@ const notificationScenarios = [
|
|||||||
// onNew: true,
|
// onNew: true,
|
||||||
builder: tasksUpdatedCreatedBuilder
|
builder: tasksUpdatedCreatedBuilder
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "job-status-change",
|
||||||
|
table: "jobs",
|
||||||
|
fields: ["status"],
|
||||||
|
builder: jobStatusChangeBuilder
|
||||||
|
},
|
||||||
{ key: "job-added-to-production", table: "jobs", fields: ["introduction"] },
|
{ key: "job-added-to-production", table: "jobs", fields: ["introduction"] },
|
||||||
{ key: "job-status-change", table: "jobs", fields: ["status"] },
|
|
||||||
{ key: "alternate-transport-changed", table: "jobs", fields: ["alt_transport"] },
|
{ key: "alternate-transport-changed", table: "jobs", fields: ["alt_transport"] },
|
||||||
{ key: "payment-collected-completed" },
|
{ key: "payment-collected-completed", table: "payments", onNew: true },
|
||||||
{ key: "new-media-added-reassigned" },
|
// MAKE SURE YOU ARE NOT ON A LMS ENVIRONMENT
|
||||||
{ key: "new-time-ticket-posted" },
|
{ key: "new-media-added-reassigned", table: "documents" },
|
||||||
{ key: "intake-delivery-checklist-completed" },
|
{ key: "new-time-ticket-posted", table: "timetickets" },
|
||||||
|
{ key: "intake-delivery-checklist-completed", table: "jobs", fields: ["intakechecklist"] },
|
||||||
{ key: "supplement-imported" },
|
{ key: "supplement-imported" },
|
||||||
{ key: "critical-parts-status-changed" },
|
{ key: "critical-parts-status-changed", table: "joblines" },
|
||||||
{ key: "part-marked-back-ordered" }
|
{ key: "part-marked-back-ordered", table: "joblines" }
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user