diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index 4d10d9369..f7f76a69c 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -58,17 +58,40 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr const updatedLanes = cloneDeep(prevBoardLanes.lanes); // Find the lane containing the card with the updated job ID - const sourceLane = updatedLanes.find((lane) => lane.cards.some((card) => card.id === updatedJob.id)); + let sourceLane = updatedLanes.find((lane) => lane.cards.some((card) => card.id === updatedJob.id)); if (!sourceLane) { - console.error("Source lane not found for the updated job."); - return prevBoardLanes; // Return the previous state if no source lane is found + console.log("Card not found in any lane. Checking for valid status to add it."); + + // Find the target lane based on the new status if the card does not exist + const targetLane = updatedLanes.find((lane) => lane.id === updatedJob.status); + + if (targetLane && updatedJob.isInProduction) { + // Check if job is in production and status is valid + console.log(`Adding card to lane ${targetLane.title}`); + + // Add the new card to the target lane + const newCard = { + id: updatedJob.id, + metadata: { ...updatedJob } + }; + targetLane.cards.push(newCard); + + // Update the lane title with the new card count + targetLane.title = `${targetLane.title.split(" ")[0]} (${targetLane.cards.length})`; + + return { lanes: updatedLanes }; // Return early since the card is added + } else { + console.error("No valid lane or status to add the job to."); + return prevBoardLanes; // Return the previous state if no valid status or lane + } } - // Find the card in the source lane + // If the card exists, find it in the source lane const cardIndex = sourceLane.cards.findIndex((card) => card.id === updatedJob.id); const currentCard = sourceLane.cards[cardIndex]; + // If we somehow can't find the card, return if (!currentCard) { console.error("Card not found for the updated job."); return prevBoardLanes; // Return the previous state if the card is not found @@ -76,7 +99,6 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr // Iterate through the properties of updatedJob and update the corresponding values in currentCard.metadata Object.keys(updatedJob).forEach((key) => { - // Ensure the field exists in currentCard.metadata before updating it if (key in currentCard.metadata && currentCard.metadata[key] !== updatedJob[key]) { console.log(`Updating ${key} from ${currentCard.metadata[key]} to ${updatedJob[key]}`); currentCard.metadata[key] = updatedJob[key]; @@ -99,10 +121,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr // Find the target lane based on the new status const targetLane = updatedLanes.find((lane) => lane.id === updatedJob.status); if (targetLane) { - // Move the card to the target lane and update its data targetLane.cards.push({ ...cardToMove, metadata: { ...currentCard.metadata } }); - - // Update the lane titles with the new card count sourceLane.title = `${sourceLane.title.split(" ")[0]} (${sourceLane.cards.length})`; targetLane.title = `${targetLane.title.split(" ")[0]} (${targetLane.cards.length})`; } else { @@ -112,12 +131,8 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr // Case 2: Only data has changed else if (isDataChanged && !isLaneChanged) { console.log("Case 2: Only Data Changed"); - - // Update the card's metadata in place sourceLane.cards[cardIndex] = { ...currentCard, metadata: { ...currentCard.metadata } }; - - // Force a shallow change in the source lane to trigger re-render - sourceLane.cards = [...sourceLane.cards]; + // sourceLane.cards = [...sourceLane.cards]; // Force shallow change for re-render } // Case 3: Only the lane has changed else if (!isDataChanged && isLaneChanged) { @@ -129,10 +144,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr // Find the target lane based on the new status const targetLane = updatedLanes.find((lane) => lane.id === updatedJob.status); if (targetLane) { - // Move the card to the new lane without changing its data targetLane.cards.push(cardToMove); - - // Update the lane titles with the new card count sourceLane.title = `${sourceLane.title.split(" ")[0]} (${sourceLane.cards.length})`; targetLane.title = `${targetLane.title.split(" ")[0]} (${targetLane.cards.length})`; } else { @@ -140,7 +152,6 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr } } - // Return the updated lanes return { lanes: updatedLanes }; }); }, diff --git a/client/src/contexts/SocketIO/useSocket.js b/client/src/contexts/SocketIO/useSocket.js index f60f1b95f..9d5b2eea1 100644 --- a/client/src/contexts/SocketIO/useSocket.js +++ b/client/src/contexts/SocketIO/useSocket.js @@ -28,20 +28,26 @@ const useSocket = (bodyshop) => { setSocket(socketInstance); - socketInstance.on("connect", () => { + // When the socket connects or reconnects, join the bodyshop room + const joinRoomOnConnect = () => { console.log("Socket connected:", socketInstance.id); setClientId(socketInstance.id); if (bodyshop.id) { - socketInstance.on("bodyshop-message", handleBodyshopMessage); socketInstance.emit("join-bodyshop-room", bodyshop.id); + console.log(`Joined bodyshop room: ${bodyshop.id}`); } - }); + }; + + // Set up the necessary socket event handlers + socketInstance.on("connect", joinRoomOnConnect); socketInstance.on("reconnect", (attempt) => { console.log(`Socket reconnected after ${attempt} attempts`); }); + socketInstance.on("bodyshop-message", handleBodyshopMessage); + socketInstance.on("connect_error", (err) => { console.error("Socket connection error:", err); }); @@ -50,15 +56,13 @@ const useSocket = (bodyshop) => { console.log("Socket disconnected"); }); - if (bodyshop?.id) { - return () => { - socketInstance.emit("leave-bodyshop-room", bodyshop.id); - socketInstance.off("bodyshop-message", handleBodyshopMessage); - socketInstance.disconnect(); - }; - } - + // Clean up on component unmount or when bodyshop changes return () => { + if (bodyshop?.id) { + socketInstance.emit("leave-bodyshop-room", bodyshop.id); + } + socketInstance.off("connect", joinRoomOnConnect); + socketInstance.off("bodyshop-message", handleBodyshopMessage); socketInstance.disconnect(); }; } @@ -69,24 +73,3 @@ const useSocket = (bodyshop) => { }; export default useSocket; - -// useEffect(() => { -// if (socket && bodyshop && bodyshop.id) { -// const handleConnect = () => { -// socket.emit("join-bodyshop-room", bodyshop.id); -// }; -// -// const handleBodyshopMessage = (message) => { -// console.log(`Received message for bodyshop ${bodyshop.id}:`, message); -// }; -// -// socket.on("connect", handleConnect); -// socket.on("bodyshop-message", handleBodyshopMessage); -// -// return () => { -// socket.emit("leave-bodyshop-room", bodyshop.id); -// socket.off("connect", handleConnect); -// socket.off("bodyshop-message", handleBodyshopMessage); -// }; -// } -// }, [socket, bodyshop]); diff --git a/server.js b/server.js index 98e20fa9d..beb242505 100644 --- a/server.js +++ b/server.js @@ -210,7 +210,8 @@ const applyRedisHelpers = (pubClient, app) => { getMultipleSessionData, setMultipleFromArraySessionData, addItemToEndOfList, - addItemToBeginningOfList + addItemToBeginningOfList, + pubClient }); app.use((req, res, next) => { @@ -222,7 +223,8 @@ const applyRedisHelpers = (pubClient, app) => { getMultipleSessionData, setMultipleFromArraySessionData, addItemToEndOfList, - addItemToBeginningOfList + addItemToBeginningOfList, + pubClient }; next(); }); diff --git a/server/web-sockets/web-socket.js b/server/web-sockets/web-socket.js index a2018de67..68eb8dd0b 100644 --- a/server/web-sockets/web-socket.js +++ b/server/web-sockets/web-socket.js @@ -3,7 +3,14 @@ require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); -const { io, setSessionData, clearSessionData, getMultipleSessionData, addItemToEndOfList } = require("../../server"); +const { + io, + setSessionData, + clearSessionData, + getMultipleSessionData, + addItemToEndOfList, + pubClient +} = require("../../server"); const { admin } = require("../firebase/firebase-handler"); const logger = require("../utils/logger"); @@ -130,15 +137,35 @@ function registerPbsApEvents(socket) { }); } +const getRedisKeyForSocket = (socketId) => `socket:${socketId}:rooms`; + // Room management and broadcasting events function registerRoomAndBroadcastEvents(socket) { + // Rejoin rooms on reconnect + pubClient.lRange(getRedisKeyForSocket(socket.id), 0, -1, (err, rooms) => { + if (rooms && rooms.length > 0) { + rooms.forEach((room) => { + socket.join(room); + createLogEvent(socket, "DEBUG", `Client rejoined bodyshop room: ${room}`); + }); + } + }); + socket.on("join-bodyshop-room", async (bodyshopUUID) => { socket.join(bodyshopUUID); + + // Store room in Redis + pubClient.rPush(getRedisKeyForSocket(socket.id), bodyshopUUID); + await createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`); }); socket.on("leave-bodyshop-room", async (bodyshopUUID) => { socket.leave(bodyshopUUID); + + // Remove room from Redis + pubClient.lRem(getRedisKeyForSocket(socket.id), 0, bodyshopUUID); + await createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`); }); @@ -146,8 +173,12 @@ function registerRoomAndBroadcastEvents(socket) { io.to(bodyshopUUID).emit("bodyshop-message", message); await createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`); }); -} + socket.on("disconnect", () => { + // Optional: Cleanup Redis entry on disconnect if needed + createLogEvent(socket, "DEBUG", `Client disconnected: ${socket.id}`); + }); +} // DMS session clearing event function registerDmsClearSessionEvent(socket) { socket.on("clear-dms-session", async () => {