--- name: data-driven-panel description: Create dashboard panel components with integrated resilient data services, combining UI construction and data fetching into a unified pattern. --- # Data-Driven Panel Pattern Create dashboard panels that seamlessly integrate UI construction with resilient data fetching. Each panel is a self-contained component that manages its own data lifecycle. ## Architecture Overview ``` Panel (base class) ├── element: HTMLElement (outer container) │ ├── header (title, status indicators) │ └── content (data display area) └── DataService (internal) ├── Circuit Breaker (failure handling) └── Caching layer ``` ## Implementation ### 1. Base Panel with Integrated Data Service Create `src/components/DataPanel.ts`: ```typescript import { createCircuitBreaker } from '../utils/circuit-breaker'; export interface PanelOptions { id: string; title: string; showCount?: boolean; className?: string; dataService: () => Promise; defaultValue: T; serviceOptions?: { maxFailures?: number; cooldownMs?: number; cacheTtlMs?: number; }; } export abstract class DataPanel { protected element: HTMLElement; protected content: HTMLElement; private breaker: CircuitBreaker; private refreshTimer: ReturnType | null = null; constructor(protected options: PanelOptions) { this.breaker = createCircuitBreaker({ name: options.id, maxFailures: options.serviceOptions?.maxFailures ?? 2, cooldownMs: options.serviceOptions?.cooldownMs ?? 300_000, cacheTtlMs: options.serviceOptions?.cacheTtlMs ?? 60_000, }); this.element = document.createElement('div'); this.element.className = `panel ${options.className || ''}`; this.element.dataset.panel = options.id; // Initialize UI this.initUI(); this.fetchData(); } private initUI(): void { // Header setup const header = document.createElement('div'); header.className = 'panel-header'; const title = document.createElement('span'); title.className = 'panel-title'; title.textContent = this.options.title; header.appendChild(title); // Content area this.content = document.createElement('div'); this.content.className = 'panel-content'; this.element.appendChild(header); this.element.appendChild(this.content); this.showLoading(); } protected async fetchData(): Promise { try { const data = await this.breaker.execute( this.options.dataService, this.options.defaultValue ); this.render(data); } catch (err) { this.showError('Failed to load data', () => this.fetchData()); } } protected abstract render(data: T): void; public showLoading(message = 'Loading...'): void { this.content.innerHTML = `
${message}
`; } public showError(message: string, retryHandler: () => void): void { this.content.innerHTML = `

${message}

`; this.content.querySelector('.retry-btn')?.addEventListener('click', retryHandler); } public setAutoRefresh(intervalMs: number): void { this.refreshTimer = setInterval(() => this.fetchData(), intervalMs); } public destroy(): void { if (this.refreshTimer) clearInterval(this.refreshTimer); this.element.remove(); } } ``` ### 2. Concrete Panel Implementation Example ```typescript import { DataPanel } from './DataPanel'; interface StockData { quotes: StockQuote[]; updatedAt: string; } export class StockPanel extends DataPanel { constructor() { super({ id: 'stocks', title: 'Stock Market', dataService: fetchStockData, defaultValue: { quotes: [], updatedAt: new Date().toISOString() }, serviceOptions: { cacheTtlMs: 30_000 // Refresh cache every 30 seconds } }); this.setAutoRefresh(60_000); // Auto-refresh every minute } protected render(data: StockData): void { const html = data.quotes.map(quote => `
${quote.symbol} $${quote.price?.toFixed(2) || '—'}
`).join(''); this.content.innerHTML = `
${html}
Updated: ${new Date(data.updatedAt).toLocaleTimeString()}
`; } } ``` ### 3. Data Service Implementation ```typescript // src/services/stockService.ts interface StockQuote { symbol: string; price: number | null; } export async function fetchStockData(): Promise { const response = await fetch('/api/stocks'); if (!response.ok) throw new Error(`HTTP ${response.status}`); const quotes: StockQuote[] = await response.json(); return { quotes, updatedAt: new Date().toISOString() }; } ``` ## Key Benefits 1. **Unified Pattern**: Combines UI and data logic in one coherent pattern 2. **Resilient Data Fetching**: Built-in circuit breaker and caching 3. **Simplified Implementation**: Concrete panels only need to implement rendering 4. **Consistent Behavior**: All panels share the same loading/error states 5. **Flexible Configuration**: Service options customizable per panel ## Best Practices 1. **Service Composition**: Create small, focused services for each data type 2. **Default Values**: Always provide meaningful fallback data 3. **Error Boundaries**: Use the built-in error handling for consistency 4. **Memory Management**: Clean up timers and event listeners in destroy() 5. **Type Safety**: Maintain strong typing throughout the data flow