feature/IO-3499-React-19 Checkpoint
This commit is contained in:
@@ -8,6 +8,8 @@ import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||
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
|
||||
@@ -27,21 +29,24 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
const [selectedWatcher, setSelectedWatcher] = useState(null);
|
||||
const [selectedTeam, setSelectedTeam] = useState(null);
|
||||
|
||||
const userEmail = currentUser.email;
|
||||
const jobid = job.id;
|
||||
const userEmail = currentUser?.email;
|
||||
const jobid = job?.id;
|
||||
const watcherVars = useMemo(() => ({ jobid }), [jobid]);
|
||||
|
||||
// Fetch current watchers with refetch capability
|
||||
const {
|
||||
data: watcherData,
|
||||
loading: watcherLoading,
|
||||
refetch
|
||||
} = useQuery(GET_JOB_WATCHERS, {
|
||||
variables: { jobid },
|
||||
fetchPolicy: "cache-and-network" // Ensure fresh data from server
|
||||
variables: watcherVars,
|
||||
skip: !jobid,
|
||||
fetchPolicy: "cache-first",
|
||||
notifiyOnNetworkStatusChange: true
|
||||
});
|
||||
|
||||
// Refetch jobWatchers when the popover opens (open changes to 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}`, {
|
||||
@@ -49,18 +54,17 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [open, refetch]);
|
||||
}, [open, refetch, jobid]);
|
||||
|
||||
const jobWatchers = useMemo(() => (watcherData?.job_watchers ? [...watcherData.job_watchers] : []), [watcherData]);
|
||||
const isWatching = jobWatchers.some((w) => w.user_email === userEmail);
|
||||
// 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, {
|
||||
onCompleted: () =>
|
||||
refetch().catch((err) =>
|
||||
console.error(`Something went wrong fetching Notification Watchers after add: ${err?.message}`, {
|
||||
stack: err?.stack
|
||||
})
|
||||
),
|
||||
onError: (err) => {
|
||||
if (err.graphQLErrors && err.graphQLErrors.length > 0) {
|
||||
const errorMessage = err.graphQLErrors[0].message;
|
||||
@@ -69,12 +73,13 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
errorMessage.includes("idx_job_watchers_jobid_user_email_unique")
|
||||
) {
|
||||
console.warn("Watcher already exists for this job and user.");
|
||||
refetch().catch((err) =>
|
||||
// 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: ${err?.message}`,
|
||||
{ stack: err?.stack }
|
||||
`Something went wrong fetching Notification Watchers after uniqueness violation: ${e?.message}`,
|
||||
{ stack: e?.stack }
|
||||
)
|
||||
); // Sync with server to ensure UI reflects actual state
|
||||
);
|
||||
} else {
|
||||
console.error(`Error adding job watcher: ${errorMessage}`);
|
||||
}
|
||||
@@ -83,65 +88,41 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
}
|
||||
},
|
||||
update(cache, { data }) {
|
||||
if (!data || !data.insert_job_watchers_one) {
|
||||
console.warn("No data or insert_job_watchers_one returned from mutation, skipping cache update.");
|
||||
refetch().catch((err) =>
|
||||
console.error(`Something went wrong updating Notification Watchers after add: ${err?.message}`, {
|
||||
stack: err?.stack
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
const inserted = data?.insert_job_watchers_one;
|
||||
if (!inserted) return;
|
||||
|
||||
const insert_job_watchers_one = data.insert_job_watchers_one;
|
||||
const existingData = cache.readQuery({
|
||||
query: GET_JOB_WATCHERS,
|
||||
variables: { jobid }
|
||||
});
|
||||
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;
|
||||
|
||||
cache.writeQuery({
|
||||
query: GET_JOB_WATCHERS,
|
||||
variables: { jobid },
|
||||
data: {
|
||||
...existingData,
|
||||
job_watchers: [...(existingData?.job_watchers || []), insert_job_watchers_one]
|
||||
}
|
||||
return {
|
||||
...existing,
|
||||
job_watchers: [...prev, inserted]
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const [removeWatcher, { loading: removing }] = useMutation(REMOVE_JOB_WATCHER, {
|
||||
onCompleted: () =>
|
||||
refetch().catch((err) =>
|
||||
console.error(`Something went wrong fetching Notification Watchers after remove: ${err?.message}`, {
|
||||
stack: err?.stack
|
||||
})
|
||||
), // Refetch to sync with server after success
|
||||
onError: (err) => console.error(`Error removing job watcher: ${err.message}`),
|
||||
update(cache, { data: { delete_job_watchers } }) {
|
||||
const existingData = cache.readQuery({
|
||||
query: GET_JOB_WATCHERS,
|
||||
variables: { jobid }
|
||||
});
|
||||
update(cache, { data }) {
|
||||
const deleted = data?.delete_job_watchers?.returning?.[0];
|
||||
if (!deleted?.user_email) return;
|
||||
|
||||
const deletedWatcher = delete_job_watchers.returning[0];
|
||||
const updatedWatchers = deletedWatcher
|
||||
? (existingData?.job_watchers || []).filter((watcher) => watcher.user_email !== deletedWatcher.user_email)
|
||||
: existingData?.job_watchers || [];
|
||||
|
||||
cache.writeQuery({
|
||||
query: GET_JOB_WATCHERS,
|
||||
variables: { jobid },
|
||||
data: {
|
||||
...existingData,
|
||||
job_watchers: updatedWatchers
|
||||
}
|
||||
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 {
|
||||
@@ -151,7 +132,9 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
|
||||
const handleRemoveWatcher = useCallback(
|
||||
async (email) => {
|
||||
if (!jobid) return;
|
||||
if (removing) return;
|
||||
if (!email) return;
|
||||
await removeWatcher({ variables: { jobid, userEmail: email } });
|
||||
},
|
||||
[removeWatcher, jobid, removing]
|
||||
@@ -159,36 +142,45 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
|
||||
const handleWatcherSelect = useCallback(
|
||||
async (selectedUser) => {
|
||||
if (!jobid) return;
|
||||
if (adding || removing) return;
|
||||
const employee = bodyshop.employees.find((e) => e.id === selectedUser);
|
||||
if (!employee) return;
|
||||
|
||||
const employee = bodyshop?.employees?.find((e) => e.id === selectedUser);
|
||||
if (!employee?.user_email) return;
|
||||
|
||||
const email = employee.user_email;
|
||||
const isAlreadyWatching = jobWatchers.some((w) => w.user_email === email);
|
||||
const isAlreadyWatching = watcherEmailSet.has(email);
|
||||
|
||||
if (isAlreadyWatching) {
|
||||
await handleRemoveWatcher(email);
|
||||
} else {
|
||||
await addWatcher({ variables: { jobid, userEmail: email } });
|
||||
}
|
||||
|
||||
setSelectedWatcher(null);
|
||||
},
|
||||
[jobWatchers, addWatcher, handleRemoveWatcher, jobid, bodyshop, adding, removing]
|
||||
[watcherEmailSet, addWatcher, handleRemoveWatcher, jobid, bodyshop, adding, removing]
|
||||
);
|
||||
|
||||
const handleTeamSelect = useCallback(
|
||||
async (team) => {
|
||||
if (!jobid) return;
|
||||
if (adding) return;
|
||||
const selectedTeamMembers = JSON.parse(team);
|
||||
const newWatchers = selectedTeamMembers.filter(
|
||||
(email) => !jobWatchers.some((watcher) => watcher.user_email === email)
|
||||
);
|
||||
|
||||
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({
|
||||
@@ -199,8 +191,10 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
setSelectedTeam(null);
|
||||
},
|
||||
[jobWatchers, addWatcher, jobid, adding]
|
||||
[watcherEmailSet, addWatcher, jobid, adding]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -223,7 +217,7 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
handleWatcherSelect={handleWatcherSelect}
|
||||
handleTeamSelect={handleTeamSelect}
|
||||
currentUser={currentUser}
|
||||
isEmployee={isEmployee} // Pass isEmployee to the component
|
||||
isEmployee={isEmployee}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user