System Events

These are built-in Socket.IO events.
EventDirectionDescription
connectINBOUNDFired when connection is established
disconnectINBOUNDFired when connection is lost
errorINBOUNDFired on system error

Room Events

Client sends these to join or leave rooms. All room events support an acknowledgment callback that returns { ok: true, room: "..." }.

Join Events

socket.emit('join_dashboard', (ack) => {
  console.log(ack); // { ok: true, room: "dashboard" }
});
EventDirectionRoom joinedDescription
join_meOUTBOUNDuser:<id>Join personal room
join_dashboardOUTBOUNDdashboardJoin dashboard page
join_inventoryOUTBOUNDinventoryJoin inventory page
join_stationOUTBOUNDstationJoin station page
join_logOUTBOUNDlogJoin material log page
join_requestOUTBOUNDrequestJoin request page
join_withdrawalOUTBOUNDwithdrawalJoin withdrawal page
join_orderOUTBOUNDorderJoin order page
join_claimOUTBOUNDclaimJoin claim page
join_paneOUTBOUNDpaneJoin pane page
join_productionOUTBOUNDproductionJoin production tracking page
join_pricingOUTBOUNDpricingJoin pricing settings page
join_station_roomOUTBOUNDstation:<stationId>Join a specific station’s work view
leave_station_roomOUTBOUNDstation:<stationId>Leave a specific station’s work view

Station Room Events

Join or leave a specific station’s room to receive real-time check-in/check-out events for that station only.
socket.emit('join_station_room', { stationId: '69a98ee25bb1d35efb8cc1ff' }, (ack) => {
  console.log(ack); // { ok: true, room: "station:69a98ee..." }
});
EventDirectionRoom joined/leftDescription
join_station_roomOUTBOUNDstation:<stationId>Join a specific station’s room
leave_station_roomOUTBOUNDstation:<stationId>Leave a specific station’s room
Payload: { stationId: "<station _id>" }
These are dynamic rooms scoped to individual stations, unlike join_station which joins a single shared room for the stations page. stationId is required — the callback returns { ok: false, error: "stationId is required" } if omitted.

QR Check-in Events

Used by the QR check-in system for worker presence at stations. The station screen joins with join-station and receives scan-confirmed when a mobile device emits mobile-scan.
socket.emit('join-station', 'cutting', (ack) => {
  console.log(ack); // { ok: true, room: "station:cutting" }
});

socket.on('scan-confirmed', ({ worker, time }) => {
  console.log(`${worker} checked in at ${time}`);
});
EventDirectionDescription
join-stationOUTBOUNDStation screen joins station:<stationId>. Receives replayed scan-confirmed if a scan occurred within the last 15 seconds.
mobile-scanOUTBOUNDMobile sends { stationId, worker? }. Server broadcasts scan-confirmed to the station room. worker defaults to the authenticated user’s name if omitted.
scan-confirmedINBOUNDSent to station:<stationId> with { worker, time } when a worker checks in.
join-station uses a hyphenated name (not underscore) to distinguish it from join_station which joins the shared stations page room. Both require JWT authentication.

Leave Events

socket.emit('leave_dashboard', (ack) => {
  console.log(ack); // { ok: true, room: "dashboard" }
});
EventDirectionRoom leftDescription
leave_dashboardOUTBOUNDdashboardLeave dashboard page
leave_inventoryOUTBOUNDinventoryLeave inventory page
leave_stationOUTBOUNDstationLeave station page
leave_logOUTBOUNDlogLeave material log page
leave_requestOUTBOUNDrequestLeave request page
leave_withdrawalOUTBOUNDwithdrawalLeave withdrawal page
leave_orderOUTBOUNDorderLeave order page
leave_claimOUTBOUNDclaimLeave claim page
leave_paneOUTBOUNDpaneLeave pane page
leave_productionOUTBOUNDproductionLeave production tracking page
leave_pricingOUTBOUNDpricingLeave pricing settings page
leave_station_roomOUTBOUNDstation:<stationId>Leave a specific station’s work view

Data Events

These events are emitted by the server when data changes via the REST API. You receive them only if you’ve joined the corresponding room.

Resource Updated Events

All resource events include an action field (created, updated, or deleted) and a data field with the affected record.
socket.on('order:updated', (payload) => {
  console.log(payload.action); // "created" | "updated" | "deleted"
  console.log(payload.data);   // the order object
});
EventDirectionEmitted to roomsTriggered by
material:updatedINBOUNDdashboard, inventoryMaterial create / update / delete
inventory:updatedINBOUNDdashboard, inventoryInventory create / update / delete / move; Withdrawal approve / reject
order:updatedINBOUNDdashboard, orderOrder create / update / delete; QR scan scan_out or qc_pass; qc_fail remake / paneCount
log:updatedINBOUNDdashboard, logMaterial log create / update / delete; QR scan (all actions)
request:updatedINBOUNDdashboard, requestRequest create / update / delete
withdrawal:updatedINBOUNDdashboard, withdrawalWithdrawal create / update / delete. Payload data is the withdrawal document (includes withdrawalNumber)
claim:updatedINBOUNDdashboard, claimClaim create / update / delete
station:updatedINBOUNDdashboard, stationStation create / update / delete
pane:updatedINBOUNDdashboard, pane, production, station:<name>Pane create / update / delete; QR scan (all actions)
production-log:updatedINBOUNDdashboard, productionProduction log create / update / delete
station-template:updatedINBOUNDdashboard, stationStation template create / update / delete
sticker-template:updatedINBOUNDdashboardSticker template create / update / delete
jobType:updatedINBOUNDdashboardJob type create / update / delete
pricing:updatedINBOUNDpricingPricing settings update
station:check_inINBOUNDstation:<stationId>Order update with station changes
station:pane_arrivedINBOUNDstation:<nextStation>, stationQR scan with scan_out or qc_pass (pane advances); remake queue (isRemake: true)
laminate:readyINBOUNDdashboard, pane, production, station:<id>Sheet scan_in at lamination station (all sheets present)
laminate:waitingINBOUNDstation:<id>Sheet scan_in at lamination station (sheets still missing)
pane:laminatedINBOUNDdashboard, pane, production, station:<id>laminate scan — survivor pane + retired sheet numbers
Payload format:
{
  "action": "created",
  "data": {
    "_id": "69a98ee25bb1d35efb8cc1ff",
    "...": "..."
  }
}

Action values

Most events use created, updated, or deleted. Some events have additional action values:
EventExtra actionsDescription
pane:updatedscannedEmitted on every QR scan action (scan_in, start, complete, scan_out, qc_pass, laminate)
pane:updatedqc_failedEmitted on qc_fail with the defected pane payload
pane:updatedcreatedEmitted when a remake pane is created (qc_fail auto-remake or claim-approved remake)
inventory:updatedadjustedEmitted when a withdrawal is approved/rejected (inventory quantity changes)
inventory:updatedmovedEmitted on inventory move/split. Payload: { action: "moved", data: { source, target, quantity } }
log:updatedpane_scannedEmitted on every QR scan. Payload: { action: "pane_scanned", data: { paneLog, material } }

Station Check-In

Emitted to a specific station’s room when an order enters or exits that station (triggered by order updates that modify stationHistory or currentStationIndex).
socket.on('station:check_in', (payload) => {
  console.log(payload.orderId);   // "69a98ee25bb1d35efb8cc1ff"
  console.log(payload.stationId); // "69b12cd35bb1d35efb8cc200"
  console.log(payload.action);    // "entered" | "exited"
});
EventDirectionEmitted toTriggered by
station:check_inINBOUNDstation:<stationId>Order update with station changes
Payload:
{
  "orderId": "69a98ee25bb1d35efb8cc1ff",
  "stationId": "69b12cd35bb1d35efb8cc200",
  "action": "entered"
}
FieldDescription
orderIdThe order that moved
stationIdThe station where it happened
actionentered (order arrived) or exited (order completed/left)

Pane Arrived (QR Scan)

Emitted when a pane is scanned with scan_out or qc_pass and advances to the next station. Also emitted when a remake pane is queued at a station (payload may include isRemake: true). Sent to the next station’s room and the general station room.
socket.on('station:pane_arrived', (payload) => {
  console.log(payload.paneNumber);  // "PNE-0001"
  console.log(payload.fromStation); // "cutting"
  console.log(payload.toStation);   // "edging"
  console.log(payload.orderId);     // "69a98ee25bb1d35efb8cc1ff"
});
EventDirectionEmitted toTriggered by
station:pane_arrivedINBOUNDstation:<nextStation>, stationPOST /api/panes/{paneNumber}/scan with action: "scan_out" or "qc_pass"; remake arrival
Payload:
{
  "paneNumber": "PNE-0001",
  "paneId": "69a98ee25bb1d35efb8cc1ff",
  "fromStation": "cutting",
  "toStation": "edging",
  "orderId": "69b12cd35bb1d35efb8cc200"
}
FieldDescription
paneNumberThe pane that moved (e.g. PNE-0001)
paneIdThe pane’s _id
fromStationThe station the pane just completed
toStationThe station the pane moved to
orderIdThe associated order ID (if any)

Pane Arrived (Station Notification)

In addition to station:pane_arrived, the scan endpoint also emits a notification event directly to the next station’s room when a pane moves via scan_out or qc_pass, and when a QC remake pane arrives at a station. This allows station screens to show an alert.
socket.on('notification', (payload) => {
  if (payload.type === 'pane_arrived') {
    console.log(payload.title);   // "มีกระจกเข้าสถานี"
    console.log(payload.message); // "กระจก PNE-0001 เข้าสถานีนี้แล้ว"
  }
});
EventDirectionEmitted toTriggered by
notificationINBOUNDstation:<nextStation>QR scan scan_out / qc_pass (pane moves); QC remake arrival
Payload:
{
  "type": "pane_arrived",
  "title": "มีกระจกเข้าสถานี",
  "message": "กระจก PNE-0001 เข้าสถานีนี้แล้ว"
}

Laminate Pairing Events

Emitted during the laminate flow when child sheets arrive at a lamination station and when laminate runs (one survivor sheet keeps its QR; see Laminate Merge).

laminate:ready

Emitted when all active child sheets have arrived at the lamination station. The frontend can use this to enable the “merge” button.
socket.on('laminate:ready', (payload) => {
  console.log(payload.parentPaneNumber); // "PNE-0100"
  console.log(payload.sheetsPresent);    // 2
  console.log(payload.sheetsTotal);      // 2
});
EventDirectionEmitted toTriggered by
laminate:readyINBOUNDdashboard, pane, production, station:<stationId>scan_in of the last active sheet at its lamination station
Payload:
FieldDescription
parentPaneNumberThe parent pane number (e.g. PNE-0100)
parentPaneIdThe parent pane’s _id
sheetsPresentNumber of sheets currently at the station
sheetsTotalTotal active sheets expected

laminate:waiting

Emitted when a sheet arrives at the lamination station but other sheets are still missing.
socket.on('laminate:waiting', (payload) => {
  console.log(`${payload.sheetsPresent}/${payload.sheetsTotal} sheets arrived`);
});
EventDirectionEmitted toTriggered by
laminate:waitingINBOUNDstation:<stationId>scan_in of a sheet at its lamination station (when siblings are still missing)
Payload: Same shape as laminate:ready.

pane:laminated

Emitted when the laminate scan merges sheets: one sheet survives with the same QR; the dormant parent and other sheets are retired (merged_into).
socket.on('pane:laminated', (payload) => {
  console.log((payload.survivor || payload.parent).paneNumber); // survivor QR
  console.log(payload.sheets);            // ["PNE-0100-A", "PNE-0100-B"]
});
EventDirectionEmitted toTriggered by
pane:laminatedINBOUNDdashboard, pane, production, station:<stationId>POST /api/panes/{paneNumber}/scan with action: "laminate"
Payload:
FieldDescription
survivorThe surviving pane (populated), awaiting_scan_out at lamination, laminateRole: "single"
parentAlias of survivor (backwards compatibility)
retiredPaneNumbersPane numbers retired to merged_into (non-survivor sheets and the dormant parent’s number)
sheetsArray of merged sheet pane numbers (e.g. ["PNE-0100-A", "PNE-0100-B"])

Pricing Updated

Emitted to the pricing room when pricing settings are updated via PUT /api/pricing-settings.
socket.on('pricing:updated', (settings) => {
  console.log(settings.glassPrices);
  console.log(settings.holePriceEach);
  console.log(settings.notchPrice);
});
EventDirectionEmitted toTriggered by
pricing:updatedINBOUNDpricingPUT /api/pricing-settings
Payload: The full pricing settings object (same as GET response).

Order Arrived (Station Notification)

Emitted to a specific station’s room when a new order is created with stations, or when an existing order advances to a new station. This notifies the station screen that work is incoming.
socket.on('notification', (payload) => {
  if (payload.type === 'order_arrived') {
    console.log(payload.title);   // "มีออเดอร์ใหม่เข้า" or "มีออเดอร์เข้า"
    console.log(payload.message); // "ออเดอร์ ORD-0001 เข้าสถานีนี้แล้ว"
  }
});
EventDirectionEmitted toTriggered by
notificationINBOUNDstation:<stationId>Order create (first station) or order update (station advancement)
Payload:
{
  "type": "order_arrived",
  "title": "มีออเดอร์ใหม่เข้า",
  "message": "ออเดอร์ ORD-0001 เข้าสถานีนี้แล้ว",
  "referenceId": "69a98ee25bb1d35efb8cc1ff",
  "referenceType": "Order",
  "priority": "high",
  "readStatus": false
}
FieldDescription
typeAlways order_arrived
titleThai text — “มีออเดอร์ใหม่เข้า” (new order) or “มีออเดอร์เข้า” (advanced order)
messageIncludes the order number
referenceIdThe order’s _id
referenceTypeAlways Order
priorityAlways high
This notification is emitted directly to the station room (not to user:<id>), so it’s received by any client that has joined station:<stationId> via join_station_room or join-station.

Notification

Sent to a specific user’s personal room when a notification is created via the REST API or the QR scan endpoint.
socket.on('notification', (notification) => {
  console.log(notification.title);
  console.log(notification.message);
});
EventDirectionEmitted toTriggered by
notificationINBOUNDuser:<recipientId>POST /api/notifications; QR scan scan_out / qc_pass; qc_remake after qc_fail when the order has assignedTo
Payload: The full populated notification object.

System Alert

Broadcast to all connected clients. Only admin users can emit this event. Non-admin users will receive an error.
// Sending (admin only)
socket.emit('system_alert', { message: 'Server maintenance in 10 minutes' });

// Receiving (all users)
socket.on('system_alert', (data) => {
  console.log(data.message);
});
EventDirectionEmitted toTriggered by
system_alertINBOUNDAll connected clientsAdmin socket client only
If a non-admin user attempts to emit system_alert, they will receive an error event with { message: "Not authorized to send system alerts" }.

Full Example

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  path: '/api/socket-entry',
  auth: { token: 'your-jwt-token' },
});

socket.on('connect', () => {
  // Join the rooms you need
  socket.emit('join_me');
  socket.emit('join_dashboard');
  socket.emit('join_order');
});

// Listen for data changes
socket.on('order:updated', ({ action, data }) => {
  if (action === 'created') console.log('New order:', data);
  if (action === 'updated') console.log('Order changed:', data);
  if (action === 'deleted') console.log('Order removed:', data);
});

// Listen for personal notifications
socket.on('notification', (notif) => {
  console.log(`[${notif.priority}] ${notif.title}: ${notif.message}`);
});

// Listen for system alerts
socket.on('system_alert', (alert) => {
  console.log('ALERT:', alert.message);
});

// When navigating away from orders page
socket.emit('leave_order');