From c27b1d802f77da9cb0edf67c2c8590b8325b15e7 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 6 Feb 2025 16:57:55 -0500 Subject: [PATCH] feature/IO-3096-GlobalNotifications - Watchers - Second Version --- client/src/graphql/bodyshop.queries.js | 10 + .../job-watcher-toggle.component.jsx | 177 +++++++++++++----- .../jobs-detail.page.component.jsx | 3 +- client/src/translations/en_us/common.json | 7 +- client/src/translations/es/common.json | 35 ++++ client/src/translations/fr/common.json | 35 ++++ 6 files changed, 217 insertions(+), 50 deletions(-) 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 ( - - + + {/* 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 ( - -