AkurAI Build
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',
  },
};