feature/IO-3225-Notifications-1.5: Finish
This commit is contained in:
@@ -22,11 +22,11 @@ const EmployeeSearchSelect = ({ options, ...props }) => {
|
|||||||
? options.map((o) => (
|
? options.map((o) => (
|
||||||
<Option key={o.id} value={o.id} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
<Option key={o.id} value={o.id} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
||||||
<Space>
|
<Space>
|
||||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
{`${o.employee_number ?? ""} ${o.first_name} ${o.last_name}`}
|
||||||
|
|
||||||
<Tag color="green">
|
<Tag color="green">
|
||||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||||
</Tag>
|
</Tag>
|
||||||
|
{o.user_email ? <Tag color="blue">{o.user_email}</Tag> : null}
|
||||||
</Space>
|
</Space>
|
||||||
</Option>
|
</Option>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -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, Checkbox, Form, Space, Switch, Table } from "antd";
|
import { Button, Card, Checkbox, Divider, Form, Space, Switch, Table, Typography } from "antd";
|
||||||
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";
|
||||||
@@ -199,6 +199,8 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
|
<Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
|
||||||
|
<Divider />
|
||||||
|
<Typography.Paragraph type="secondary">{t("notifications.labels.auto-add-description")}</Typography.Paragraph>
|
||||||
</Card>
|
</Card>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Card, Tabs } from "antd";
|
import { Button, Card, Tabs } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -24,6 +23,8 @@ import ShopInfoRoGuard from "./shop-info.roguard.component";
|
|||||||
import ShopInfoIntellipay from "./shop-intellipay-config.component";
|
import ShopInfoIntellipay from "./shop-intellipay-config.component";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
import ShopInfoNotificationsAutoadd from "./shop-info.notifications-autoadd.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -41,6 +42,7 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
|||||||
names: ["CriticalPartsScanning", "Enhanced_Payroll"],
|
names: ["CriticalPartsScanning", "Enhanced_Payroll"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
const { scenarioNotificationsOn } = useSocket();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
@@ -137,9 +139,21 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
key: "intellipay",
|
key: "intellipay",
|
||||||
label: InstanceRenderManager({ rome: t("bodyshop.labels.romepay"), imex: t("bodyshop.labels.imexpay") }),
|
label: InstanceRenderManager({
|
||||||
|
rome: t("bodyshop.labels.romepay"),
|
||||||
|
imex: t("bodyshop.labels.imexpay")
|
||||||
|
}),
|
||||||
children: <ShopInfoIntellipay form={form} />
|
children: <ShopInfoIntellipay form={form} />
|
||||||
}
|
},
|
||||||
|
...(scenarioNotificationsOn
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "notifications_autoadd",
|
||||||
|
label: t("bodyshop.labels.notifications.followers"),
|
||||||
|
children: <ShopInfoNotificationsAutoadd form={form} bodyshop={bodyshop} />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// shop-info.notifications-autoadd.component.jsx
|
||||||
|
import React from "react";
|
||||||
|
import { Form, Typography } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component.jsx";
|
||||||
|
|
||||||
|
const { Text, Paragraph } = Typography;
|
||||||
|
|
||||||
|
export default function ShopInfoNotificationsAutoadd({ form, bodyshop }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const employeeOptions = bodyshop?.employees?.filter((e) => e.active && e.id) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Paragraph>{t("bodyshop.fields.notifications.description")}</Paragraph>
|
||||||
|
<Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text>
|
||||||
|
{employeeOptions.length > 0 ? (
|
||||||
|
<Form.Item name="notification_followers" rules={[{ type: "array", message: t("general.validation.array") }]}>
|
||||||
|
<EmployeeSearchSelectComponent
|
||||||
|
style={{ minWidth: "100%" }}
|
||||||
|
mode="multiple"
|
||||||
|
options={employeeOptions}
|
||||||
|
placeholder={t("bodyshop.fields.notifications.placeholder")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<Text type="secondary">{t("bodyshop.fields.no_employees_available")}</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -648,7 +648,11 @@
|
|||||||
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
|
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
|
||||||
"uselocalmediaserver": "Use Local Media Server?",
|
"uselocalmediaserver": "Use Local Media Server?",
|
||||||
"website": "Website",
|
"website": "Website",
|
||||||
"zip_post": "Zip/Postal Code"
|
"zip_post": "Zip/Postal Code",
|
||||||
|
"notifications": {
|
||||||
|
"description": "Select employees to automatically follow new jobs and receive notifications for job updates.",
|
||||||
|
"placeholder": "Search for employees"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"2tiername": "Name => RO",
|
"2tiername": "Name => RO",
|
||||||
@@ -728,7 +732,10 @@
|
|||||||
"ssbuckets": "Job Size Definitions",
|
"ssbuckets": "Job Size Definitions",
|
||||||
"systemsettings": "System Settings",
|
"systemsettings": "System Settings",
|
||||||
"task-presets": "Task Presets",
|
"task-presets": "Task Presets",
|
||||||
"workingdays": "Working Days"
|
"workingdays": "Working Days",
|
||||||
|
"notifications": {
|
||||||
|
"followers": "Notification Followers"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "Contains",
|
"contains": "Contains",
|
||||||
@@ -2441,6 +2448,11 @@
|
|||||||
"fcm": "Push"
|
"fcm": "Push"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"auto-add-on": "Unfollow",
|
||||||
|
"auto-add-off": "Follow",
|
||||||
|
"auto-add-success": "Follow status successfully changed.",
|
||||||
|
"auto-add-failure": "Something went wrong updating your Follow status.",
|
||||||
|
"auto-add-description": "When enabled, the Follow setting automatically adds you as a watcher to new jobs, ensuring you receive notifications for job updates.",
|
||||||
"add-watchers": "Add Watchers",
|
"add-watchers": "Add Watchers",
|
||||||
"add-watchers-team": "Add Team Members",
|
"add-watchers-team": "Add Team Members",
|
||||||
"employee-search": "Search for an Employee",
|
"employee-search": "Search for an Employee",
|
||||||
|
|||||||
@@ -648,7 +648,11 @@
|
|||||||
"use_paint_scale_data": "",
|
"use_paint_scale_data": "",
|
||||||
"uselocalmediaserver": "",
|
"uselocalmediaserver": "",
|
||||||
"website": "",
|
"website": "",
|
||||||
"zip_post": ""
|
"zip_post": "",
|
||||||
|
"notifications": {
|
||||||
|
"description": "",
|
||||||
|
"placeholder": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
@@ -728,7 +732,10 @@
|
|||||||
"ssbuckets": "",
|
"ssbuckets": "",
|
||||||
"systemsettings": "",
|
"systemsettings": "",
|
||||||
"task-presets": "",
|
"task-presets": "",
|
||||||
"workingdays": ""
|
"workingdays": "",
|
||||||
|
"notifications": {
|
||||||
|
"followers": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "",
|
"contains": "",
|
||||||
@@ -2441,6 +2448,11 @@
|
|||||||
"fcm": ""
|
"fcm": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"auto-add-on": "",
|
||||||
|
"auto-add-off": "",
|
||||||
|
"auto-add-success": "",
|
||||||
|
"auto-add-failure": "",
|
||||||
|
"auto-add-description": "",
|
||||||
"add-watchers": "",
|
"add-watchers": "",
|
||||||
"add-watchers-team": "",
|
"add-watchers-team": "",
|
||||||
"employee-search": "",
|
"employee-search": "",
|
||||||
|
|||||||
@@ -648,7 +648,11 @@
|
|||||||
"use_paint_scale_data": "",
|
"use_paint_scale_data": "",
|
||||||
"uselocalmediaserver": "",
|
"uselocalmediaserver": "",
|
||||||
"website": "",
|
"website": "",
|
||||||
"zip_post": ""
|
"zip_post": "",
|
||||||
|
"notifications": {
|
||||||
|
"description": "",
|
||||||
|
"placeholder": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
@@ -728,7 +732,10 @@
|
|||||||
"ssbuckets": "",
|
"ssbuckets": "",
|
||||||
"systemsettings": "",
|
"systemsettings": "",
|
||||||
"task-presets": "",
|
"task-presets": "",
|
||||||
"workingdays": ""
|
"workingdays": "",
|
||||||
|
"notifications": {
|
||||||
|
"followers": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "",
|
"contains": "",
|
||||||
@@ -2441,6 +2448,11 @@
|
|||||||
"fcm": ""
|
"fcm": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"auto-add-on": "",
|
||||||
|
"auto-add-off": "",
|
||||||
|
"auto-add-success": "",
|
||||||
|
"auto-add-failure": "",
|
||||||
|
"auto-add-description": "",
|
||||||
"add-watchers": "",
|
"add-watchers": "",
|
||||||
"add-watchers-team": "",
|
"add-watchers-team": "",
|
||||||
"employee-search": "",
|
"employee-search": "",
|
||||||
|
|||||||
@@ -2958,3 +2958,30 @@ exports.INSERT_JOB_WATCHERS = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports.GET_NOTIFICATION_ASSOCIATIONS_BY_IDS = `
|
||||||
|
query GET_NOTIFICATION_ASSOCIATIONS_BY_IDS($associationIds: [uuid!]!, $shopid: uuid!) {
|
||||||
|
associations(where: { id: { _in: $associationIds }, shopid: { _eq: $shopid }, active: { _eq: true } }) {
|
||||||
|
id
|
||||||
|
useremail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports.GET_EMPLOYEE_EMAILS = `
|
||||||
|
query GET_EMPLOYEE_EMAILS($employeeIds: [uuid!]!, $shopid: uuid!) {
|
||||||
|
employees(where: { id: { _in: $employeeIds }, shopid: { _eq: $shopid }, active: { _eq: true } }) {
|
||||||
|
id
|
||||||
|
user_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports.GET_NOTIFICATION_ASSOCIATIONS_BY_EMAILS = `
|
||||||
|
query GET_NOTIFICATION_ASSOCIATIONS_BY_EMAILS($emails: [String!]!, $shopid: uuid!) {
|
||||||
|
associations(where: { useremail: { _in: $emails }, shopid: { _eq: $shopid }, active: { _eq: true } }) {
|
||||||
|
id
|
||||||
|
useremail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -52,21 +52,23 @@ const autoAddWatchers = async (req) => {
|
|||||||
associationId: assoc.id
|
associationId: assoc.id
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
// Get users from notification_followers (array of association IDs)
|
// Get users from notification_followers (array of employee IDs)
|
||||||
const notificationFollowers = autoAddData?.bodyshops_by_pk?.notification_followers || [];
|
const notificationFollowers = autoAddData?.bodyshops_by_pk?.notification_followers || [];
|
||||||
let followerEmails = [];
|
let followerEmails = [];
|
||||||
if (notificationFollowers.length > 0) {
|
if (notificationFollowers.length > 0) {
|
||||||
// Fetch associations for notification_followers
|
const validFollowers = notificationFollowers.filter((id) => id); // Remove null values
|
||||||
const followerAssociations = await gqlClient.request(queries.GET_NOTIFICATION_ASSOCIATIONS, {
|
if (validFollowers.length > 0) {
|
||||||
emails: [], // Filter by association IDs
|
const employeeData = await gqlClient.request(queries.GET_EMPLOYEE_EMAILS, {
|
||||||
shopid: shopId
|
employeeIds: validFollowers,
|
||||||
});
|
shopid: shopId
|
||||||
followerEmails = followerAssociations.associations
|
});
|
||||||
.filter((assoc) => notificationFollowers.includes(assoc.id))
|
followerEmails = employeeData.employees
|
||||||
.map((assoc) => ({
|
.filter((e) => e.user_email)
|
||||||
email: assoc.useremail,
|
.map((e) => ({
|
||||||
associationId: assoc.id
|
email: e.user_email,
|
||||||
}));
|
associationId: null
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine and deduplicate emails (use email as the unique key)
|
// Combine and deduplicate emails (use email as the unique key)
|
||||||
@@ -91,7 +93,6 @@ const autoAddWatchers = async (req) => {
|
|||||||
.filter((user) => !existingWatcherEmails.includes(user.email))
|
.filter((user) => !existingWatcherEmails.includes(user.email))
|
||||||
.filter((user) => {
|
.filter((user) => {
|
||||||
if (FILTER_SELF_FROM_WATCHERS && hasuraUserRole === "user") {
|
if (FILTER_SELF_FROM_WATCHERS && hasuraUserRole === "user") {
|
||||||
// Fetch user email for hasuraUserId to compare
|
|
||||||
const userData = existingWatchersData?.job_watchers?.find((w) => w.user?.authid === hasuraUserId);
|
const userData = existingWatchersData?.job_watchers?.find((w) => w.user?.authid === hasuraUserId);
|
||||||
return userData ? user.email !== userData.user_email : true;
|
return userData ? user.email !== userData.user_email : true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user