import { useEffect, useMemo, useRef, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Link, useNavigate, useParams } from 'react-router-dom' import { getEntity } from '../api/entitiesApi' import { activateTask, buildTaskPreviewPrompt, deleteTask, generateTaskPreview, getTask, inactivateTask, updateTask, type EmailLookback, } from '../api/tasksApi' interface TaskFormState { name: string prompt: string scheduleCron: string emailLookback: EmailLookback } interface CronParts { minute: string hour: string dayOfMonth: string month: string dayOfWeek: string } interface RegularitySuggestion { id: string label: string description: string cronParts: CronParts } interface GeneratedMessageItem { id: string label: string content: string } const DEFAULT_CRON_PARTS: CronParts = { minute: '0', hour: '9', dayOfMonth: '*', month: '*', dayOfWeek: '1-5', } const REGULARITY_SUGGESTIONS: RegularitySuggestion[] = [ { id: 'daily', label: 'Daily', description: 'Every day at 09:00', cronParts: { minute: '0', hour: '9', dayOfMonth: '*', month: '*', dayOfWeek: '*' }, }, { id: 'weekdays', label: 'Weekdays', description: 'Monday to Friday at 09:00', cronParts: { minute: '0', hour: '9', dayOfMonth: '*', month: '*', dayOfWeek: '1-5' }, }, { id: 'weekly', label: 'Weekly', description: 'Every Monday at 09:00', cronParts: { minute: '0', hour: '9', dayOfMonth: '*', month: '*', dayOfWeek: '1' }, }, { id: 'monthly', label: 'Monthly', description: 'Day 1 of each month at 09:00', cronParts: { minute: '0', hour: '9', dayOfMonth: '1', month: '*', dayOfWeek: '*' }, }, ] function buildCron(parts: CronParts): string { return [parts.minute, parts.hour, parts.dayOfMonth, parts.month, parts.dayOfWeek].join(' ') } function parseCron(scheduleCron: string): CronParts { const parts = scheduleCron.split(' ') if (parts.length !== 5) { return DEFAULT_CRON_PARTS } return { minute: parts[0], hour: parts[1], dayOfMonth: parts[2], month: parts[3], dayOfWeek: parts[4], } } const DEFAULT_TASK_FORM: TaskFormState = { name: '', prompt: '', scheduleCron: buildCron(DEFAULT_CRON_PARTS), emailLookback: 'last_week', } async function invalidateTaskQueries( queryClient: ReturnType, entityId: string, taskId: string ) { await Promise.all([ queryClient.invalidateQueries({ queryKey: ['entity-tasks', entityId] }), queryClient.invalidateQueries({ queryKey: ['entity-task', taskId] }), queryClient.invalidateQueries({ queryKey: ['entity-tasks'] }), ]) } export default function EditTaskPage() { const { entityId = '', taskId = '' } = useParams() const navigate = useNavigate() const queryClient = useQueryClient() const [cronParts, setCronParts] = useState(DEFAULT_CRON_PARTS) const [taskForm, setTaskForm] = useState(DEFAULT_TASK_FORM) const [generatedMessages, setGeneratedMessages] = useState([]) const [selectedMessageId, setSelectedMessageId] = useState('') const [previewError, setPreviewError] = useState('') const generatedMessageCounter = useRef(0) const { data: entity, isLoading: isLoadingEntity } = useQuery({ queryKey: ['entity', entityId], queryFn: () => getEntity(entityId), enabled: Boolean(entityId), }) const { data: task, isLoading: isLoadingTask } = useQuery({ queryKey: ['entity-task', taskId], queryFn: () => getTask(taskId), enabled: Boolean(taskId), }) useEffect(() => { if (!task) { return } const nextCronParts = parseCron(task.scheduleCron) setCronParts(nextCronParts) setTaskForm({ name: task.name, prompt: task.prompt, scheduleCron: task.scheduleCron, emailLookback: task.emailLookback, }) }, [task]) const updateTaskMutation = useMutation({ mutationFn: (data: TaskFormState) => updateTask(taskId, { entityId, name: data.name, prompt: data.prompt, scheduleCron: data.scheduleCron, emailLookback: data.emailLookback, }), onSuccess: async () => { await invalidateTaskQueries(queryClient, entityId, taskId) navigate(`/entities/${entityId}`) }, }) const inactivateTaskMutation = useMutation({ mutationFn: () => inactivateTask(taskId), onSuccess: async () => { await invalidateTaskQueries(queryClient, entityId, taskId) navigate(`/entities/${entityId}`) }, }) const activateTaskMutation = useMutation({ mutationFn: () => activateTask(taskId), onSuccess: async () => { await invalidateTaskQueries(queryClient, entityId, taskId) navigate(`/entities/${entityId}`) }, }) const deleteTaskMutation = useMutation({ mutationFn: () => deleteTask(taskId), onSuccess: async () => { await invalidateTaskQueries(queryClient, entityId, taskId) navigate(`/entities/${entityId}`) }, }) const previewMutation = useMutation({ mutationFn: generateTaskPreview, onMutate: () => { setPreviewError('') }, onSuccess: (value) => { generatedMessageCounter.current += 1 const nextMessage: GeneratedMessageItem = { id: `message-${generatedMessageCounter.current}`, label: `Message #${generatedMessageCounter.current}`, content: value, } setGeneratedMessages((prev) => [nextMessage, ...prev]) setSelectedMessageId(nextMessage.id) }, onError: (error) => { setPreviewError( error instanceof Error ? error.message : 'Unable to generate a test message from the local model.' ) }, }) const canSubmit = useMemo(() => { const hasFilledCronParts = Object.values(cronParts).every((value) => value.trim().length > 0) return Boolean(taskForm.name.trim() && taskForm.prompt.trim() && hasFilledCronParts) }, [cronParts, taskForm.name, taskForm.prompt]) const currentTask = useMemo( () => ({ entityId, name: taskForm.name, prompt: taskForm.prompt, scheduleCron: taskForm.scheduleCron, emailLookback: taskForm.emailLookback, }), [entityId, taskForm.emailLookback, taskForm.name, taskForm.prompt, taskForm.scheduleCron] ) const finalPrompt = useMemo(() => { if (!entity) { return 'Entity details unavailable.' } return buildTaskPreviewPrompt(entity, currentTask) }, [currentTask, entity]) const selectedMessage = useMemo( () => generatedMessages.find((message) => message.id === selectedMessageId), [generatedMessages, selectedMessageId] ) const applyCronParts = (nextCronParts: CronParts) => { setCronParts(nextCronParts) setTaskForm((prev) => ({ ...prev, scheduleCron: buildCron(nextCronParts) })) } const regularitySummary = useMemo( () => `At ${cronParts.hour.padStart(2, '0')}:${cronParts.minute.padStart(2, '0')} - day ${cronParts.dayOfMonth}, month ${cronParts.month}, week day ${cronParts.dayOfWeek}`, [cronParts.dayOfMonth, cronParts.dayOfWeek, cronParts.hour, cronParts.minute, cronParts.month] ) if (!entityId || !taskId) { return
Task identifier is missing.
} if (isLoadingEntity || isLoadingTask) { return
Loading...
} if (!task) { return (

Task not found.

Back to {entity?.name ?? 'Entity'}
) } return (

Edit Task {entity && ( — {entity.name} )}

{ event.preventDefault() if (!canSubmit) return updateTaskMutation.mutate(taskForm) }} >
setTaskForm((prev) => ({ ...prev, name: event.target.value }))} className="mt-1 w-full rounded-md border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100" required />