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:
JSC
2025-08-29 00:09:45 +02:00
parent 6a40311a82
commit 009780e64c
10 changed files with 1205 additions and 0 deletions

View 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>
)
}