649 lines
22 KiB
JavaScript
649 lines
22 KiB
JavaScript
import { api } from "../lib/api.js";
|
|
import { on } from "../lib/bus.js";
|
|
import { navigateToItem } from "../lib/router.js";
|
|
|
|
function formatDate(dateStr) {
|
|
if (!dateStr) return "—";
|
|
const d = new Date(dateStr);
|
|
return d.toLocaleString("es-AR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
}
|
|
|
|
function statusLabel(status) {
|
|
const map = {
|
|
pending: "Pendiente",
|
|
processing: "Procesando",
|
|
"on-hold": "En espera",
|
|
completed: "Completado",
|
|
cancelled: "Cancelado",
|
|
refunded: "Reembolsado",
|
|
failed: "Fallido",
|
|
};
|
|
return map[status] || status;
|
|
}
|
|
|
|
function statusColor(status) {
|
|
const map = {
|
|
pending: "#f59e0b",
|
|
processing: "#3b82f6",
|
|
"on-hold": "#8b5cf6",
|
|
completed: "#22c55e",
|
|
cancelled: "#6b7280",
|
|
refunded: "#ec4899",
|
|
failed: "#ef4444",
|
|
};
|
|
return map[status] || "#8aa0b5";
|
|
}
|
|
|
|
class OrdersCrud extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.attachShadow({ mode: "open" });
|
|
this.orders = [];
|
|
this.selectedOrder = null;
|
|
this.loading = false;
|
|
|
|
// Paginación
|
|
this.page = 1;
|
|
this.limit = 50;
|
|
this.totalPages = 1;
|
|
this.totalOrders = 0;
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
<style>
|
|
:host {
|
|
--bg: #0b0f14;
|
|
--panel: #121823;
|
|
--muted: #8aa0b5;
|
|
--text: #e7eef7;
|
|
--line: #1e2a3a;
|
|
--blue: #1f6feb;
|
|
--green: #238636;
|
|
--red: #da3633;
|
|
--orange: #f59e0b;
|
|
}
|
|
* { box-sizing: border-box; font-family: system-ui, Segoe UI, Roboto, Arial; }
|
|
.container {
|
|
height: 100%;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
display: grid;
|
|
grid-template-columns: 1fr 400px;
|
|
gap: 16px;
|
|
padding: 16px;
|
|
overflow: hidden;
|
|
}
|
|
.panel {
|
|
background: var(--panel);
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
overflow: hidden;
|
|
}
|
|
.panel-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
border-bottom: 1px solid var(--line);
|
|
padding-bottom: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
button {
|
|
background: var(--blue);
|
|
border: none;
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: opacity 0.15s;
|
|
}
|
|
button:hover { opacity: 0.9; }
|
|
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
button.secondary {
|
|
background: transparent;
|
|
border: 1px solid var(--line);
|
|
color: var(--muted);
|
|
}
|
|
button.secondary:hover { border-color: var(--blue); color: var(--text); }
|
|
button.small { padding: 4px 8px; font-size: 11px; }
|
|
.empty { color: var(--muted); font-size: 12px; text-align: center; padding: 40px; }
|
|
|
|
/* Orders table */
|
|
.orders-table {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 12px;
|
|
}
|
|
th {
|
|
text-align: left;
|
|
padding: 10px 8px;
|
|
border-bottom: 2px solid var(--line);
|
|
color: var(--muted);
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
font-size: 10px;
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--panel);
|
|
}
|
|
td {
|
|
padding: 10px 8px;
|
|
border-bottom: 1px solid var(--line);
|
|
vertical-align: middle;
|
|
}
|
|
tr { cursor: pointer; transition: background 0.15s; }
|
|
tr:hover { background: rgba(31,111,235,0.1); }
|
|
tr.selected { background: rgba(31,111,235,0.2); }
|
|
|
|
.order-id { font-weight: 700; }
|
|
.badges { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
.badge {
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-size: 9px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
.badge.test { background: var(--orange); color: #000; }
|
|
.badge.real { background: var(--green); color: #fff; }
|
|
.badge.whatsapp { background: #25d366; color: #fff; }
|
|
.badge.web { background: var(--muted); color: #000; }
|
|
.status-badge {
|
|
padding: 3px 8px;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
display: inline-block;
|
|
}
|
|
.total { font-weight: 600; }
|
|
.customer-name {
|
|
max-width: 150px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Detail panel */
|
|
.detail-section {
|
|
margin-bottom: 16px;
|
|
}
|
|
.detail-title {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: var(--blue);
|
|
text-transform: uppercase;
|
|
margin-bottom: 8px;
|
|
}
|
|
.detail-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 6px 0;
|
|
border-bottom: 1px solid var(--line);
|
|
font-size: 12px;
|
|
}
|
|
.detail-row:last-child { border-bottom: none; }
|
|
.detail-label { color: var(--muted); }
|
|
.detail-value { font-weight: 500; }
|
|
.items-list {
|
|
background: var(--bg);
|
|
border: 1px solid var(--line);
|
|
border-radius: 6px;
|
|
padding: 8px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
}
|
|
.item-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 6px 0;
|
|
border-bottom: 1px solid var(--line);
|
|
font-size: 12px;
|
|
}
|
|
.item-row:last-child { border-bottom: none; }
|
|
.item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.item-qty { color: var(--muted); margin: 0 8px; }
|
|
.item-total { font-weight: 600; }
|
|
.detail-empty {
|
|
color: var(--muted);
|
|
font-size: 12px;
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
}
|
|
|
|
/* Paginación */
|
|
.pagination {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 0;
|
|
border-top: 1px solid var(--line);
|
|
margin-top: auto;
|
|
font-size: 12px;
|
|
}
|
|
.pagination-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.pagination-info {
|
|
color: var(--muted);
|
|
}
|
|
.pagination select {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
border: 1px solid var(--line);
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
.pagination select:hover {
|
|
border-color: var(--blue);
|
|
}
|
|
.pagination button {
|
|
padding: 4px 10px;
|
|
font-size: 11px;
|
|
}
|
|
</style>
|
|
|
|
<div class="container">
|
|
<div class="panel">
|
|
<div class="panel-title">
|
|
<span>Pedidos</span>
|
|
<button id="btnRefresh" class="secondary small">Actualizar</button>
|
|
</div>
|
|
<div class="orders-table" id="ordersTable">
|
|
<div class="empty">Cargando pedidos...</div>
|
|
</div>
|
|
<div class="pagination" id="pagination">
|
|
<div class="pagination-controls">
|
|
<span>Mostrar:</span>
|
|
<select id="limitSelect">
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
<option value="200">200</option>
|
|
</select>
|
|
</div>
|
|
<div class="pagination-controls">
|
|
<button id="btnPrev" class="secondary small" disabled>← Anterior</button>
|
|
<span class="pagination-info" id="pageInfo">Página 1 de 1</span>
|
|
<button id="btnNext" class="secondary small" disabled>Siguiente →</button>
|
|
</div>
|
|
<div class="pagination-info" id="totalInfo">0 pedidos</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="panel-title">Detalle del Pedido</div>
|
|
<div id="orderDetail">
|
|
<div class="detail-empty">Seleccioná un pedido para ver los detalles</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.shadowRoot.getElementById("btnRefresh").onclick = () => this.loadOrders();
|
|
|
|
// Paginación
|
|
this.shadowRoot.getElementById("limitSelect").onchange = (e) => {
|
|
this.limit = parseInt(e.target.value);
|
|
this.page = 1;
|
|
this.loadOrders();
|
|
};
|
|
this.shadowRoot.getElementById("btnPrev").onclick = () => {
|
|
if (this.page > 1) {
|
|
this.page--;
|
|
this.loadOrders();
|
|
}
|
|
};
|
|
this.shadowRoot.getElementById("btnNext").onclick = () => {
|
|
if (this.page < this.totalPages) {
|
|
this.page++;
|
|
this.loadOrders();
|
|
}
|
|
};
|
|
|
|
// Escuchar cambios de ruta para deep-linking
|
|
this._unsubRouter = on("router:viewChanged", ({ view, params }) => {
|
|
if (view === "orders" && params.id) {
|
|
this.selectOrderById(params.id);
|
|
}
|
|
});
|
|
|
|
// Escuchar nuevos pedidos para actualizar automáticamente
|
|
this._unsubOrderCreated = on("order:created", ({ order_id }) => {
|
|
console.log("[orders-crud] order:created received, order_id:", order_id);
|
|
this.refreshWithRetry(order_id);
|
|
});
|
|
|
|
this.loadOrders();
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this._unsubRouter?.();
|
|
this._unsubOrderCreated?.();
|
|
}
|
|
|
|
/**
|
|
* Refresca la lista de pedidos con retry si el pedido esperado no aparece
|
|
* WooCommerce puede tardar en devolver un pedido recién creado
|
|
*/
|
|
async refreshWithRetry(expectedOrderId, attempt = 1) {
|
|
const maxAttempts = 3;
|
|
const delays = [500, 2000, 4000]; // ms entre intentos
|
|
|
|
// Esperar antes de refrescar
|
|
if (attempt > 1) {
|
|
await new Promise(r => setTimeout(r, delays[attempt - 1] || 2000));
|
|
} else {
|
|
// Primer intento: pequeño delay para dar tiempo a WooCommerce
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
|
|
await this.loadOrders();
|
|
|
|
// Verificar si el pedido esperado apareció
|
|
if (expectedOrderId) {
|
|
const found = this.orders.find(o => o.id === expectedOrderId);
|
|
if (!found && attempt < maxAttempts) {
|
|
console.log(`[orders-crud] order ${expectedOrderId} not found, retry ${attempt + 1}/${maxAttempts}...`);
|
|
return this.refreshWithRetry(expectedOrderId, attempt + 1);
|
|
}
|
|
if (found) {
|
|
console.log(`[orders-crud] order ${expectedOrderId} found on attempt ${attempt}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async loadOrders() {
|
|
const container = this.shadowRoot.getElementById("ordersTable");
|
|
container.innerHTML = `<div class="empty">Cargando pedidos...</div>`;
|
|
|
|
try {
|
|
const result = await api.listOrders({ page: this.page, limit: this.limit });
|
|
this.orders = result.items || [];
|
|
|
|
// Actualizar paginación
|
|
if (result.pagination) {
|
|
this.totalPages = result.pagination.pages || 1;
|
|
this.totalOrders = result.pagination.total || 0;
|
|
}
|
|
|
|
this.renderTable();
|
|
this.updatePagination();
|
|
|
|
// Si hay un pedido pendiente de selección (deep-link), seleccionarlo
|
|
if (this._pendingOrderId) {
|
|
const order = this.orders.find(o => o.id === this._pendingOrderId);
|
|
if (order) {
|
|
this.selectOrder(order, { updateUrl: false });
|
|
}
|
|
this._pendingOrderId = null;
|
|
}
|
|
} catch (e) {
|
|
console.error("[orders-crud] Error loading orders:", e);
|
|
container.innerHTML = `<div class="empty">Error cargando pedidos: ${e.message}</div>`;
|
|
}
|
|
}
|
|
|
|
updatePagination() {
|
|
const pageInfo = this.shadowRoot.getElementById("pageInfo");
|
|
const totalInfo = this.shadowRoot.getElementById("totalInfo");
|
|
const btnPrev = this.shadowRoot.getElementById("btnPrev");
|
|
const btnNext = this.shadowRoot.getElementById("btnNext");
|
|
const limitSelect = this.shadowRoot.getElementById("limitSelect");
|
|
|
|
pageInfo.textContent = `Página ${this.page} de ${this.totalPages}`;
|
|
totalInfo.textContent = `${this.totalOrders.toLocaleString("es-AR")} pedidos`;
|
|
|
|
btnPrev.disabled = this.page <= 1;
|
|
btnNext.disabled = this.page >= this.totalPages;
|
|
|
|
limitSelect.value = String(this.limit);
|
|
}
|
|
|
|
renderTable() {
|
|
const container = this.shadowRoot.getElementById("ordersTable");
|
|
|
|
if (this.orders.length === 0) {
|
|
container.innerHTML = `<div class="empty">No hay pedidos</div>`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = `
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Tipo</th>
|
|
<th>Estado</th>
|
|
<th>Envío</th>
|
|
<th>Pago</th>
|
|
<th>Cliente</th>
|
|
<th>Total</th>
|
|
<th>Fecha</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${this.orders.map(order => {
|
|
const isSelected = this.selectedOrder?.id === order.id;
|
|
const customerName = [order.billing.first_name, order.billing.last_name].filter(Boolean).join(" ") || order.billing.phone || "—";
|
|
|
|
return `
|
|
<tr class="${isSelected ? "selected" : ""}" data-order-id="${order.id}">
|
|
<td class="order-id">${order.id}</td>
|
|
<td>
|
|
<div class="badges">
|
|
${order.is_test ? '<span class="badge test">TEST</span>' : '<span class="badge real">REAL</span>'}
|
|
${order.source === "whatsapp" ? '<span class="badge whatsapp">WA</span>' : '<span class="badge web">WEB</span>'}
|
|
</div>
|
|
</td>
|
|
<td><span class="status-badge" style="background:${statusColor(order.status)}">${statusLabel(order.status)}</span></td>
|
|
<td><span class="badge" style="background:${order.is_delivery ? '#3b82f6' : '#8b5cf6'};color:#fff">${order.is_delivery ? 'DEL' : 'RET'}</span></td>
|
|
<td>
|
|
<div class="badges">
|
|
<span class="badge" style="background:${order.is_cash ? '#f59e0b' : '#1f6feb'};color:${order.is_cash ? '#000' : '#fff'}">${order.is_cash ? '$' : 'MP'}</span>
|
|
<span class="badge" style="background:${order.is_paid ? '#22c55e' : '#ef4444'};color:#fff">${order.is_paid ? '✓' : '✗'}</span>
|
|
</div>
|
|
</td>
|
|
<td class="customer-name" title="${customerName}">${customerName}</td>
|
|
<td class="total">$${Number(order.total || 0).toLocaleString("es-AR")}</td>
|
|
<td>${formatDate(order.date_created)}</td>
|
|
</tr>
|
|
`;
|
|
}).join("")}
|
|
</tbody>
|
|
</table>
|
|
`;
|
|
|
|
container.querySelectorAll("tr[data-order-id]").forEach(row => {
|
|
row.onclick = () => {
|
|
const orderId = parseInt(row.dataset.orderId);
|
|
const order = this.orders.find(o => o.id === orderId);
|
|
if (order) {
|
|
this.selectOrder(order);
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
selectOrder(order, { updateUrl = true } = {}) {
|
|
this.selectedOrder = order;
|
|
this.renderTable();
|
|
this.renderDetail();
|
|
|
|
// Actualizar URL
|
|
if (updateUrl && order) {
|
|
navigateToItem("orders", order.id);
|
|
}
|
|
}
|
|
|
|
selectOrderById(orderId) {
|
|
const id = parseInt(orderId);
|
|
if (!id) return;
|
|
|
|
// Si ya tenemos los pedidos cargados, buscar y seleccionar
|
|
const order = this.orders.find(o => o.id === id);
|
|
if (order) {
|
|
this.selectOrder(order, { updateUrl: false });
|
|
} else {
|
|
// Guardar el ID pendiente para seleccionar después de cargar
|
|
this._pendingOrderId = id;
|
|
}
|
|
}
|
|
|
|
renderDetail() {
|
|
const container = this.shadowRoot.getElementById("orderDetail");
|
|
|
|
if (!this.selectedOrder) {
|
|
container.innerHTML = `<div class="detail-empty">Seleccioná un pedido para ver los detalles</div>`;
|
|
return;
|
|
}
|
|
|
|
const order = this.selectedOrder;
|
|
const customerName = [order.billing.first_name, order.billing.last_name].filter(Boolean).join(" ") || "—";
|
|
|
|
// Construir dirección de envío
|
|
const shippingAddr = [
|
|
order.shipping?.address_1,
|
|
order.shipping?.address_2,
|
|
order.shipping?.city,
|
|
order.shipping?.state,
|
|
order.shipping?.postcode
|
|
].filter(Boolean).join(", ");
|
|
|
|
const billingAddr = [
|
|
order.billing?.address_1,
|
|
order.billing?.address_2,
|
|
order.billing?.city,
|
|
order.billing?.state,
|
|
order.billing?.postcode
|
|
].filter(Boolean).join(", ");
|
|
|
|
const address = shippingAddr || billingAddr || "—";
|
|
|
|
container.innerHTML = `
|
|
<div class="detail-section">
|
|
<div class="detail-title">Información General</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Pedido #</span>
|
|
<span class="detail-value">${order.id}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Estado</span>
|
|
<span class="status-badge" style="background:${statusColor(order.status)}">${statusLabel(order.status)}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Tipo</span>
|
|
<span class="detail-value">${order.is_test ? "Test" : "Real"} • ${order.source === "whatsapp" ? "WhatsApp" : "Web"}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Fecha</span>
|
|
<span class="detail-value">${formatDate(order.date_created)}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Total</span>
|
|
<span class="detail-value" style="font-size:16px;color:var(--green)">$${Number(order.total || 0).toLocaleString("es-AR")} ${order.currency || ""}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detail-section">
|
|
<div class="detail-title">Envío</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Método</span>
|
|
<span class="detail-value">
|
|
<span class="badge ${order.is_delivery ? 'delivery' : 'pickup'}" style="background:${order.is_delivery ? '#3b82f6' : '#8b5cf6'};color:#fff;padding:3px 8px;border-radius:4px;font-size:10px;">
|
|
${order.is_delivery ? 'DELIVERY' : 'RETIRO'}
|
|
</span>
|
|
${order.shipping_method ? `<span style="margin-left:8px;color:var(--muted);font-size:11px;">${order.shipping_method}</span>` : ''}
|
|
</span>
|
|
</div>
|
|
${order.is_delivery && address !== "—" ? `
|
|
<div class="detail-row">
|
|
<span class="detail-label">Dirección</span>
|
|
<span class="detail-value" style="font-size:11px;">${address}</span>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
|
|
<div class="detail-section">
|
|
<div class="detail-title">Pago</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Método</span>
|
|
<span class="detail-value">
|
|
<span class="badge" style="background:${order.is_cash ? '#f59e0b' : '#1f6feb'};color:${order.is_cash ? '#000' : '#fff'};padding:3px 8px;border-radius:4px;font-size:10px;">
|
|
${order.is_cash ? 'EFECTIVO' : 'LINK'}
|
|
</span>
|
|
${order.payment_method_title ? `<span style="margin-left:8px;color:var(--muted);font-size:11px;">${order.payment_method_title}</span>` : ''}
|
|
</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Estado</span>
|
|
<span class="detail-value">
|
|
<span class="badge" style="background:${order.is_paid ? '#22c55e' : '#ef4444'};color:#fff;padding:3px 8px;border-radius:4px;font-size:10px;">
|
|
${order.is_paid ? 'PAGADO' : 'PENDIENTE'}
|
|
</span>
|
|
${order.date_paid ? `<span style="margin-left:8px;color:var(--muted);font-size:11px;">${formatDate(order.date_paid)}</span>` : ''}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detail-section">
|
|
<div class="detail-title">Cliente</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Nombre</span>
|
|
<span class="detail-value">${customerName}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Teléfono</span>
|
|
<span class="detail-value">${order.billing.phone || "—"}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Email</span>
|
|
<span class="detail-value">${order.billing.email || "—"}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detail-section">
|
|
<div class="detail-title">Productos (${order.line_items.length})</div>
|
|
<div class="items-list">
|
|
${order.line_items.length === 0 ? '<div style="color:var(--muted);text-align:center;padding:20px;">Sin productos</div>' :
|
|
order.line_items.map(item => `
|
|
<div class="item-row">
|
|
<span class="item-name" title="${item.name}">${item.name}</span>
|
|
<span class="item-qty">x${item.quantity}</span>
|
|
<span class="item-total">$${Number(item.total || 0).toLocaleString("es-AR")}</span>
|
|
</div>
|
|
`).join("")
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
${order.run_id ? `
|
|
<div class="detail-section">
|
|
<div class="detail-title">Metadata</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Run ID</span>
|
|
<span class="detail-value" style="font-family:monospace;font-size:10px;">${order.run_id}</span>
|
|
</div>
|
|
</div>
|
|
` : ""}
|
|
`;
|
|
}
|
|
}
|
|
|
|
customElements.define("orders-crud", OrdersCrud);
|