feature/IO-3096-GlobalNotifications - Global Notification Settings on profile page
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Card, Form, Switch, Row, Col, Spin } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../../graphql/user.queries.js";
|
||||
import notificationScenarios from "../../utils/jobNotificationScenarios.js";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
||||
|
||||
function NotificationSettingsForm({ currentUser }) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [initialValues, setInitialValues] = useState({});
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
|
||||
// Fetch notification settings
|
||||
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: { email: currentUser.email },
|
||||
skip: !currentUser
|
||||
});
|
||||
|
||||
const [updateNotificationSettings, { loading: saving }] = useMutation(UPDATE_NOTIFICATION_SETTINGS);
|
||||
|
||||
// Populate form with fetched data
|
||||
useEffect(() => {
|
||||
if (data?.associations?.length > 0) {
|
||||
const settings = data.associations[0].notification_settings || {};
|
||||
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
|
||||
acc[scenario] = settings[scenario] ?? false;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
setInitialValues(formattedValues);
|
||||
form.setFieldsValue(formattedValues);
|
||||
setIsDirty(false); // Reset dirty state when new data loads
|
||||
}
|
||||
}, [data, form]);
|
||||
|
||||
const handleSave = async (values) => {
|
||||
if (data?.associations?.length > 0) {
|
||||
const userId = data.associations[0].id;
|
||||
await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
||||
setInitialValues(values);
|
||||
setIsDirty(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormChange = () => {
|
||||
setIsDirty(true);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
form.setFieldsValue(initialValues);
|
||||
setIsDirty(false);
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||
if (loading) return <LoadingSpinner />;
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSave}
|
||||
onValuesChange={handleFormChange}
|
||||
initialValues={initialValues}
|
||||
autoComplete="off"
|
||||
layout="vertical"
|
||||
>
|
||||
<Card
|
||||
title={t("notifications.labels.notificationscenarios")}
|
||||
extra={
|
||||
<>
|
||||
<Button type="default" onClick={handleReset} disabled={!isDirty} style={{ marginRight: 8 }}>
|
||||
{t("general.actions.clear")}
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" disabled={!isDirty} loading={saving}>
|
||||
{t("notifications.labels.save")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
{notificationScenarios.map((scenario) => (
|
||||
<Col xs={24} sm={12} md={8} key={scenario}>
|
||||
<Form.Item name={scenario} label={t(`notifications.scenarios.${scenario}`)} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Card>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
// Redux connection
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(NotificationSettingsForm);
|
||||
@@ -9,6 +9,7 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { logImEXEvent, updateCurrentPassword } from "../../firebase/firebase.utils";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import NotificationSettingsForm from "./notification-settings.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
@@ -46,6 +47,8 @@ export default connect(
|
||||
}
|
||||
};
|
||||
|
||||
const handleScenarios = async ({ values }) => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Col span={24}>
|
||||
@@ -78,6 +81,10 @@ export default connect(
|
||||
</Card>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<NotificationSettingsForm />
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<Form onFinish={handleChangePassword} autoComplete={"no"} initialValues={currentUser} layout="vertical">
|
||||
<Card
|
||||
|
||||
@@ -85,3 +85,21 @@ export const UPDATE_KANBAN_SETTINGS = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_NOTIFICATION_SETTINGS = gql`
|
||||
query QUERY_NOTIFICATION_SETTINGS($email: String!) {
|
||||
associations(where: { _and: { useremail: { _eq: $email }, active: { _eq: true } } }) {
|
||||
id
|
||||
notification_settings
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_NOTIFICATION_SETTINGS = gql`
|
||||
mutation UPDATE_NOTIFICATION_SETTINGS($id: uuid!, $ns: jsonb) {
|
||||
update_associations_by_pk(pk_columns: { id: $id }, _set: { notification_settings: $ns }) {
|
||||
id
|
||||
notification_settings
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3758,6 +3758,29 @@
|
||||
"validation": {
|
||||
"unique_vendor_name": "You must enter a unique vendor name."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"labels": {
|
||||
"notificationscenarios": "Notification Scenarios",
|
||||
"save": "Save Scenarios"
|
||||
},
|
||||
"scenarios": {
|
||||
"job-assigned-to-me": "Job Assigned to Me",
|
||||
"bill-posted": "Bill Posted",
|
||||
"critical-parts-status-changed": "Critical Parts Status Changed",
|
||||
"part-marked-back-ordered": "Part Marked Back Ordered",
|
||||
"new-note-added": "New Note Added",
|
||||
"supplement-imported": "Supplement Imported",
|
||||
"schedule-dates-changed": "Schedule Dates Changed",
|
||||
"tasks-updated-created": "Tasks Updated / Created",
|
||||
"new-media-added-reassigned": "New Media Added or Reassigned",
|
||||
"new-time-ticket-posted": "New Time Ticket Posted",
|
||||
"intake-delivery-checklist-completed": "Intake or Delivery Checklist Completed",
|
||||
"job-added-to-production": "Job Added to Production",
|
||||
"job-status-change": "Job Status Changed",
|
||||
"payment-collected-completed": "Payment Collected / Completed",
|
||||
"alternate-transport-changed": "Alternate Transport Changed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
client/src/utils/jobNotificationScenarios.js
Normal file
19
client/src/utils/jobNotificationScenarios.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const notificationScenarios = [
|
||||
"job-assigned-to-me",
|
||||
"bill-posted",
|
||||
"critical-parts-status-changed",
|
||||
"part-marked-back-ordered",
|
||||
"new-note-added",
|
||||
"supplement-imported",
|
||||
"schedule-dates-changed",
|
||||
"tasks-updated-created",
|
||||
"new-media-added-reassigned",
|
||||
"new-time-ticket-posted",
|
||||
"intake-delivery-checklist-completed",
|
||||
"job-added-to-production",
|
||||
"job-status-change",
|
||||
"payment-collected-completed",
|
||||
"alternate-transport-changed"
|
||||
];
|
||||
|
||||
export default notificationScenarios;
|
||||
Reference in New Issue
Block a user