diff --git a/src/frontend/src/routes/+page.svelte b/src/frontend/src/routes/+page.svelte index c41f121..2854b81 100644 --- a/src/frontend/src/routes/+page.svelte +++ b/src/frontend/src/routes/+page.svelte @@ -1,14 +1,79 @@
@@ -40,6 +105,86 @@
+ +
+

🔄 Recent Sync Jobs

+ {#if loading} +
Loading jobs...
+ {:else if jobs.length === 0} +
+

No sync jobs yet. Sync your QSOs from LoTW or DCL to get started!

+ +
+ {:else} +
+ {#each jobs as job (job.id)} +
+
+
+ {getJobIcon(job.type)} + {getJobLabel(job.type)} + #{job.id} +
+ + {job.status} + +
+ +
+ + {formatDate(job.createdAt)} + + {#if job.startedAt} + {formatTime(job.startedAt)} + {/if} + {#if getDuration(job)} + ({getDuration(job)}) + {/if} +
+ + {#if job.status === 'failed' && job.error} +
+ ❌ {job.error} +
+ {:else if job.result} +
+ {#if job.result.total !== undefined} + + {job.result.total} total + + {/if} + {#if job.result.added !== undefined && job.result.added > 0} + + +{job.result.added} added + + {/if} + {#if job.result.updated !== undefined && job.result.updated > 0} + + ~{job.result.updated} updated + + {/if} + {#if job.result.skipped !== undefined && job.result.skipped > 0} + + {job.result.skipped} skipped + + {/if} +
+ {:else if job.status === 'running' || job.status === 'pending'} +
+ + {job.status === 'pending' ? 'Waiting to start...' : 'Processing...'} + +
+ {/if} +
+ {/each} +
+ {/if} +
+

Getting Started

    @@ -191,4 +336,196 @@ color: #666; line-height: 1.8; } + + /* Jobs Section */ + .jobs-section { + margin-bottom: 2rem; + } + + .section-title { + font-size: 1.5rem; + color: #333; + margin-bottom: 1rem; + } + + .loading-state, + .empty-state { + background: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 2rem; + text-align: center; + color: #666; + } + + .empty-actions { + display: flex; + gap: 1rem; + justify-content: center; + margin-top: 1.5rem; + flex-wrap: wrap; + } + + .jobs-list { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .job-card { + background: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 1rem 1.25rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.2s; + } + + .job-card:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); + } + + .job-card.failed { + border-left: 4px solid #dc3545; + } + + .job-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; + } + + .job-title { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .job-icon { + font-size: 1.5rem; + } + + .job-name { + font-weight: 600; + color: #333; + font-size: 1.1rem; + } + + .job-id { + font-size: 0.85rem; + color: #999; + font-family: monospace; + } + + .status-badge { + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.85rem; + font-weight: 500; + text-transform: capitalize; + } + + .bg-yellow-100 { + background-color: #fef3c7; + } + + .bg-blue-100 { + background-color: #dbeafe; + } + + .bg-green-100 { + background-color: #d1fae5; + } + + .bg-red-100 { + background-color: #fee2e2; + } + + .text-yellow-800 { + color: #92400e; + } + + .text-blue-800 { + color: #1e40af; + } + + .text-green-800 { + color: #065f46; + } + + .text-red-800 { + color: #991b1b; + } + + .job-meta { + display: flex; + gap: 0.75rem; + font-size: 0.9rem; + color: #666; + margin-bottom: 0.5rem; + flex-wrap: wrap; + } + + .job-date { + font-weight: 500; + } + + .job-time, + .job-duration { + color: #999; + } + + .job-error { + background: #fee2e2; + color: #991b1b; + padding: 0.75rem; + border-radius: 4px; + font-size: 0.95rem; + margin-top: 0.5rem; + } + + .job-stats { + display: flex; + gap: 1rem; + flex-wrap: wrap; + margin-top: 0.5rem; + } + + .stat-item { + font-size: 0.9rem; + color: #666; + padding: 0.25rem 0.5rem; + background: #f8f9fa; + border-radius: 4px; + } + + .stat-item strong { + color: #333; + } + + .stat-added { + color: #065f46; + background: #d1fae5; + } + + .stat-updated { + color: #1e40af; + background: #dbeafe; + } + + .stat-skipped { + color: #92400e; + background: #fef3c7; + } + + .job-progress { + margin-top: 0.5rem; + } + + .progress-text { + color: #1e40af; + font-size: 0.9rem; + font-style: italic; + }