diff --git a/client/src/components/production-board-kanban/production-board-kanban.container.jsx b/client/src/components/production-board-kanban/production-board-kanban.container.jsx index 6182b3ffb..dd5d73d1e 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.container.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.container.jsx @@ -3,7 +3,7 @@ import { useApolloClient, useQuery, useSubscription } from "@apollo/client"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { - GET_JOB_BY_PK, + QUERY_EXACT_JOB_IN_PRODUCTION, QUERY_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW @@ -100,7 +100,7 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp query: QUERY_JOBS_IN_PRODUCTION, data: { jobs: existingJobs.map((job) => - job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "Job" } : job + job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job ) } }); @@ -108,7 +108,7 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp // If the job doesn't exist, fetch it from the server and then add it to the cache try { const { data: jobData } = await client.query({ - query: GET_JOB_BY_PK, + query: QUERY_EXACT_JOB_IN_PRODUCTION, variables: { id: jobId }, fetchPolicy: "network-only" }); @@ -117,7 +117,7 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp client.writeQuery({ query: QUERY_JOBS_IN_PRODUCTION, data: { - jobs: [...existingJobs, { ...jobData.job, __typename: "Job" }] + jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }] } }); } catch (error) { @@ -127,11 +127,11 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp }; // Listen for 'job-changed' events - socket.on("job-updated", handleJobUpdates); + socket.on("production-job-updated", handleJobUpdates); // Clean up on unmount or when dependencies change return () => { - socket.off("job-updated", handleJobUpdates); + socket.off("production-job-updated", handleJobUpdates); }; }, [subscriptionEnabled, socket, bodyshop, data, client]); diff --git a/client/src/components/production-list-table/production-list-table.container.jsx b/client/src/components/production-list-table/production-list-table.container.jsx index 9f564bde7..3cb9d8b5e 100644 --- a/client/src/components/production-list-table/production-list-table.container.jsx +++ b/client/src/components/production-list-table/production-list-table.container.jsx @@ -1,5 +1,5 @@ import { useApolloClient, useQuery, useSubscription } from "@apollo/client"; -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { QUERY_EXACT_JOB_IN_PRODUCTION, QUERY_EXACT_JOBS_IN_PRODUCTION, @@ -9,19 +9,42 @@ import { } from "../../graphql/jobs.queries"; import ProductionListTable from "./production-list-table.component"; import _ from "lodash"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; -export default function ProductionListTableContainer({ subscriptionType = "direct" }) { +export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) { + const client = useApolloClient(); + const { socket } = useContext(SocketContext); + const [joblist, setJoblist] = useState([]); + + // Get Split treatment + const { + treatments: { Websocket_Production } + } = useSplitTreatments({ + attributes: {}, + names: ["Websocket_Production"], + splitKey: bodyshop && bodyshop.imexshopid + }); + + // Determine if subscription is enabled + const subscriptionEnabled = Websocket_Production?.treatment === "on"; + + // Use GraphQL query const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, { pollInterval: 3600000, fetchPolicy: "network-only", nextFetchPolicy: "network-only" }); - const client = useApolloClient(); - const [joblist, setJoblist] = useState([]); + + // Use GraphQL subscription when subscription is enabled const { data: updatedJobs } = useSubscription( - subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION + subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION, + { + skip: !subscriptionEnabled + } ); + // Update joblist when data changes useEffect(() => { if (!(data && data.jobs)) return; setJoblist( @@ -31,34 +54,99 @@ export default function ProductionListTableContainer({ subscriptionType = "direc ); }, [data]); + // Handle updates from GraphQL subscription useEffect(() => { - if (!updatedJobs || joblist.length === 0) return; + if (subscriptionEnabled) { + if (!updatedJobs || joblist.length === 0) return; - const jobDiff = _.differenceWith( - joblist, - updatedJobs.jobs, - (a, b) => a.id === b.id && a.updated_at === b.updated_at - ); + const jobDiff = _.differenceWith( + joblist, + updatedJobs.jobs, + (a, b) => a.id === b.id && a.updated_at === b.updated_at + ); - if (jobDiff.length > 1) { - getUpdatedJobsData(jobDiff.map((j) => j.id)); - } else if (jobDiff.length === 1) { - jobDiff.forEach((job) => { - getUpdatedJobData(job.id); - }); + if (jobDiff.length > 1) { + getUpdatedJobsData(jobDiff.map((j) => j.id)); + } else if (jobDiff.length === 1) { + jobDiff.forEach((job) => { + getUpdatedJobData(job.id); + }); + } + + setJoblist(updatedJobs.jobs); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updatedJobs, subscriptionEnabled]); + + // Handle updates from Socket.IO when subscription is disabled + useEffect(() => { + if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) { + return; } - setJoblist(updatedJobs.jobs); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [updatedJobs]); + const handleJobUpdates = async (jobChangedData) => { + const jobId = jobChangedData.id; + // Access the existing cache for QUERY_JOBS_IN_PRODUCTION + const existingJobsCache = client.readQuery({ + query: QUERY_JOBS_IN_PRODUCTION + }); + + const existingJobs = existingJobsCache?.jobs || []; + + // Check if the job already exists in the cached jobs + const existingJob = existingJobs.find((job) => job.id === jobId); + + if (existingJob) { + // If the job exists, we update the cache without making any additional queries + client.writeQuery({ + query: QUERY_JOBS_IN_PRODUCTION, + data: { + jobs: existingJobs.map((job) => + job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job + ) + } + }); + } else { + // If the job doesn't exist, fetch it from the server and then add it to the cache + try { + const { data: jobData } = await client.query({ + query: QUERY_EXACT_JOB_IN_PRODUCTION, + variables: { id: jobId }, + fetchPolicy: "network-only" + }); + + // Add the job to the existing cached jobs + client.writeQuery({ + query: QUERY_JOBS_IN_PRODUCTION, + data: { + jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }] + } + }); + } catch (error) { + console.error(`Error fetching job ${jobId}: ${error.message}`); + } + } + }; + + // Listen for 'production-job-updated' events + socket.on("production-job-updated", handleJobUpdates); + + // Clean up on unmount or when dependencies change + return () => { + socket.off("production-job-updated", handleJobUpdates); + }; + }, [subscriptionEnabled, socket, bodyshop, client]); + + // Functions to fetch updated job data const getUpdatedJobData = async (jobId) => { - client.query({ + await client.query({ query: QUERY_EXACT_JOB_IN_PRODUCTION, - variables: { id: jobId } + variables: { id: jobId }, + fetchPolicy: "network-only" }); }; - const getUpdatedJobsData = async (jobIds) => { + const getUpdatedJobsData = (jobIds) => { client.query({ query: QUERY_EXACT_JOBS_IN_PRODUCTION, variables: { ids: jobIds } diff --git a/client/src/pages/production-list/production-list.component.jsx b/client/src/pages/production-list/production-list.component.jsx index 177108f6d..aa6e4e1b1 100644 --- a/client/src/pages/production-list/production-list.component.jsx +++ b/client/src/pages/production-list/production-list.component.jsx @@ -26,7 +26,7 @@ export function ProductionListComponent({ bodyshop }) { return ( <> - + ); } diff --git a/server/job/job-updated.js b/server/job/job-updated.js index bec619837..f218f83bc 100644 --- a/server/job/job-updated.js +++ b/server/job/job-updated.js @@ -24,7 +24,7 @@ const jobUpdated = async (req, res) => { const bodyshopID = updatedJob.shopid; // Emit the job-updated event only to the room corresponding to the bodyshop - ioRedis.to(ioHelpers.getBodyshopRoom(bodyshopID)).emit("job-updated", updatedJob); + ioRedis.to(ioHelpers.getBodyshopRoom(bodyshopID)).emit("production-job-updated", updatedJob); return res.json({ message: "Job updated and event emitted" }); };