Portal: - New business portal UI (portal/index.html, portal.css, portal.js) - Dashboard with real-time stats (orders today, revenue, pending, menu items) - Business info endpoint (api/businesses/get.cfm) - Portal stats endpoint (api/portal/stats.cfm) - Menu page links to existing full-featured menu editor Stripe Connect: - Onboarding endpoint (api/stripe/onboard.cfm) - Status check endpoint (api/stripe/status.cfm) - Payment intent creation (api/stripe/createPaymentIntent.cfm) - Webhook handler (api/stripe/webhook.cfm) Beacon APIs: - List all beacons (api/beacons/list_all.cfm) - Get business from beacon (api/beacons/getBusinessFromBeacon.cfm) Task System: - List pending tasks (api/tasks/listPending.cfm) - Accept task (api/tasks/accept.cfm) Other: - HUD interface for quick order status display - KDS debug/test pages - Updated Application.cfm with public endpoint allowlist - Order status check improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
225 lines
6.2 KiB
HTML
225 lines
6.2 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>KDS Modifier Test</title>
|
||
<link rel="stylesheet" href="index.html">
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||
background: #1a1a1a;
|
||
color: #fff;
|
||
padding: 20px;
|
||
}
|
||
|
||
.order-card {
|
||
background: #2a2a2a;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
max-width: 600px;
|
||
margin: 20px auto;
|
||
}
|
||
|
||
.line-item {
|
||
margin-bottom: 15px;
|
||
padding: 12px;
|
||
background: #1a1a1a;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.line-item-main {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.item-name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
flex: 1;
|
||
}
|
||
|
||
.item-qty {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #3b82f6;
|
||
margin-left: 10px;
|
||
min-width: 30px;
|
||
text-align: right;
|
||
}
|
||
|
||
.modifiers {
|
||
margin-left: 15px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.modifier {
|
||
font-size: 14px;
|
||
color: #aaa;
|
||
margin-bottom: 4px;
|
||
padding-left: 12px;
|
||
border-left: 2px solid #3a3a3a;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1 style="text-align: center; margin-bottom: 20px;">Modifier Rendering Test</h1>
|
||
|
||
<div id="output"></div>
|
||
|
||
<script>
|
||
// Mock data with nested modifiers
|
||
const mockOrder = {
|
||
"OrderID": 999,
|
||
"OrderStatusID": 1,
|
||
"LineItems": [
|
||
// Root item: Burger
|
||
{
|
||
"OrderLineItemID": 1000,
|
||
"OrderLineItemParentOrderLineItemID": 0,
|
||
"ItemName": "Custom Burger",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 12.99
|
||
},
|
||
// Level 1 modifier: Add Cheese
|
||
{
|
||
"OrderLineItemID": 1001,
|
||
"OrderLineItemParentOrderLineItemID": 1000,
|
||
"ItemName": "Add Cheese",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 1.50
|
||
},
|
||
// Level 2 modifier: Extra Cheese (child of Add Cheese)
|
||
{
|
||
"OrderLineItemID": 1002,
|
||
"OrderLineItemParentOrderLineItemID": 1001,
|
||
"ItemName": "Extra Cheese",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 0.50
|
||
},
|
||
// Level 3 modifier: Melt Well Done (child of Extra Cheese)
|
||
{
|
||
"OrderLineItemID": 1003,
|
||
"OrderLineItemParentOrderLineItemID": 1002,
|
||
"ItemName": "Melt Well Done",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 0.00
|
||
},
|
||
// Level 1 modifier: Add Bacon
|
||
{
|
||
"OrderLineItemID": 1004,
|
||
"OrderLineItemParentOrderLineItemID": 1000,
|
||
"ItemName": "Add Bacon",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 2.00
|
||
},
|
||
// Level 2 modifier: Crispy (child of Add Bacon)
|
||
{
|
||
"OrderLineItemID": 1005,
|
||
"OrderLineItemParentOrderLineItemID": 1004,
|
||
"ItemName": "Crispy",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 0.00
|
||
},
|
||
// Another root item: Fries
|
||
{
|
||
"OrderLineItemID": 2000,
|
||
"OrderLineItemParentOrderLineItemID": 0,
|
||
"ItemName": "French Fries",
|
||
"OrderLineItemQuantity": 2,
|
||
"OrderLineItemPrice": 3.99
|
||
},
|
||
// Level 1 modifier for fries: Extra Salt
|
||
{
|
||
"OrderLineItemID": 2001,
|
||
"OrderLineItemParentOrderLineItemID": 2000,
|
||
"ItemName": "Extra Salt",
|
||
"OrderLineItemQuantity": 1,
|
||
"OrderLineItemPrice": 0.00
|
||
}
|
||
]
|
||
};
|
||
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// Render all modifiers (flattened, no nested wrappers)
|
||
function renderAllModifiers(modifiers, allItems) {
|
||
let html = '<div class="modifiers">';
|
||
modifiers.forEach(mod => {
|
||
html += renderModifierRecursive(mod, allItems, 0);
|
||
});
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
|
||
// Render modifier recursively with indentation
|
||
function renderModifierRecursive(modifier, allItems, level) {
|
||
const subModifiers = allItems.filter(mod => mod.OrderLineItemParentOrderLineItemID === modifier.OrderLineItemID);
|
||
const indent = level * 20;
|
||
|
||
console.log(`Level ${level}: ${modifier.ItemName} (ID: ${modifier.OrderLineItemID}), Sub-modifiers: ${subModifiers.length}`);
|
||
|
||
let html = `<div class="modifier" style="padding-left: ${indent}px;">+ ${escapeHtml(modifier.ItemName)}</div>`;
|
||
|
||
// Recursively render sub-modifiers
|
||
if (subModifiers.length > 0) {
|
||
subModifiers.forEach(sub => {
|
||
html += renderModifierRecursive(sub, allItems, level + 1);
|
||
});
|
||
}
|
||
|
||
return html;
|
||
}
|
||
|
||
// Render line item with modifiers
|
||
function renderLineItem(item, allItems) {
|
||
const modifiers = allItems.filter(mod => mod.OrderLineItemParentOrderLineItemID === item.OrderLineItemID);
|
||
|
||
console.log(`Item: ${item.ItemName}, ID: ${item.OrderLineItemID}, Direct modifiers: ${modifiers.length}`);
|
||
|
||
return `
|
||
<div class="line-item">
|
||
<div class="line-item-main">
|
||
<div class="item-name">${escapeHtml(item.ItemName)}</div>
|
||
<div class="item-qty">×${item.OrderLineItemQuantity}</div>
|
||
</div>
|
||
${modifiers.length > 0 ? renderAllModifiers(modifiers, allItems) : ''}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Render the test order
|
||
function renderTest() {
|
||
const rootItems = mockOrder.LineItems.filter(item => item.OrderLineItemParentOrderLineItemID === 0);
|
||
|
||
const html = `
|
||
<div class="order-card">
|
||
<h2>Order #${mockOrder.OrderID}</h2>
|
||
<div style="margin-top: 20px;">
|
||
${rootItems.map(item => renderLineItem(item, mockOrder.LineItems)).join('')}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById('output').innerHTML = html;
|
||
}
|
||
|
||
// Run test on load
|
||
console.log('=== KDS Modifier Test ===');
|
||
console.log('Mock order:', mockOrder);
|
||
renderTest();
|
||
</script>
|
||
</body>
|
||
</html>
|