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

@@ -4,3 +4,4 @@ export * from './player'
export * from './files'
export * from './extractions'
export * from './favorites'
export * from './schedulers'

View File

@@ -0,0 +1,201 @@
import { apiClient } from '../client'
import type { ApiResponse } from '../types'
// Task types
export type TaskType = 'CREDIT_RECHARGE' | 'PLAY_SOUND' | 'PLAY_PLAYLIST'
export type TaskStatus = 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED'
export type RecurrenceType = 'NONE' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'CRON'
// Task interfaces
export interface ScheduledTask {
id: number
name: string
task_type: TaskType
status: TaskStatus
user_id: number | null
scheduled_at: string
timezone: string
parameters: Record<string, unknown>
recurrence_type: RecurrenceType
cron_expression: string | null
recurrence_count: number | null
expires_at: string | null
executions_count: number
last_executed_at: string | null
next_execution_at: string | null
error_message: string | null
is_active: boolean
created_at: string
updated_at: string
}
export interface CreateScheduledTaskRequest {
name: string
task_type: TaskType
scheduled_at: string
timezone?: string
parameters?: Record<string, unknown>
recurrence_type?: RecurrenceType
cron_expression?: string | null
recurrence_count?: number | null
expires_at?: string | null
}
export interface UpdateScheduledTaskRequest {
name?: string
scheduled_at?: string
timezone?: string
parameters?: Record<string, unknown>
is_active?: boolean
expires_at?: string | null
}
export interface GetUserTasksParams {
status?: TaskStatus
task_type?: TaskType
limit?: number
offset?: number
}
export interface GetAllTasksParams {
status?: TaskStatus
task_type?: TaskType
limit?: number
offset?: number
}
// API service
export const schedulersService = {
// User tasks
async getUserTasks(params?: GetUserTasksParams): Promise<ScheduledTask[]> {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.append('status', params.status)
if (params?.task_type) searchParams.append('task_type', params.task_type)
if (params?.limit) searchParams.append('limit', params.limit.toString())
if (params?.offset) searchParams.append('offset', params.offset.toString())
const queryString = searchParams.toString()
const endpoint = `/api/v1/scheduler/tasks${queryString ? `?${queryString}` : ''}`
return await apiClient.get<ScheduledTask[]>(endpoint)
},
async getTask(taskId: number): Promise<ScheduledTask> {
return await apiClient.get<ScheduledTask>(`/api/v1/scheduler/tasks/${taskId}`)
},
async createTask(data: CreateScheduledTaskRequest): Promise<ScheduledTask> {
return await apiClient.post<ScheduledTask>('/api/v1/scheduler/tasks', data)
},
async updateTask(taskId: number, data: UpdateScheduledTaskRequest): Promise<ScheduledTask> {
return await apiClient.patch<ScheduledTask>(`/api/v1/scheduler/tasks/${taskId}`, data)
},
async cancelTask(taskId: number): Promise<ApiResponse> {
return await apiClient.delete<ApiResponse>(`/api/v1/scheduler/tasks/${taskId}`)
},
// Admin endpoints
async getAllTasks(params?: GetAllTasksParams): Promise<ScheduledTask[]> {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.append('status', params.status)
if (params?.task_type) searchParams.append('task_type', params.task_type)
if (params?.limit) searchParams.append('limit', params.limit.toString())
if (params?.offset) searchParams.append('offset', params.offset.toString())
const queryString = searchParams.toString()
const endpoint = `/api/v1/scheduler/admin/tasks${queryString ? `?${queryString}` : ''}`
return await apiClient.get<ScheduledTask[]>(endpoint)
},
async getSystemTasks(params?: { status?: TaskStatus; task_type?: TaskType }): Promise<ScheduledTask[]> {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.append('status', params.status)
if (params?.task_type) searchParams.append('task_type', params.task_type)
const queryString = searchParams.toString()
const endpoint = `/api/v1/scheduler/admin/system-tasks${queryString ? `?${queryString}` : ''}`
return await apiClient.get<ScheduledTask[]>(endpoint)
},
async createSystemTask(data: CreateScheduledTaskRequest): Promise<ScheduledTask> {
return await apiClient.post<ScheduledTask>('/api/v1/scheduler/admin/system-tasks', data)
},
}
// Utility functions
export function getTaskTypeLabel(taskType: TaskType): string {
switch (taskType) {
case 'CREDIT_RECHARGE':
return 'Credit Recharge'
case 'PLAY_SOUND':
return 'Play Sound'
case 'PLAY_PLAYLIST':
return 'Play Playlist'
default:
return taskType
}
}
export function getTaskStatusLabel(status: TaskStatus): string {
switch (status) {
case 'PENDING':
return 'Pending'
case 'RUNNING':
return 'Running'
case 'COMPLETED':
return 'Completed'
case 'FAILED':
return 'Failed'
case 'CANCELLED':
return 'Cancelled'
default:
return status
}
}
export function getRecurrenceTypeLabel(recurrenceType: RecurrenceType): string {
switch (recurrenceType) {
case 'NONE':
return 'None'
case 'HOURLY':
return 'Hourly'
case 'DAILY':
return 'Daily'
case 'WEEKLY':
return 'Weekly'
case 'MONTHLY':
return 'Monthly'
case 'YEARLY':
return 'Yearly'
case 'CRON':
return 'Custom'
default:
return recurrenceType
}
}
export function getTaskStatusVariant(status: TaskStatus): 'default' | 'secondary' | 'destructive' | 'outline' {
switch (status) {
case 'PENDING':
return 'outline'
case 'RUNNING':
return 'default'
case 'COMPLETED':
return 'secondary'
case 'FAILED':
return 'destructive'
case 'CANCELLED':
return 'outline'
default:
return 'default'
}
}