<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Customers</title>
<style>
/* Inlined from shared/styles.css */
:root{color-scheme:light dark;--spacing-xs:4px;--spacing-sm:8px;--spacing-md:16px;--spacing-lg:24px;--spacing-xl:32px;--radius-sm:6px;--radius-md:8px;--radius-lg:12px;--accent-color:#0066cc;--color-bg-primary:#ffffff;--color-bg-secondary:#f7f7f8;--color-bg-tertiary:#ececf1;--color-border:#d9d9e3;--color-border-light:#ececf1;--color-text-primary:#2d333a;--color-text-secondary:#6e6e80;--color-text-tertiary:#9b9ba5;--color-accent:#0066cc;--color-accent-hover:#0052a3;--color-success:#10a37f;--color-warning:#f59e0b;--color-danger:#ef4444}@media (prefers-color-scheme:dark){:root{--color-bg-primary:#212121;--color-bg-secondary:#2f2f2f;--color-bg-tertiary:#3f3f3f;--color-border:#4d4d4d;--color-border-light:#3f3f3f;--color-text-primary:#ececec;--color-text-secondary:#c5c5d2;--color-text-tertiary:#9b9ba5;--color-accent:#3b82f6;--color-accent-hover:#2563eb;--color-success:#10a37f;--color-warning:#f59e0b;--color-danger:#ef4444}}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:14px;line-height:1.6;color:var(--color-text-primary);background:var(--color-bg-primary);padding:var(--spacing-md)}.container{max-width:100%;margin:0 auto}.grid{display:grid;gap:var(--spacing-md);grid-template-columns:repeat(auto-fill,minmax(280px,1fr))}.customer-card{background:var(--color-bg-primary);border:1px solid var(--color-border-light);border-radius:var(--radius-md);padding:var(--spacing-md);transition:all 0.2s ease;cursor:pointer}.customer-card:hover{border-color:var(--color-border);background:var(--color-bg-secondary)}.customer-card-header{display:flex;align-items:center;gap:var(--spacing-sm);margin-bottom:var(--spacing-sm)}.customer-card-avatar{width:40px;height:40px;border-radius:50%;background:var(--color-bg-tertiary);display:flex;align-items:center;justify-content:center;font-weight:600;color:var(--color-text-secondary);font-size:16px;flex-shrink:0}.customer-card-title{font-size:15px;font-weight:600;color:var(--color-text-primary);margin-bottom:2px}.customer-card-subtitle{font-size:13px;color:var(--color-text-tertiary)}.customer-card-body{display:flex;flex-direction:column;gap:var(--spacing-xs)}.customer-card-row{display:flex;align-items:center;gap:var(--spacing-xs);font-size:13px;color:var(--color-text-secondary)}.customer-card-icon{width:16px;height:16px;opacity:0.6}.empty-state{text-align:center;padding:var(--spacing-xl) var(--spacing-md);color:var(--color-text-secondary)}.empty-state-icon{font-size:48px;margin-bottom:var(--spacing-sm);opacity:0.3}.empty-state-title{font-size:16px;font-weight:600;color:var(--color-text-primary);margin-bottom:var(--spacing-xs)}.empty-state-message{font-size:14px;color:var(--color-text-secondary)}.pagination{display:flex;justify-content:center;align-items:center;gap:var(--spacing-sm);margin-top:var(--spacing-md);padding-top:var(--spacing-md);border-top:1px solid var(--color-border-light)}.pagination-info{text-align:center;color:var(--color-text-secondary);font-size:13px}a{color:var(--color-accent);text-decoration:none;transition:color 0.15s ease}a:hover{color:var(--color-accent-hover);text-decoration:underline}.text-sm{font-size:13px}.text-tertiary{color:var(--color-text-tertiary)}
</style>
</head>
<body>
<div class="container">
<div id="customers-container">
<!-- Initial loading state -->
<div class="empty-state">
<div class="empty-state-icon">⏳</div>
<div class="empty-state-title">Loading customers...</div>
</div>
</div>
<div id="pagination-container"></div>
</div>
<script>
const SET_GLOBALS_EVENT_TYPE = 'openai:set_globals';
function getInitials(name) {
if (!name) return '?';
const parts = name.trim().split(' ');
if (parts.length === 1) return parts[0].charAt(0).toUpperCase();
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = String(text);
return div.innerHTML;
}
function render() {
// Access toolOutput from window.openai
const toolOutput = window.openai?.toolOutput;
if (!toolOutput) {
console.log('[Fergus MCP] No toolOutput available yet');
return;
}
const customers = toolOutput.customers || [];
const pagination = toolOutput.pagination || {};
console.log('[Fergus MCP] Rendering', customers.length, 'customers');
const container = document.getElementById('customers-container');
if (!customers || customers.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">👥</div>
<div class="empty-state-title">No customers found</div>
<div class="empty-state-message">Try adjusting your search criteria</div>
</div>
`;
return;
}
// Render as a grid of cards
const html = `
<div class="grid">
${customers.map(customer => {
const initials = getInitials(customer.name);
const hasContact = customer.email || customer.phone;
const hasAddress = customer.address?.city || customer.address?.postalCode;
return `
<div class="customer-card">
<div class="customer-card-header">
<div class="customer-card-avatar" aria-label="${escapeHtml(customer.name || 'Unknown')} avatar">
${escapeHtml(initials)}
</div>
<div style="flex: 1; min-width: 0;">
<div class="customer-card-title">${escapeHtml(customer.name || 'Unnamed Customer')}</div>
<div class="customer-card-subtitle">ID: ${escapeHtml(customer.id?.toString() || 'N/A')}</div>
</div>
</div>
<div class="customer-card-body">
${customer.email ? `
<div class="customer-card-row">
<svg class="customer-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M2 4l6 4 6-4M2 4v8h12V4M2 4h12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<a href="mailto:${escapeHtml(customer.email)}" class="text-sm">
${escapeHtml(customer.email)}
</a>
</div>
` : ''}
${customer.phone ? `
<div class="customer-card-row">
<svg class="customer-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M3 2h3l1 4-2 2c1 2 3 4 5 5l2-2 4 1v3a1 1 0 01-1 1C7 16 0 9 0 3a1 1 0 011-1z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="text-sm">${escapeHtml(customer.phone)}</span>
</div>
` : ''}
${hasAddress ? `
<div class="customer-card-row">
<svg class="customer-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M8 14s6-4 6-8a6 6 0 00-12 0c0 4 6 8 6 8z" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="8" cy="6" r="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="text-sm">
${escapeHtml([customer.address?.city, customer.address?.postalCode].filter(Boolean).join(', '))}
</span>
</div>
` : ''}
${!hasContact && !hasAddress ? `
<div class="customer-card-row text-tertiary">
<span class="text-sm">No contact information</span>
</div>
` : ''}
</div>
</div>
`;
}).join('')}
</div>
`;
container.innerHTML = html;
// Render pagination info
if (pagination.count > 0) {
const paginationContainer = document.getElementById('pagination-container');
const totalText = pagination.total && pagination.total > pagination.count
? ` of ${pagination.total}`
: '';
paginationContainer.innerHTML = `
<div class="pagination">
<div class="pagination-info">
Showing ${pagination.count}${totalText} customer${pagination.count !== 1 ? 's' : ''}
</div>
</div>
`;
}
}
// Listen for the set_globals event (fired when data updates)
window.addEventListener(SET_GLOBALS_EVENT_TYPE, (event) => {
console.log('[Fergus MCP] openai:set_globals event received');
console.log('[Fergus MCP] event.detail.globals:', event.detail.globals);
// Re-render when globals change
render();
});
// Initial render on load (in case data is already available)
console.log('[Fergus MCP] Template loaded');
console.log('[Fergus MCP] window.openai on load:', window.openai);
console.log('[Fergus MCP] window.openai.toolOutput on load:', window.openai?.toolOutput);
// Try rendering immediately if window.openai exists
if (window.openai?.toolOutput) {
console.log('[Fergus MCP] Initial data found, rendering immediately');
render();
} else {
console.log('[Fergus MCP] No initial data, waiting for openai:set_globals event');
}
</script>
</body>
</html>