226 lines
7.2 KiB
JavaScript
226 lines
7.2 KiB
JavaScript
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
import { useMutation, useQuery } from "@apollo/client/react";
|
|
import { ADD_JOB_WATCHER, GET_JOB_WATCHERS, REMOVE_JOB_WATCHER } from "../../graphql/jobs.queries.js";
|
|
import { connect } from "react-redux";
|
|
import { createStructuredSelector } from "reselect";
|
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
|
import { useTreatmentsWithConfig } from "../../feature-flags/splitio-react-replacement";
|
|
import JobWatcherToggleComponent from "./job-watcher-toggle.component.jsx";
|
|
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
|
|
|
const EMPTY_ARRAY = Object.freeze([]);
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
bodyshop: selectBodyshop,
|
|
currentUser: selectCurrentUser
|
|
});
|
|
|
|
function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
|
const {
|
|
treatments: { Enhanced_Payroll }
|
|
} = useTreatmentsWithConfig({
|
|
attributes: {},
|
|
names: ["Enhanced_Payroll"],
|
|
splitKey: bodyshop && bodyshop.imexshopid
|
|
});
|
|
|
|
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
|
const [open, setOpen] = useState(false);
|
|
const [selectedWatcher, setSelectedWatcher] = useState(null);
|
|
const [selectedTeam, setSelectedTeam] = useState(null);
|
|
|
|
const userEmail = currentUser?.email;
|
|
const jobid = job?.id;
|
|
const watcherVars = useMemo(() => ({ jobid }), [jobid]);
|
|
|
|
const {
|
|
data: watcherData,
|
|
loading: watcherLoading,
|
|
refetch
|
|
} = useQuery(GET_JOB_WATCHERS, {
|
|
variables: watcherVars,
|
|
skip: !jobid,
|
|
fetchPolicy: "cache-first",
|
|
notifiyOnNetworkStatusChange: true
|
|
});
|
|
|
|
// Refetch jobWatchers when the popover opens
|
|
useEffect(() => {
|
|
if (!jobid) return;
|
|
if (open) {
|
|
refetch().catch((err) =>
|
|
console.error(`Something went wrong fetching Notification Watchers on popover open: ${err?.message}`, {
|
|
stack: err?.stack
|
|
})
|
|
);
|
|
}
|
|
}, [open, refetch, jobid]);
|
|
|
|
// Do NOT clone arrays; keep referential stability for React Compiler and to reduce rerenders.
|
|
const jobWatchers = watcherData?.job_watchers ?? EMPTY_ARRAY;
|
|
const watcherEmailSet = useMemo(
|
|
() => new Set((jobWatchers ?? EMPTY_ARRAY).map((w) => w?.user_email).filter(Boolean)),
|
|
[jobWatchers]
|
|
);
|
|
const isWatching = !!userEmail && watcherEmailSet.has(userEmail);
|
|
|
|
const [addWatcher, { loading: adding }] = useMutation(ADD_JOB_WATCHER, {
|
|
onError: (err) => {
|
|
if (err.graphQLErrors && err.graphQLErrors.length > 0) {
|
|
const errorMessage = err.graphQLErrors[0].message;
|
|
if (
|
|
errorMessage.includes("Uniqueness violation") ||
|
|
errorMessage.includes("idx_job_watchers_jobid_user_email_unique")
|
|
) {
|
|
console.warn("Watcher already exists for this job and user.");
|
|
// Only refetch for this edge case to ensure UI is accurate
|
|
refetch().catch((e) =>
|
|
console.error(
|
|
`Something went wrong fetching Notification Watchers after uniqueness violation: ${e?.message}`,
|
|
{ stack: e?.stack }
|
|
)
|
|
);
|
|
} else {
|
|
console.error(`Error adding job watcher: ${errorMessage}`);
|
|
}
|
|
} else {
|
|
console.error(`Unexpected error adding job watcher: ${err.message || JSON.stringify(err)}`);
|
|
}
|
|
},
|
|
update(cache, { data }) {
|
|
const inserted = data?.insert_job_watchers_one;
|
|
if (!inserted) return;
|
|
|
|
cache.updateQuery({ query: GET_JOB_WATCHERS, variables: watcherVars }, (existing) => {
|
|
const prev = existing?.job_watchers ?? [];
|
|
if (prev.some((w) => w.user_email === inserted.user_email)) return existing;
|
|
|
|
return {
|
|
...existing,
|
|
job_watchers: [...prev, inserted]
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
const [removeWatcher, { loading: removing }] = useMutation(REMOVE_JOB_WATCHER, {
|
|
onError: (err) => console.error(`Error removing job watcher: ${err.message}`),
|
|
update(cache, { data }) {
|
|
const deleted = data?.delete_job_watchers?.returning?.[0];
|
|
if (!deleted?.user_email) return;
|
|
|
|
cache.updateQuery({ query: GET_JOB_WATCHERS, variables: watcherVars }, (existing) => {
|
|
const prev = existing?.job_watchers ?? [];
|
|
return {
|
|
...existing,
|
|
job_watchers: prev.filter((w) => w.user_email !== deleted.user_email)
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
const handleToggleSelf = useCallback(async () => {
|
|
if (!jobid || !userEmail) return;
|
|
if (adding || removing || !isEmployee) return;
|
|
|
|
if (isWatching) {
|
|
await removeWatcher({ variables: { jobid, userEmail } });
|
|
} else {
|
|
await addWatcher({ variables: { jobid, userEmail } });
|
|
}
|
|
}, [isWatching, addWatcher, removeWatcher, jobid, userEmail, adding, removing, isEmployee]);
|
|
|
|
const handleRemoveWatcher = useCallback(
|
|
async (email) => {
|
|
if (!jobid) return;
|
|
if (removing) return;
|
|
if (!email) return;
|
|
await removeWatcher({ variables: { jobid, userEmail: email } });
|
|
},
|
|
[removeWatcher, jobid, removing]
|
|
);
|
|
|
|
const handleWatcherSelect = useCallback(
|
|
async (selectedUser) => {
|
|
if (!jobid) return;
|
|
if (adding || removing) return;
|
|
|
|
const employee = bodyshop?.employees?.find((e) => e.id === selectedUser);
|
|
if (!employee?.user_email) return;
|
|
|
|
const email = employee.user_email;
|
|
const isAlreadyWatching = watcherEmailSet.has(email);
|
|
|
|
if (isAlreadyWatching) {
|
|
await handleRemoveWatcher(email);
|
|
} else {
|
|
await addWatcher({ variables: { jobid, userEmail: email } });
|
|
}
|
|
|
|
setSelectedWatcher(null);
|
|
},
|
|
[watcherEmailSet, addWatcher, handleRemoveWatcher, jobid, bodyshop, adding, removing]
|
|
);
|
|
|
|
const handleTeamSelect = useCallback(
|
|
async (team) => {
|
|
if (!jobid) return;
|
|
if (adding) return;
|
|
|
|
let selectedTeamMembers = [];
|
|
try {
|
|
selectedTeamMembers = Array.isArray(team) ? team : JSON.parse(team);
|
|
} catch {
|
|
selectedTeamMembers = [];
|
|
}
|
|
|
|
const newWatchers = (selectedTeamMembers ?? []).filter(Boolean).filter((email) => !watcherEmailSet.has(email));
|
|
if (newWatchers.length === 0) {
|
|
console.warn("All selected team members are already watchers.");
|
|
setSelectedTeam(null);
|
|
return;
|
|
}
|
|
|
|
await Promise.all(
|
|
newWatchers.map((email) =>
|
|
addWatcher({
|
|
variables: {
|
|
jobid,
|
|
userEmail: email
|
|
}
|
|
})
|
|
)
|
|
);
|
|
|
|
setSelectedTeam(null);
|
|
},
|
|
[watcherEmailSet, addWatcher, jobid, adding]
|
|
);
|
|
|
|
return (
|
|
<JobWatcherToggleComponent
|
|
jobWatchers={jobWatchers}
|
|
isWatching={isWatching}
|
|
watcherLoading={watcherLoading}
|
|
adding={adding}
|
|
removing={removing}
|
|
open={open}
|
|
setOpen={setOpen}
|
|
selectedWatcher={selectedWatcher}
|
|
setSelectedWatcher={setSelectedWatcher}
|
|
selectedTeam={selectedTeam}
|
|
setSelectedTeam={setSelectedTeam}
|
|
bodyshop={bodyshop}
|
|
Enhanced_Payroll={Enhanced_Payroll}
|
|
handleToggleSelf={handleToggleSelf}
|
|
handleRemoveWatcher={handleRemoveWatcher}
|
|
handleWatcherSelect={handleWatcherSelect}
|
|
handleTeamSelect={handleTeamSelect}
|
|
currentUser={currentUser}
|
|
isEmployee={isEmployee}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default connect(mapStateToProps)(JobWatcherToggleContainer);
|