KDS: per-station line item filtering with expand toggle

Backend returns all line items for every order (removes station
filter from sub-query). Frontend filters by station, showing only
relevant items by default. An expand toggle reveals other stations'
items dimmed at 35% opacity for full order context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-03-02 15:30:59 -08:00
parent c65cd8242b
commit 94b5bbbce1
3 changed files with 77 additions and 56 deletions

View file

@ -110,57 +110,28 @@
<cfset orders = []> <cfset orders = []>
<cfloop query="qOrders"> <cfloop query="qOrders">
<!--- Get line items for this order ---> <!--- Get all line items for this order (frontend handles station filtering) --->
<!--- If filtering by station, only show items for that station (plus modifiers) ---> <cfset qLineItems = queryTimed("
<cfif StationID GT 0> SELECT
<cfset qLineItems = queryTimed(" oli.ID,
SELECT oli.ParentOrderLineItemID,
oli.ID, oli.ItemID,
oli.ParentOrderLineItemID, oli.Price,
oli.ItemID, oli.Quantity,
oli.Price, oli.Remark,
oli.Quantity, oli.IsDeleted,
oli.Remark, i.Name,
oli.IsDeleted, i.ParentItemID,
i.Name, i.IsCheckedByDefault,
i.ParentItemID, i.StationID,
i.IsCheckedByDefault, parent.Name AS ItemParentName
i.StationID, FROM OrderLineItems oli
parent.Name AS ItemParentName INNER JOIN Items i ON i.ID = oli.ItemID
FROM OrderLineItems oli LEFT JOIN Items parent ON parent.ID = i.ParentItemID
INNER JOIN Items i ON i.ID = oli.ItemID WHERE oli.OrderID = ?
LEFT JOIN Items parent ON parent.ID = i.ParentItemID AND oli.IsDeleted = b'0'
WHERE oli.OrderID = ? ORDER BY oli.ID
AND oli.IsDeleted = b'0' ", [ { value = qOrders.ID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
AND (i.StationID = ? OR i.StationID = 0 OR i.StationID IS NULL OR oli.ParentOrderLineItemID > 0)
ORDER BY oli.ID
", [
{ value = qOrders.ID, cfsqltype = "cf_sql_integer" },
{ value = StationID, cfsqltype = "cf_sql_integer" }
], { datasource = "payfrit" })>
<cfelse>
<cfset qLineItems = queryTimed("
SELECT
oli.ID,
oli.ParentOrderLineItemID,
oli.ItemID,
oli.Price,
oli.Quantity,
oli.Remark,
oli.IsDeleted,
i.Name,
i.ParentItemID,
i.IsCheckedByDefault,
i.StationID,
parent.Name AS ItemParentName
FROM OrderLineItems oli
INNER JOIN Items i ON i.ID = oli.ItemID
LEFT JOIN Items parent ON parent.ID = i.ParentItemID
WHERE oli.OrderID = ?
AND oli.IsDeleted = b'0'
ORDER BY oli.ID
", [ { value = qOrders.ID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
</cfif>
<cfset lineItems = []> <cfset lineItems = []>
<cfloop query="qLineItems"> <cfloop query="qLineItems">

View file

@ -79,6 +79,13 @@
.station-btn.all-stations:hover { border-color: #3b82f6; background: #001a2a; } .station-btn.all-stations:hover { border-color: #3b82f6; background: #001a2a; }
.station-btn svg { width: 24px; height: 24px; opacity: 0.5; } .station-btn svg { width: 24px; height: 24px; opacity: 0.5; }
.station-name-display { font-size: 11px; color: #555; margin-left: 8px; text-transform: uppercase; letter-spacing: 1px; } .station-name-display { font-size: 11px; color: #555; margin-left: 8px; text-transform: uppercase; letter-spacing: 1px; }
/* Expand toggle */
.expand-toggle { background: none; border: 1px solid #333; color: #555; padding: 4px 10px; border-radius: 4px; font-size: 11px; cursor: pointer; margin-bottom: 10px; width: 100%; transition: all 0.2s; }
.expand-toggle:hover { border-color: #555; color: #888; }
/* Other-station items (dimmed) */
.line-item.other-station { opacity: 0.35; }
</style> </style>
</head> </head>
<body> <body>
@ -109,6 +116,6 @@
</div> </div>
</div> </div>
<script src="kds.js?v=5"></script> <script src="kds.js?v=6"></script>
</body> </body>
</html> </html>

View file

@ -12,6 +12,7 @@ let config = {
let orders = []; let orders = [];
let stations = []; let stations = [];
let refreshTimer = null; let refreshTimer = null;
let expandedOrders = new Set();
// Status ID mapping // Status ID mapping
const STATUS = { NEW: 1, PREPARING: 2, READY: 3, COMPLETED: 4 }; const STATUS = { NEW: 1, PREPARING: 2, READY: 3, COMPLETED: 4 };
@ -283,12 +284,52 @@ function renderOrders() {
grid.innerHTML = orders.map(order => renderOrder(order)).join(''); grid.innerHTML = orders.map(order => renderOrder(order)).join('');
} }
// Check if a root item belongs to the current station
function isStationItem(item) {
if (!config.stationId || config.stationId === 0) return true;
const sid = parseInt(item.StationID) || 0;
return sid === config.stationId || sid === 0;
}
// Toggle expand/collapse for an order
function toggleExpand(orderId) {
if (expandedOrders.has(orderId)) {
expandedOrders.delete(orderId);
} else {
expandedOrders.add(orderId);
}
renderOrders();
}
// Render single order card // Render single order card
function renderOrder(order) { function renderOrder(order) {
const statusClass = getStatusClass(order.StatusID); const statusClass = getStatusClass(order.StatusID);
const elapsedTime = getElapsedTime(order.SubmittedOn); const elapsedTime = getElapsedTime(order.SubmittedOn);
const timeClass = getTimeClass(elapsedTime); const timeClass = getTimeClass(elapsedTime);
const rootItems = order.LineItems.filter(item => item.ParentOrderLineItemID === 0); const allRootItems = order.LineItems.filter(item => item.ParentOrderLineItemID === 0);
const isFiltering = config.stationId && config.stationId > 0;
const isExpanded = expandedOrders.has(order.OrderID);
// Split into station items and other-station items
const stationItems = isFiltering ? allRootItems.filter(i => isStationItem(i)) : allRootItems;
const otherItems = isFiltering ? allRootItems.filter(i => !isStationItem(i)) : [];
const hasOtherItems = otherItems.length > 0;
// Build expand toggle button
let expandToggle = '';
if (isFiltering && hasOtherItems) {
if (isExpanded) {
expandToggle = `<button class="expand-toggle" onclick="toggleExpand(${order.OrderID})">Hide ${otherItems.length} other item${otherItems.length > 1 ? 's' : ''}</button>`;
} else {
expandToggle = `<button class="expand-toggle" onclick="toggleExpand(${order.OrderID})">Show ${otherItems.length} other item${otherItems.length > 1 ? 's' : ''}</button>`;
}
}
// Render line items: station items always, other items only when expanded
let lineItemsHtml = stationItems.map(item => renderLineItem(item, order.LineItems, false)).join('');
if (isExpanded && hasOtherItems) {
lineItemsHtml += otherItems.map(item => renderLineItem(item, order.LineItems, true)).join('');
}
return ` return `
<div class="order-card ${statusClass}"> <div class="order-card ${statusClass}">
@ -309,8 +350,9 @@ function renderOrder(order) {
${order.Remarks ? `<div class="order-remarks">Note: ${escapeHtml(order.Remarks)}</div>` : ''} ${order.Remarks ? `<div class="order-remarks">Note: ${escapeHtml(order.Remarks)}</div>` : ''}
<div class="line-items"> <div class="line-items">
${rootItems.map(item => renderLineItem(item, order.LineItems)).join('')} ${lineItemsHtml}
</div> </div>
${expandToggle}
<div class="action-buttons"> <div class="action-buttons">
${renderActionButtons(order)} ${renderActionButtons(order)}
@ -320,11 +362,12 @@ function renderOrder(order) {
} }
// Render line item with modifiers // Render line item with modifiers
function renderLineItem(item, allItems) { function renderLineItem(item, allItems, isOtherStation = false) {
const modifiers = allItems.filter(mod => mod.ParentOrderLineItemID === item.OrderLineItemID); const modifiers = allItems.filter(mod => mod.ParentOrderLineItemID === item.OrderLineItemID);
console.log(`Item: ${item.Name} (ID: ${item.OrderLineItemID}) has ${modifiers.length} direct modifiers:`, modifiers.map(m => m.Name)); console.log(`Item: ${item.Name} (ID: ${item.OrderLineItemID}) has ${modifiers.length} direct modifiers:`, modifiers.map(m => m.Name));
const otherClass = isOtherStation ? ' other-station' : '';
return ` return `
<div class="line-item"> <div class="line-item${otherClass}">
<div class="line-item-main"> <div class="line-item-main">
<div class="item-name">${escapeHtml(item.Name)}</div> <div class="item-name">${escapeHtml(item.Name)}</div>
<div class="item-qty">x${item.Quantity}</div> <div class="item-qty">x${item.Quantity}</div>