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:
parent
c65cd8242b
commit
94b5bbbce1
3 changed files with 77 additions and 56 deletions
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
51
kds/kds.js
51
kds/kds.js
|
|
@ -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>
|
||||||
|
|
|
||||||
Reference in a new issue