560 lines
26 KiB
TypeScript
560 lines
26 KiB
TypeScript
import {useState} from 'react'
|
|
import {useQuery} from '@tanstack/react-query'
|
|
|
|
import useApiUrlStore from '../../../app/store/apiStore'
|
|
import {JobClient} from '../../../generated/ManagingApi'
|
|
import {BottomMenuBar} from '../../../components/mollecules'
|
|
|
|
import JobsTable from './jobsTable'
|
|
|
|
const JobsSettings: React.FC = () => {
|
|
const { apiUrl } = useApiUrlStore()
|
|
const [page, setPage] = useState(1)
|
|
const [pageSize, setPageSize] = useState(50)
|
|
const [sortBy, setSortBy] = useState<string>('CreatedAt')
|
|
const [sortOrder, setSortOrder] = useState<string>('desc')
|
|
const [statusFilter, setStatusFilter] = useState<string>('Pending')
|
|
const [jobTypeFilter, setJobTypeFilter] = useState<string>('')
|
|
const [userIdFilter, setUserIdFilter] = useState<string>('')
|
|
const [workerIdFilter, setWorkerIdFilter] = useState<string>('')
|
|
const [bundleRequestIdFilter, setBundleRequestIdFilter] = useState<string>('')
|
|
const [filtersOpen, setFiltersOpen] = useState<boolean>(false)
|
|
const [showTable, setShowTable] = useState<boolean>(false)
|
|
|
|
const jobClient = new JobClient({}, apiUrl)
|
|
|
|
// Fetch job summary statistics
|
|
const {
|
|
data: jobSummary,
|
|
isLoading: isLoadingSummary
|
|
} = useQuery({
|
|
queryKey: ['jobSummary'],
|
|
queryFn: async () => {
|
|
return await jobClient.job_GetJobSummary()
|
|
},
|
|
staleTime: 10000, // 10 seconds
|
|
gcTime: 5 * 60 * 1000,
|
|
refetchInterval: 5000, // Auto-refresh every 5 seconds
|
|
})
|
|
|
|
const {
|
|
data: jobsData,
|
|
isLoading,
|
|
error,
|
|
refetch
|
|
} = useQuery({
|
|
queryKey: ['jobs', page, pageSize, sortBy, sortOrder, statusFilter, jobTypeFilter, userIdFilter, workerIdFilter, bundleRequestIdFilter],
|
|
queryFn: async () => {
|
|
return await jobClient.job_GetJobs(
|
|
page,
|
|
pageSize,
|
|
sortBy,
|
|
sortOrder,
|
|
statusFilter || null,
|
|
jobTypeFilter || null,
|
|
userIdFilter ? parseInt(userIdFilter) : null,
|
|
workerIdFilter || null,
|
|
bundleRequestIdFilter || null
|
|
)
|
|
},
|
|
enabled: showTable, // Only fetch when table is shown
|
|
staleTime: 10000, // 10 seconds
|
|
gcTime: 5 * 60 * 1000,
|
|
refetchInterval: showTable ? 5000 : false, // Auto-refresh only when table is shown
|
|
})
|
|
|
|
const jobs = jobsData?.jobs || []
|
|
const totalCount = jobsData?.totalCount || 0
|
|
const totalPages = jobsData?.totalPages || 0
|
|
const currentPage = jobsData?.currentPage || 1
|
|
|
|
const handlePageChange = (newPage: number) => {
|
|
setPage(newPage)
|
|
}
|
|
|
|
const handleSortChange = (newSortBy: string) => {
|
|
if (sortBy === newSortBy) {
|
|
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
|
} else {
|
|
setSortBy(newSortBy)
|
|
setSortOrder('desc')
|
|
}
|
|
}
|
|
|
|
const handleFilterChange = () => {
|
|
setPage(1) // Reset to first page when filters change
|
|
}
|
|
|
|
const clearFilters = () => {
|
|
setStatusFilter('Pending') // Reset to Pending instead of All
|
|
setJobTypeFilter('')
|
|
setUserIdFilter('')
|
|
setWorkerIdFilter('')
|
|
setBundleRequestIdFilter('')
|
|
setPage(1)
|
|
}
|
|
|
|
// Helper function to get status badge color
|
|
const getStatusBadgeColor = (status: string | undefined) => {
|
|
if (!status) return 'badge-ghost'
|
|
const statusLower = status.toLowerCase()
|
|
switch (statusLower) {
|
|
case 'pending':
|
|
return 'badge-warning'
|
|
case 'running':
|
|
return 'badge-info'
|
|
case 'completed':
|
|
return 'badge-success'
|
|
case 'failed':
|
|
return 'badge-error'
|
|
case 'cancelled':
|
|
return 'badge-ghost'
|
|
default:
|
|
return 'badge-ghost'
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="container mx-auto p-4 pb-20">
|
|
{/* Job Summary Statistics */}
|
|
<div className="mb-8">
|
|
{isLoadingSummary ? (
|
|
<div className="flex flex-col items-center justify-center py-12">
|
|
<progress className="progress progress-primary w-56"></progress>
|
|
<p className="mt-4 text-base-content/70">Loading job summary...</p>
|
|
</div>
|
|
) : jobSummary && (
|
|
<div className="space-y-6">
|
|
{/* Status Overview Section */}
|
|
{jobSummary.statusSummary && jobSummary.statusSummary.length > 0 && (
|
|
<div className="card bg-base-100 shadow-md">
|
|
<div className="card-body">
|
|
<h3 className="card-title text-xl mb-4">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />
|
|
</svg>
|
|
Status Overview
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{jobSummary.statusSummary.map((statusItem) => {
|
|
const statusLower = (statusItem.status || '').toLowerCase()
|
|
let statusIcon, statusDesc, statusColor
|
|
|
|
switch (statusLower) {
|
|
case 'pending':
|
|
statusIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
)
|
|
statusDesc = 'Waiting to be processed'
|
|
statusColor = 'text-warning'
|
|
break
|
|
case 'running':
|
|
statusIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.986V5.653z" />
|
|
</svg>
|
|
)
|
|
statusDesc = 'Currently processing'
|
|
statusColor = 'text-info'
|
|
break
|
|
case 'completed':
|
|
statusIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
)
|
|
statusDesc = 'Successfully finished'
|
|
statusColor = 'text-success'
|
|
break
|
|
case 'failed':
|
|
statusIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
|
|
</svg>
|
|
)
|
|
statusDesc = 'Requires attention'
|
|
statusColor = 'text-error'
|
|
break
|
|
case 'cancelled':
|
|
statusIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
)
|
|
statusDesc = 'Cancelled by user'
|
|
statusColor = 'text-neutral'
|
|
break
|
|
default:
|
|
statusIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
|
|
</svg>
|
|
)
|
|
statusDesc = 'Unknown status'
|
|
statusColor = 'text-base-content'
|
|
}
|
|
|
|
return (
|
|
<div key={statusItem.status} className="card bg-base-200 shadow-sm">
|
|
<div className="card-body p-4">
|
|
<div className="stat p-0">
|
|
<div className={`stat-figure ${statusColor}`}>
|
|
{statusIcon}
|
|
</div>
|
|
<div className="stat-title">{statusItem.status || 'Unknown'}</div>
|
|
<div className={`stat-value ${statusColor}`}>{statusItem.count || 0}</div>
|
|
<div className="stat-desc">{statusDesc}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Job Types Section */}
|
|
{jobSummary.jobTypeSummary && jobSummary.jobTypeSummary.length > 0 && (
|
|
<div className="card bg-base-100 shadow-md">
|
|
<div className="card-body">
|
|
<h3 className="card-title text-xl mb-4">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
|
|
</svg>
|
|
Job Types
|
|
</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{jobSummary.jobTypeSummary.map((typeItem) => {
|
|
const jobTypeLower = (typeItem.jobType || '').toLowerCase()
|
|
let jobTypeIcon, jobTypeDesc
|
|
|
|
switch (jobTypeLower) {
|
|
case 'backtest':
|
|
jobTypeIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232 1.232 3.228 0 4.46s-3.228 1.232-4.46 0L14.3 19.8M5 14.5l-1.402 1.402c-1.232 1.232-1.232 3.228 0 4.46s3.228 1.232 4.46 0L9.7 19.8" />
|
|
</svg>
|
|
)
|
|
jobTypeDesc = 'Backtest jobs'
|
|
break
|
|
case 'geneticbacktest':
|
|
jobTypeIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z" />
|
|
</svg>
|
|
)
|
|
jobTypeDesc = 'Genetic backtest jobs'
|
|
break
|
|
default:
|
|
jobTypeIcon = (
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
|
|
</svg>
|
|
)
|
|
jobTypeDesc = 'Job type'
|
|
}
|
|
|
|
return (
|
|
<div key={typeItem.jobType} className="card bg-base-200 shadow-sm">
|
|
<div className="card-body p-4">
|
|
<div className="stat p-0">
|
|
<div className="stat-figure text-primary">
|
|
{jobTypeIcon}
|
|
</div>
|
|
<div className="stat-title">{typeItem.jobType || 'Unknown'}</div>
|
|
<div className="stat-value text-primary">{typeItem.count || 0}</div>
|
|
<div className="stat-desc">{jobTypeDesc}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Status by Job Type Table Section */}
|
|
{jobSummary.statusTypeSummary && jobSummary.statusTypeSummary.length > 0 && (
|
|
<div className="card bg-base-100 shadow-md">
|
|
<div className="card-body">
|
|
<h3 className="card-title text-xl mb-4">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5M9 11.25v1.5M12 9v3.75m3-3.75v3.75m-9 .75h12.75a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v7.5a2.25 2.25 0 002.25 2.25z" />
|
|
</svg>
|
|
Status by Job Type
|
|
</h3>
|
|
<div className="overflow-x-auto">
|
|
<table className="table table-zebra w-full">
|
|
<thead>
|
|
<tr>
|
|
<th>Status</th>
|
|
<th>Job Type</th>
|
|
<th>Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{jobSummary.statusTypeSummary.map((item, index) => (
|
|
<tr key={`${item.status}-${item.jobType}-${index}`} className="hover">
|
|
<td>
|
|
<span className={`badge ${getStatusBadgeColor(item.status)}`}>
|
|
{item.status || 'Unknown'}
|
|
</span>
|
|
</td>
|
|
<td>{item.jobType || 'Unknown'}</td>
|
|
<td>{item.count || 0}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Load Table Button */}
|
|
{!showTable && (
|
|
<div className="card bg-base-100 shadow-md mb-4">
|
|
<div className="card-body">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h3 className="card-title text-lg">Jobs List</h3>
|
|
<p className="text-sm text-base-content/70">Click the button below to load and view the jobs table</p>
|
|
</div>
|
|
<button
|
|
className="btn btn-primary"
|
|
onClick={() => setShowTable(true)}
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-5 h-5">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5M9 11.25v1.5M12 9v3.75m3-3.75v3.75m-9 .75h12.75a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v7.5a2.25 2.25 0 002.25 2.25z" />
|
|
</svg>
|
|
Load Jobs Table
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{showTable && (
|
|
<>
|
|
{/* Hide Table Button */}
|
|
<div className="card bg-base-100 shadow-md mb-4">
|
|
<div className="card-body py-3">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="card-title text-lg">Jobs List</h3>
|
|
<button
|
|
className="btn btn-ghost btn-sm"
|
|
onClick={() => setShowTable(false)}
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-5 h-5">
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
Hide Table
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{filtersOpen && (
|
|
<div className="card bg-base-200 mb-4">
|
|
<div className="card-body">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
<div>
|
|
<label className="label">
|
|
<span className="label-text">Status</span>
|
|
</label>
|
|
<select
|
|
data-filter="status"
|
|
className="select select-bordered w-full"
|
|
value={statusFilter}
|
|
onChange={(e) => {
|
|
setStatusFilter(e.target.value)
|
|
handleFilterChange()
|
|
}}
|
|
>
|
|
<option value="">All</option>
|
|
<option value="Pending">Pending</option>
|
|
<option value="Running">Running</option>
|
|
<option value="Completed">Completed</option>
|
|
<option value="Failed">Failed</option>
|
|
<option value="Cancelled">Cancelled</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label">
|
|
<span className="label-text">Job Type</span>
|
|
</label>
|
|
<select
|
|
data-filter="jobType"
|
|
className="select select-bordered w-full"
|
|
value={jobTypeFilter}
|
|
onChange={(e) => {
|
|
setJobTypeFilter(e.target.value)
|
|
handleFilterChange()
|
|
}}
|
|
>
|
|
<option value="">All</option>
|
|
<option value="Backtest">Backtest</option>
|
|
<option value="GeneticBacktest">Genetic Backtest</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label">
|
|
<span className="label-text">User ID</span>
|
|
</label>
|
|
<input
|
|
data-filter="userId"
|
|
type="number"
|
|
className="input input-bordered w-full"
|
|
placeholder="User ID"
|
|
value={userIdFilter}
|
|
onChange={(e) => {
|
|
setUserIdFilter(e.target.value)
|
|
handleFilterChange()
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label">
|
|
<span className="label-text">Worker ID</span>
|
|
</label>
|
|
<input
|
|
data-filter="workerId"
|
|
type="text"
|
|
className="input input-bordered w-full"
|
|
placeholder="Worker ID"
|
|
value={workerIdFilter}
|
|
onChange={(e) => {
|
|
setWorkerIdFilter(e.target.value)
|
|
handleFilterChange()
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label">
|
|
<span className="label-text">Bundle Request ID</span>
|
|
</label>
|
|
<input
|
|
data-filter="bundleRequestId"
|
|
type="text"
|
|
className="input input-bordered w-full"
|
|
placeholder="Bundle Request ID"
|
|
value={bundleRequestIdFilter}
|
|
onChange={(e) => {
|
|
setBundleRequestIdFilter(e.target.value)
|
|
handleFilterChange()
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="alert alert-error mb-4">
|
|
<span>Error loading jobs: {(error as any)?.message || 'Unknown error'}</span>
|
|
</div>
|
|
)}
|
|
|
|
<JobsTable
|
|
jobs={jobs}
|
|
isLoading={isLoading}
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
totalCount={totalCount}
|
|
pageSize={pageSize}
|
|
sortBy={sortBy}
|
|
sortOrder={sortOrder}
|
|
onPageChange={handlePageChange}
|
|
onSortChange={handleSortChange}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{/* Bottom Menu Bar */}
|
|
{showTable && (
|
|
<BottomMenuBar>
|
|
<li>
|
|
<a
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
setFiltersOpen(!filtersOpen)
|
|
}}
|
|
className={filtersOpen ? 'active' : ''}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
refetch()
|
|
}}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
clearFilters()
|
|
}}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
</BottomMenuBar>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default JobsSettings
|
|
|