- Introduced SchedulersPage for managing scheduled tasks. - Implemented CreateTaskDialog for creating new scheduled tasks. - Added SchedulersHeader for filtering and searching tasks. - Created SchedulersTable to display scheduled tasks with actions. - Implemented loading and error states with SchedulersLoadingStates. - Added API service for task management in schedulers. - Enhanced date formatting utility to handle timezone. - Updated AppSidebar and AppRoutes to include SchedulersPage.
176 lines
5.0 KiB
TypeScript
176 lines
5.0 KiB
TypeScript
import { AppLayout } from '@/components/AppLayout'
|
|
import { CreateTaskDialog } from '@/components/schedulers/CreateTaskDialog'
|
|
import { SchedulersHeader } from '@/components/schedulers/SchedulersHeader'
|
|
import {
|
|
SchedulersEmpty,
|
|
SchedulersError,
|
|
SchedulersLoading,
|
|
} from '@/components/schedulers/SchedulersLoadingStates'
|
|
import { SchedulersTable } from '@/components/schedulers/SchedulersTable'
|
|
import {
|
|
type CreateScheduledTaskRequest,
|
|
type ScheduledTask,
|
|
type TaskStatus,
|
|
type TaskType,
|
|
schedulersService,
|
|
} from '@/lib/api/services/schedulers'
|
|
import { useCallback, useEffect, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
export function SchedulersPage() {
|
|
const [tasks, setTasks] = useState<ScheduledTask[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
// Search and filtering state
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [statusFilter, setStatusFilter] = useState<TaskStatus | 'all'>('all')
|
|
const [taskTypeFilter, setTaskTypeFilter] = useState<TaskType | 'all'>('all')
|
|
|
|
// Create task dialog state
|
|
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
|
const [createLoading, setCreateLoading] = useState(false)
|
|
|
|
// Debounce search query
|
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery)
|
|
|
|
useEffect(() => {
|
|
const handler = setTimeout(() => {
|
|
setDebouncedSearchQuery(searchQuery)
|
|
}, 300)
|
|
|
|
return () => clearTimeout(handler)
|
|
}, [searchQuery])
|
|
|
|
const fetchTasks = useCallback(async () => {
|
|
try {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
const allTasks = await schedulersService.getUserTasks({
|
|
status: statusFilter !== 'all' ? statusFilter : undefined,
|
|
task_type: taskTypeFilter !== 'all' ? taskTypeFilter : undefined,
|
|
limit: 100, // Get all tasks for now, we'll implement pagination if needed
|
|
})
|
|
|
|
// Client-side search filtering
|
|
let filteredTasks = allTasks
|
|
if (debouncedSearchQuery.trim()) {
|
|
const query = debouncedSearchQuery.toLowerCase()
|
|
filteredTasks = allTasks.filter(task =>
|
|
task.name.toLowerCase().includes(query) ||
|
|
task.task_type.toLowerCase().includes(query) ||
|
|
task.status.toLowerCase().includes(query)
|
|
)
|
|
}
|
|
|
|
setTasks(filteredTasks)
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch tasks'
|
|
setError(errorMessage)
|
|
toast.error(errorMessage)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [debouncedSearchQuery, statusFilter, taskTypeFilter])
|
|
|
|
useEffect(() => {
|
|
fetchTasks()
|
|
}, [fetchTasks])
|
|
|
|
const handleCreateTask = async (data: CreateScheduledTaskRequest) => {
|
|
try {
|
|
setCreateLoading(true)
|
|
|
|
await schedulersService.createTask(data)
|
|
toast.success('Task created successfully')
|
|
|
|
// Close dialog and refresh tasks
|
|
setShowCreateDialog(false)
|
|
fetchTasks()
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : 'Failed to create task'
|
|
toast.error(errorMessage)
|
|
} finally {
|
|
setCreateLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleCancelCreate = () => {
|
|
setShowCreateDialog(false)
|
|
}
|
|
|
|
const handleTaskUpdated = (updatedTask: ScheduledTask) => {
|
|
setTasks(prev => prev.map(task =>
|
|
task.id === updatedTask.id ? updatedTask : task
|
|
))
|
|
}
|
|
|
|
const handleTaskDeleted = (taskId: number) => {
|
|
setTasks(prev => prev.filter(task => task.id !== taskId))
|
|
}
|
|
|
|
const renderContent = () => {
|
|
if (loading) {
|
|
return <SchedulersLoading />
|
|
}
|
|
|
|
if (error) {
|
|
return <SchedulersError error={error} onRetry={fetchTasks} />
|
|
}
|
|
|
|
if (tasks.length === 0) {
|
|
return (
|
|
<SchedulersEmpty
|
|
searchQuery={searchQuery}
|
|
statusFilter={statusFilter}
|
|
taskTypeFilter={taskTypeFilter}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<SchedulersTable
|
|
tasks={tasks}
|
|
onTaskUpdated={handleTaskUpdated}
|
|
onTaskDeleted={handleTaskDeleted}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<AppLayout
|
|
breadcrumb={{
|
|
items: [{ label: 'Dashboard', href: '/' }, { label: 'Schedulers' }],
|
|
}}
|
|
>
|
|
<div className="flex-1 rounded-xl bg-muted/50 p-4">
|
|
<SchedulersHeader
|
|
searchQuery={searchQuery}
|
|
onSearchChange={setSearchQuery}
|
|
statusFilter={statusFilter}
|
|
onStatusFilterChange={setStatusFilter}
|
|
taskTypeFilter={taskTypeFilter}
|
|
onTaskTypeFilterChange={setTaskTypeFilter}
|
|
onRefresh={fetchTasks}
|
|
onCreateClick={() => setShowCreateDialog(true)}
|
|
loading={loading}
|
|
error={error}
|
|
taskCount={tasks.length}
|
|
/>
|
|
|
|
<CreateTaskDialog
|
|
open={showCreateDialog}
|
|
onOpenChange={setShowCreateDialog}
|
|
loading={createLoading}
|
|
onSubmit={handleCreateTask}
|
|
onCancel={handleCancelCreate}
|
|
/>
|
|
|
|
<div className="mt-6">
|
|
{renderContent()}
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
)
|
|
} |