feat: add schedulers feature with task management
- 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.
This commit is contained in:
176
src/pages/SchedulersPage.tsx
Normal file
176
src/pages/SchedulersPage.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user