Add Mode6Test: copy of Mode5Test running on port 3000
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* COMPONENT: JobList
|
||||
*
|
||||
* PURPOSE: Display list of jobs with pagination
|
||||
* FEATURES:
|
||||
* - Show jobs in grid/list view
|
||||
* - Pagination support
|
||||
* - Empty state
|
||||
* - Loading state
|
||||
* - Click handler for job details
|
||||
*/
|
||||
|
||||
import JobCard from './JobCard.svelte';
|
||||
import type { Job } from '../utils/types';
|
||||
|
||||
export let jobs: Job[] = [];
|
||||
export let loading: boolean = false;
|
||||
export let onJobClick: (job: Job) => void = () => {};
|
||||
|
||||
// Pagination
|
||||
const jobsPerPage = 12;
|
||||
let currentPage = 1;
|
||||
|
||||
/**
|
||||
* Get paginated jobs for current page
|
||||
*/
|
||||
function getPaginatedJobs(): Job[] {
|
||||
const start = (currentPage - 1) * jobsPerPage;
|
||||
const end = start + jobsPerPage;
|
||||
return jobs.slice(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total number of pages
|
||||
*/
|
||||
function getTotalPages(): number {
|
||||
return Math.ceil(jobs.length / jobsPerPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to next page
|
||||
*/
|
||||
function nextPage() {
|
||||
if (currentPage < getTotalPages()) {
|
||||
currentPage++;
|
||||
// Scroll to top
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page
|
||||
*/
|
||||
function prevPage() {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
// Scroll to top
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset pagination when jobs change
|
||||
*/
|
||||
$: if (jobs) {
|
||||
currentPage = 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Job List Container -->
|
||||
<div class="space-y-4">
|
||||
<!-- Loading State -->
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="text-center">
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
|
||||
<p class="mt-2 text-gray-600">Loading jobs...</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if jobs.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="text-center">
|
||||
<p class="text-gray-600">No jobs found</p>
|
||||
<p class="text-sm text-gray-500 mt-1">Try adjusting your search or filters</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Job Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#each getPaginatedJobs() as job (job.id)}
|
||||
<JobCard {job} onClick={() => onJobClick(job)} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{#if getTotalPages() > 1}
|
||||
<div class="flex items-center justify-between mt-8 pt-4 border-t border-gray-200">
|
||||
<!-- Info -->
|
||||
<div class="text-sm text-gray-600">
|
||||
Showing {(currentPage - 1) * jobsPerPage + 1} - {Math.min(
|
||||
currentPage * jobsPerPage,
|
||||
jobs.length
|
||||
)}
|
||||
of {jobs.length} jobs
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
on:click={prevPage}
|
||||
disabled={currentPage === 1}
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
← Previous
|
||||
</button>
|
||||
|
||||
<!-- Page Numbers -->
|
||||
<div class="flex items-center gap-1">
|
||||
{#each Array.from({ length: getTotalPages() }, (_, i) => i + 1) as page}
|
||||
{#if page === currentPage}
|
||||
<div class="w-8 h-8 flex items-center justify-center rounded bg-blue-600 text-white text-sm font-medium">
|
||||
{page}
|
||||
</div>
|
||||
{:else if page <= 2 || page >= getTotalPages() - 1 || Math.abs(page - currentPage) <= 1}
|
||||
<button
|
||||
on:click={() => (currentPage = page)}
|
||||
class="w-8 h-8 flex items-center justify-center rounded hover:bg-gray-100 text-sm transition-colors"
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
{:else if page === 3 && currentPage > 4}
|
||||
<span class="px-2 text-gray-500">...</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click={nextPage}
|
||||
disabled={currentPage === getTotalPages()}
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user