Menu
Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f
AkurAI-Framework / site/frontend/ai/components/progress/order_summary.js
Latest change d49e9bb8beb5dc669fe029e034a13342d358a4f3 - feat(ai): tool-ui Progress + Confirmation components (AB4-B) by Ólafur Búi Ólafsson
// Order Summary component — tool-ui Confirmation category.
// Payload: { items: [{ name, qty, price }], subtotal?, tax?, total, currency? }
// Receipt: { confirmed: true }
// Emits: CustomEvent('toolui:confirm', { detail: { confirmed: true }, bubbles: true })
function fmt(amount, currency) {
try {
return new Intl.NumberFormat(undefined, {
style: 'currency',
currency: currency ?? 'USD',
}).format(amount);
} catch {
return `${currency ?? '$'}${Number(amount).toFixed(2)}`;
}
}
export const def = {
name: 'order_summary',
category: 'Confirmation',
title: 'Order Summary',
match(payload) {
return Array.isArray(payload?.items) && payload?.total !== undefined;
},
render(payload, ctx) {
const cur = payload.currency ?? 'USD';
const root = document.createElement('div');
root.className = 'card tc-progress-order';
root.style.cssText = 'display:flex;flex-direction:column;gap:0;';
// Item rows
const table = document.createElement('table');
table.style.cssText = 'width:100%;border-collapse:collapse;font-size:14px;';
// Header
const thead = document.createElement('thead');
const hrow = document.createElement('tr');
for (const text of ['Item', 'Qty', 'Price']) {
const th = document.createElement('th');
th.textContent = text;
th.style.cssText = 'text-align:left;padding:0 0 8px;color:var(--muted);font-size:12px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;border-bottom:1px solid var(--border);';
if (text !== 'Item') th.style.textAlign = 'right';
hrow.appendChild(th);
}
thead.appendChild(hrow);
table.appendChild(thead);
// Body rows
const tbody = document.createElement('tbody');
for (const item of payload.items) {
const tr = document.createElement('tr');
const tdName = document.createElement('td');
tdName.style.cssText = 'padding:8px 0;border-bottom:1px solid var(--border);';
tdName.textContent = item.name ?? '';
const tdQty = document.createElement('td');
tdQty.style.cssText = 'padding:8px 0;border-bottom:1px solid var(--border);text-align:right;color:var(--muted);';
tdQty.textContent = String(item.qty ?? 1);
const tdPrice = document.createElement('td');
tdPrice.style.cssText = 'padding:8px 0;border-bottom:1px solid var(--border);text-align:right;';
tdPrice.textContent = fmt(item.price ?? 0, cur);
tr.appendChild(tdName);
tr.appendChild(tdQty);
tr.appendChild(tdPrice);
tbody.appendChild(tr);
}
table.appendChild(tbody);
root.appendChild(table);
// Totals section
const totals = document.createElement('div');
totals.style.cssText = 'display:flex;flex-direction:column;gap:4px;padding:12px 0 0;font-size:14px;';
function addRow(label, value, isBold) {
const row = document.createElement('div');
row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;';
const l = document.createElement('span');
l.style.color = isBold ? 'var(--fg)' : 'var(--muted)';
if (isBold) l.style.fontWeight = '700';
l.textContent = label;
const v = document.createElement('span');
if (isBold) v.style.fontWeight = '700';
v.textContent = value;
row.appendChild(l);
row.appendChild(v);
return row;
}
if (payload.subtotal !== undefined) {
totals.appendChild(addRow('Subtotal', fmt(payload.subtotal, cur), false));
}
if (payload.tax !== undefined) {
totals.appendChild(addRow('Tax', fmt(payload.tax, cur), false));
}
const divider = document.createElement('div');
divider.style.cssText = 'height:1px;background:var(--border);margin:6px 0;';
totals.appendChild(divider);
totals.appendChild(addRow('Total', fmt(payload.total, cur), true));
root.appendChild(totals);
// Confirm button
const actions = document.createElement('div');
actions.className = 'btn-row';
actions.style.marginTop = '14px';
const confirmBtn = document.createElement('button');
confirmBtn.type = 'button';
confirmBtn.className = 'btn btn-primary';
confirmBtn.textContent = 'Confirm order';
let done = false;
confirmBtn.addEventListener('click', () => {
if (done) return;
done = true;
confirmBtn.disabled = true;
const detail = { confirmed: true };
if (ctx?.sendReceipt) ctx.sendReceipt(detail);
root.dispatchEvent(new CustomEvent('toolui:confirm', { detail, bubbles: true }));
const badge = document.createElement('span');
badge.className = 'badge badge-ok';
badge.style.marginLeft = '8px';
badge.textContent = 'Confirmed';
confirmBtn.appendChild(badge);
});
actions.appendChild(confirmBtn);
root.appendChild(actions);
return root;
},
example: {
items: [
{ name: 'Widget Pro', qty: 2, price: 9.99 },
{ name: 'Gadget Plus', qty: 1, price: 24.50 },
],
subtotal: 44.48,
tax: 4.00,
total: 48.48,
currency: 'USD',
},
};