diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js
index 670fe963e..32a93980f 100644
--- a/client/src/graphql/bodyshop.queries.js
+++ b/client/src/graphql/bodyshop.queries.js
@@ -349,3 +349,13 @@ export const QUERY_STRIPE_ID = gql`
}
}
`;
+
+export const GET_ACTIVE_EMPLOYEES_IN_SHOP = gql`
+ query GetActiveEmployeesInShop($shopid: uuid!) {
+ associations(where: { shopid: { _eq: $shopid } }) {
+ id
+ useremail
+ shopid
+ }
+ }
+`;
diff --git a/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx b/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx
index 4a043e1ef..3fc09c04b 100644
--- a/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx
+++ b/client/src/pages/jobs-detail/job-watcher-toggle.component.jsx
@@ -1,71 +1,154 @@
-import { useCallback, useMemo } from "react";
+import { useCallback, useMemo, useState } from "react";
import { useMutation, useQuery } from "@apollo/client";
-import { EyeFilled, EyeOutlined } from "@ant-design/icons";
-import { GET_JOB_WATCHERS, ADD_JOB_WATCHER, REMOVE_JOB_WATCHER } from "../../graphql/jobs.queries.js";
-import { Button, Tooltip } from "antd";
+import { EyeFilled, EyeOutlined, UserOutlined } from "@ant-design/icons";
+import { ADD_JOB_WATCHER, GET_JOB_WATCHERS, REMOVE_JOB_WATCHER } from "../../graphql/jobs.queries.js";
+import { Avatar, Button, List, Popover, Tooltip, Typography } from "antd";
import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
+import EmployeeSearchSelectComponent from "../../components/employee-search-select/employee-search-select.component.jsx";
+import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component.jsx"; // Ensure correct path
-const JobWatcherToggle = ({ job, currentUser }) => {
+const { Text } = Typography;
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+ currentUser: selectCurrentUser
+});
+
+const JobWatcherToggle = ({ job, currentUser, bodyshop }) => {
const { t } = useTranslation();
const userEmail = currentUser.email;
const jobid = job.id;
- // Fetch current watchers
- const { data, loading } = useQuery(GET_JOB_WATCHERS, {
- variables: { jobid }
- });
+ const [open, setOpen] = useState(false);
+ const [selectedWatcher, setSelectedWatcher] = useState(null); // New state for selected value
- // Extract current watchers list
- const jobWatchers = useMemo(() => data?.job_watchers || [], [data]);
- const isWatching = useMemo(() => !!jobWatchers.find((w) => w.user_email === userEmail), [jobWatchers, userEmail]);
+ // Fetch current watchers
+ const { data: watcherData, loading: watcherLoading } = useQuery(GET_JOB_WATCHERS, { variables: { jobid } });
+
+ // Extract watchers list
+ const jobWatchers = useMemo(() => watcherData?.job_watchers || [], [watcherData]);
+ const isWatching = useMemo(() => jobWatchers.some((w) => w.user_email === userEmail), [jobWatchers, userEmail]);
// Add watcher mutation
- const [addWatcher] = useMutation(ADD_JOB_WATCHER, {
- variables: { jobid, userEmail },
+ const [addWatcher, { loading: adding }] = useMutation(ADD_JOB_WATCHER, {
refetchQueries: [{ query: GET_JOB_WATCHERS, variables: { jobid } }]
});
// Remove watcher mutation
- const [removeWatcher] = useMutation(REMOVE_JOB_WATCHER, {
- variables: { jobid, userEmail },
+ const [removeWatcher, { loading: removing }] = useMutation(REMOVE_JOB_WATCHER, {
refetchQueries: [{ query: GET_JOB_WATCHERS, variables: { jobid } }]
});
- // Toggle watcher status
- const handleToggle = useCallback(() => {
- if (!isWatching) {
- // Fix: Add if not watching, remove if watching
- addWatcher().catch((err) => console.error(`Something went wrong adding a job watcher: ${err.message}`));
- } else {
- removeWatcher().catch((err) => console.error(`Something went wrong removing a job watcher: ${err.message}`));
- }
- }, [isWatching, addWatcher, removeWatcher]);
+ // Toggle watcher for self
+ const handleToggleSelf = useCallback(() => {
+ (isWatching
+ ? removeWatcher({ variables: { jobid, userEmail } })
+ : addWatcher({ variables: { jobid, userEmail } })
+ ).catch((err) => console.error(`Error updating job watcher: ${err.message}`));
+ }, [isWatching, addWatcher, removeWatcher, jobid, userEmail]);
- if (loading) {
- return (
-
- }
- disabled
- aria-label={t("notifications.aria.toggle")}
- />
-
+ // Handle removing a watcher
+ const handleRemoveWatcher = (userEmail) => {
+ removeWatcher({ variables: { jobid, userEmail } }).catch((err) =>
+ console.error(`Error removing job watcher: ${err.message}`)
);
- }
+ };
+
+ const handleWatcherSelect = (selectedUser) => {
+ const employee = bodyshop.employees.find((e) => e.id === selectedUser);
+ if (!employee) return;
+
+ const isAlreadyWatching = jobWatchers.some((w) => w.user_email === employee.user_email);
+
+ if (isAlreadyWatching) {
+ handleRemoveWatcher(employee.user_email);
+ } else {
+ addWatcher({ variables: { jobid, userEmail: employee.user_email } }).catch((err) =>
+ console.error(`Error adding job watcher: ${err.message}`)
+ );
+ }
+
+ // Clear selection
+ setSelectedWatcher(null);
+ };
+
+ // Popover content
+ const popoverContent = (
+
+ {/* Self-toggle Button */}
+ : }
+ onClick={handleToggleSelf}
+ loading={adding || removing}
+ >
+ {isWatching ? t("notifications.tooltips.unwatch") : t("notifications.tooltips.watch")}
+
+
+ {/* List of Watchers */}
+
+ {t("notifications.labels.watching-issue")}
+
+
+ {watcherLoading ? (
+
+ ) : (
+ {
+ const employee = bodyshop.employees.find((e) => e.user_email === watcher.user_email);
+ const displayName = employee ? `${employee.first_name} ${employee.last_name}` : watcher.user_email;
+
+ return (
+ handleRemoveWatcher(watcher.user_email)}>
+ {t("notifications.actions.remove")}
+
+ ]}
+ >
+ } />}
+ title={{displayName}}
+ description={watcher.user_email} // Keep the email for reference
+ />
+
+ );
+ }}
+ />
+ )}
+
+ {/* Employee Search Select (for adding watchers) */}
+ {t("notifications.labels.add-watchers")}
+ jobWatchers.every((w) => w.user_email !== e.user_email))}
+ placeholder={t("production.labels.employeesearch")}
+ value={selectedWatcher} // Controlled value
+ onChange={(value) => {
+ setSelectedWatcher(value); // Update selected state
+ handleWatcherSelect(value); // Add watcher logic
+ }}
+ />
+
+ );
return (
-
- : }
- onClick={handleToggle}
- aria-label={t("notifications.aria.toggle")}
- />
-
+
+
+ : }
+ loading={watcherLoading}
+ />
+
+
);
};
-export default JobWatcherToggle;
+export default connect(mapStateToProps)(JobWatcherToggle);
diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx
index 8785413e5..b65f15e8e 100644
--- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx
+++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx
@@ -2,7 +2,6 @@ import Icon, {
BarsOutlined,
CalendarFilled,
DollarCircleOutlined,
- EyeOutlined,
FileImageFilled,
HistoryOutlined,
PrinterFilled,
@@ -326,7 +325,7 @@ export function JobsDetailPage({
title={
-
+
{job.ro_number || t("general.labels.na")}
}
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index c4d7af3fd..da13654d5 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -3762,7 +3762,12 @@
"notifications": {
"labels": {
"notificationscenarios": "Notification Scenarios",
- "save": "Save Scenarios"
+ "save": "Save Scenarios",
+ "watching-issue": "Watching",
+ "add-watchers": "Add Watchers"
+ },
+ "actions": {
+ "remove": "remove"
},
"aria": {
"toggle": "Toggle Watching Job"
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index d2bbf7b9c..208d7318d 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -3758,6 +3758,41 @@
"validation": {
"unique_vendor_name": ""
}
+ },
+ "notifications": {
+ "labels": {
+ "notificationscenarios": "",
+ "save": "",
+ "watching-issue": "",
+ "add-watchers": ""
+ },
+ "actions": {
+ "remove": ""
+ },
+ "aria": {
+ "toggle": ""
+ },
+ "tooltips": {
+ "watch": "",
+ "unwatch": ""
+ },
+ "scenarios": {
+ "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": ""
+ }
}
}
}
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 17b5cde2e..8e2144048 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -3758,6 +3758,41 @@
"validation": {
"unique_vendor_name": ""
}
+ },
+ "notifications": {
+ "labels": {
+ "notificationscenarios": "",
+ "save": "",
+ "watching-issue": "",
+ "add-watchers": ""
+ },
+ "actions": {
+ "remove": ""
+ },
+ "aria": {
+ "toggle": ""
+ },
+ "tooltips": {
+ "watch": "",
+ "unwatch": ""
+ },
+ "scenarios": {
+ "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": ""
+ }
}
}
}