| class CustomList extends HTMLElement { |
| constructor() { |
| super(); |
| this.attachShadow({ mode: 'open' }); |
| this.shadowRoot.innerHTML = ` |
| <style> |
| @import url('https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css'); |
| |
| table { |
| width: 100%; |
| border-collapse: collapse; |
| } |
| |
| th { |
| cursor: pointer; |
| user-select: none; |
| } |
| |
| th:hover { |
| background-color: #f3f4f6; |
| } |
| |
| .sort-icon { |
| margin-left: 4px; |
| opacity: 0.5; |
| } |
| |
| .sort-icon.active { |
| opacity: 1; |
| } |
| </style> |
| <div class="overflow-x-auto"> |
| <table class="min-w-full divide-y divide-gray-200"> |
| <thead class="bg-gray-50"> |
| <tr> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" data-sort="id"> |
| ID <i data-feather="chevron-down" class="sort-icon"></i> |
| </th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" data-sort="type"> |
| Type <i data-feather="chevron-down" class="sort-icon"></i> |
| </th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" data-sort="status"> |
| Status <i data-feather="chevron-down" class="sort-icon"></i> |
| </th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |
| Actions |
| </th> |
| </tr> |
| </thead> |
| <tbody id="batchTableBody" class="bg-white divide-y divide-gray-200"> |
| <!-- Batches will be loaded here --> |
| </tbody> |
| </table> |
| </div> |
| <div id="loading" class="flex justify-center items-center py-8"> |
| <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div> |
| </div> |
| <div id="emptyState" class="hidden text-center py-12"> |
| <i data-feather="inbox" class="mx-auto h-12 w-12 text-gray-400"></i> |
| <h3 class="mt-2 text-sm font-medium text-gray-900">No batches yet</h3> |
| <p class="mt-1 text-sm text-gray-500">Upload a file to get started</p> |
| </div> |
| `; |
| |
| this.sortConfig = { |
| key: 'id', |
| direction: 'desc' |
| }; |
| } |
|
|
| connectedCallback() { |
| this.loadBatches(); |
| feather.replace({ width: 16, height: 16 }); |
| |
| |
| this.shadowRoot.querySelectorAll('th[data-sort]').forEach(th => { |
| th.addEventListener('click', () => this.handleSort(th.dataset.sort)); |
| }); |
| |
| |
| document.addEventListener('batch-created', () => this.loadBatches()); |
| } |
|
|
| async loadBatches() { |
| const tableBody = this.shadowRoot.getElementById('batchTableBody'); |
| const loading = this.shadowRoot.getElementById('loading'); |
| const emptyState = this.shadowRoot.getElementById('emptyState'); |
| |
| tableBody.innerHTML = ''; |
| loading.classList.remove('hidden'); |
| emptyState.classList.add('hidden'); |
| |
| try { |
| const batches = await window.batchService.getAllBatches(); |
| |
| if (batches.length === 0) { |
| loading.classList.add('hidden'); |
| emptyState.classList.remove('hidden'); |
| return; |
| } |
| |
| |
| const sortedBatches = [...batches].sort((a, b) => { |
| if (a[this.sortConfig.key] < b[this.sortConfig.key]) { |
| return this.sortConfig.direction === 'asc' ? -1 : 1; |
| } |
| if (a[this.sortConfig.key] > b[this.sortConfig.key]) { |
| return this.sortConfig.direction === 'asc' ? 1 : -1; |
| } |
| return 0; |
| }); |
| |
| sortedBatches.forEach(batch => { |
| const row = document.createElement('tr'); |
| |
| let statusBadgeClass = ''; |
| switch (batch.status) { |
| case 'PENDING': |
| statusBadgeClass = 'processing-badge-pending'; |
| break; |
| case 'RUNNING': |
| statusBadgeClass = 'processing-badge-running'; |
| break; |
| case 'COMPLETED': |
| statusBadgeClass = 'processing-badge-completed'; |
| break; |
| } |
| |
| row.innerHTML = ` |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${batch.id}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${batch.type}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| <span class="processing-badge ${statusBadgeClass}">${batch.status}</span> |
| </td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| <button class="download-btn" ${batch.status !== 'COMPLETED' ? 'disabled' : ''} data-id="${batch.id}"> |
| <i data-feather="download" class="w-4 h-4 mr-1"></i> Download |
| </button> |
| </td> |
| `; |
| |
| tableBody.appendChild(row); |
| }); |
| |
| |
| this.shadowRoot.querySelectorAll('.download-btn').forEach(btn => { |
| btn.addEventListener('click', () => this.handleDownload(btn.dataset.id)); |
| }); |
| |
| |
| this.updateSortIndicators(); |
| |
| } catch (error) { |
| console.error('Error loading batches:', error); |
| } finally { |
| loading.classList.add('hidden'); |
| feather.replace({ width: 16, height: 16 }); |
| } |
| } |
|
|
| handleSort(key) { |
| if (this.sortConfig.key === key) { |
| this.sortConfig.direction = this.sortConfig.direction === 'asc' ? 'desc' : 'asc'; |
| } else { |
| this.sortConfig.key = key; |
| this.sortConfig.direction = 'desc'; |
| } |
| |
| this.loadBatches(); |
| } |
|
|
| updateSortIndicators() { |
| this.shadowRoot.querySelectorAll('.sort-icon').forEach(icon => { |
| icon.classList.remove('active'); |
| icon.setAttribute('data-feather', 'chevron-down'); |
| }); |
| |
| const activeTh = this.shadowRoot.querySelector(`th[data-sort="${this.sortConfig.key}"]`); |
| if (activeTh) { |
| const icon = activeTh.querySelector('.sort-icon'); |
| icon.classList.add('active'); |
| icon.setAttribute('data-feather', this.sortConfig.direction === 'asc' ? 'chevron-up' : 'chevron-down'); |
| feather.replace({ width: 16, height: 16 }); |
| } |
| } |
|
|
| async handleDownload(batchId) { |
| try { |
| await window.batchService.downloadResult(parseInt(batchId)); |
| } catch (error) { |
| console.error('Error downloading batch result:', error); |
| } |
| } |
| } |
|
|
| customElements.define('custom-list', CustomList); |