import { useMemo, useState } from 'react'; import { Badge } from '@renderer/components/ui/badge'; import { Button } from '@renderer/components/ui/button'; import { Checkbox } from '@renderer/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@renderer/components/ui/dialog'; import { Input } from '@renderer/components/ui/input'; import { Label } from '@renderer/components/ui/label'; import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@renderer/components/ui/select'; import { getTeamColorSet } from '@renderer/constants/teamColors'; import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { AlertTriangle, Search } from 'lucide-react'; import type { MentionSuggestion } from '@renderer/types/mention'; import type { ResolvedTeamMember, TeamTaskWithKanban } from '@shared/types'; interface CreateTaskDialogProps { open: boolean; teamName: string; members: ResolvedTeamMember[]; tasks: TeamTaskWithKanban[]; isTeamAlive?: boolean; defaultSubject?: string; defaultDescription?: string; defaultOwner?: string; defaultStartImmediately?: boolean; onClose: () => void; onSubmit: ( subject: string, description: string, owner?: string, blockedBy?: string[], related?: string[], prompt?: string, startImmediately?: boolean ) => void; submitting?: boolean; } export const CreateTaskDialog = ({ open, teamName, members, tasks, isTeamAlive = false, defaultSubject = '', defaultDescription = '', defaultOwner = '', defaultStartImmediately, onClose, onSubmit, submitting = false, }: CreateTaskDialogProps): React.JSX.Element => { const colorMap = useMemo(() => buildMemberColorMap(members), [members]); const [subject, setSubject] = useState(defaultSubject); const descriptionDraft = useDraftPersistence({ key: `createTask:${teamName}:description`, initialValue: defaultDescription || undefined, }); const [owner, setOwner] = useState(defaultOwner); const [blockedBy, setBlockedBy] = useState([]); const [related, setRelated] = useState([]); const [startImmediately, setStartImmediately] = useState(true); const promptDraft = useDraftPersistence({ key: `createTask:${teamName}:prompt` }); const [blockedBySearch, setBlockedBySearch] = useState(''); const [relatedSearch, setRelatedSearch] = useState(''); const [prevOpen, setPrevOpen] = useState(false); if (open && !prevOpen) { setSubject(defaultSubject); if (defaultDescription) { descriptionDraft.setValue(defaultDescription); } setOwner(defaultOwner); setBlockedBy([]); setRelated([]); setStartImmediately(defaultStartImmediately ?? isTeamAlive); promptDraft.clearDraft(); setBlockedBySearch(''); setRelatedSearch(''); } if (open !== prevOpen) { setPrevOpen(open); } const mentionSuggestions = useMemo( () => members.map((m) => ({ id: m.name, name: m.name, subtitle: formatAgentRole(m.role) ?? formatAgentRole(m.agentType) ?? undefined, color: colorMap.get(m.name), })), [members, colorMap] ); const requiresOwner = defaultStartImmediately === true; const canSubmit = subject.trim().length > 0 && !submitting && (!requiresOwner || !!owner); // Only show non-internal, non-deleted tasks as candidates for blocking const availableTasks = tasks.filter( (t) => t.status !== 'deleted' && t.kanbanColumn !== 'approved' ); const toggleBlockedBy = (taskId: string): void => { setBlockedBy((prev) => prev.includes(taskId) ? prev.filter((id) => id !== taskId) : [...prev, taskId] ); }; const toggleRelated = (taskId: string): void => { setRelated((prev) => prev.includes(taskId) ? prev.filter((id) => id !== taskId) : [...prev, taskId] ); }; const handleSubmit = (): void => { if (!canSubmit) return; onSubmit( subject.trim(), descriptionDraft.value.trim(), owner || undefined, blockedBy.length > 0 ? blockedBy : undefined, related.length > 0 ? related : undefined, promptDraft.value.trim() || undefined, startImmediately ); descriptionDraft.clearDraft(); promptDraft.clearDraft(); }; const handleOpenChange = (nextOpen: boolean): void => { if (!nextOpen) { onClose(); } }; const assigneeField = (
); return ( Create Task The task will be created in the team's tasks/ directory and appear on the Kanban board. {!isTeamAlive ? (

Team is offline. The task will be added to TODO — launch the team to start execution.

) : null}
setSubject(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && canSubmit) handleSubmit(); }} />
{assigneeField}
Draft saved ) : null } />
Draft saved ) : null } />
{owner ? (
setStartImmediately(v === true)} disabled={!isTeamAlive} />
{!isTeamAlive ? (

Team is offline. Launch the team first to start tasks immediately.

) : null}
) : null} {availableTasks.length > 0 ? (
{availableTasks.length > 3 ? (
setBlockedBySearch(e.target.value)} className="w-full bg-transparent py-0.5 pl-5 text-xs text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none" />
) : null}
{availableTasks .filter( (t) => !blockedBySearch || t.subject.toLowerCase().includes(blockedBySearch.toLowerCase()) || t.id.includes(blockedBySearch) ) .map((t) => { const isSelected = blockedBy.includes(t.id); return ( ); })}
{blockedBy.length > 0 ? (

Task will be blocked by: {blockedBy.map((id) => `#${id}`).join(', ')}

) : null}
) : null} {availableTasks.length > 0 ? (
{availableTasks.length > 3 ? (
setRelatedSearch(e.target.value)} className="w-full bg-transparent py-0.5 pl-5 text-xs text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none" />
) : null}
{availableTasks .filter( (t) => !relatedSearch || t.subject.toLowerCase().includes(relatedSearch.toLowerCase()) || t.id.includes(relatedSearch) ) .map((t) => { const isSelected = related.includes(t.id); return ( ); })}
{related.length > 0 ? (

Related: {related.map((id) => `#${id}`).join(', ')}

) : null}
) : null}
); };