"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; // Escalate to CEO Dialog interface EscalateToCeoDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (reason: string) => void; isPending?: boolean; } export function EscalateToCeoDialog({ open, onOpenChange, onConfirm, isPending, }: EscalateToCeoDialogProps) { const [reason, setReason] = useState(""); const handleConfirm = () => { if (reason.trim()) { onConfirm(reason.trim()); setReason(""); } }; const handleOpenChange = (newOpen: boolean) => { if (!newOpen) setReason(""); onOpenChange(newOpen); }; return ( Escalate to CEO Explain why this task needs CEO review and approval. Reason for escalation setReason(e.target.value)} placeholder="This task requires CEO approval because..." rows={4} /> onOpenChange(false)}> Cancel {isPending ? "Escalating..." : "Escalate"} ); } // CEO Reject Dialog interface CeoRejectDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (notes: string) => void; isPending?: boolean; } export function CeoRejectDialog({ open, onOpenChange, onConfirm, isPending, }: CeoRejectDialogProps) { const [notes, setNotes] = useState(""); const handleConfirm = () => { if (notes.trim()) { onConfirm(notes.trim()); setNotes(""); } }; const handleOpenChange = (newOpen: boolean) => { if (!newOpen) setNotes(""); onOpenChange(newOpen); }; return ( Request Changes Explain what changes are needed before this task can be approved. Required changes setNotes(e.target.value)} placeholder="Please address the following..." rows={4} /> onOpenChange(false)}> Cancel {isPending ? "Submitting..." : "Request Changes"} ); } // Approve & Merge Dialog — simple confirmation for POST /tasks/{id}/approve-and-merge. // The backend endpoint accepts NO notes parameter, so no text input is needed here. interface ApproveAndMergeDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: () => void; isPending?: boolean; } export function ApproveAndMergeDialog({ open, onOpenChange, onConfirm, isPending, }: ApproveAndMergeDialogProps) { return ( Approve & Merge This will approve the completed work and merge the pull request into the target branch. This action cannot be undone. onOpenChange(false)} disabled={isPending}> Cancel {isPending ? "Merging..." : "Approve & Merge"} ); } // CEO Approve Dialog — the sign-off note is the audit record for merging to // production, so it is REQUIRED and must be substantive (>= 20 chars), matching // the server's CEO_NOTES_REQUIRED gate. const _CEO_NOTES_MIN = 20; interface CeoApproveDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (notes: string) => void; isPending?: boolean; } export function CeoApproveDialog({ open, onOpenChange, onConfirm, isPending, }: CeoApproveDialogProps) { const [notes, setNotes] = useState(""); const tooShort = notes.trim().length < _CEO_NOTES_MIN; const handleConfirm = () => { if (!tooShort) { onConfirm(notes.trim()); setNotes(""); } }; const handleOpenChange = (newOpen: boolean) => { if (!newOpen) setNotes(""); onOpenChange(newOpen); }; return ( Approve & Merge Record why this work is approved for production. This note is the permanent audit record for the merge and is required. Approval notes setNotes(e.target.value)} placeholder="Verified against acceptance criteria; approving for production because..." rows={4} /> {notes.trim().length}/{_CEO_NOTES_MIN} characters minimum onOpenChange(false)}> Cancel {isPending ? "Approving..." : "Approve & Merge"} ); } // Required Notes Dialog — a generalized version of CeoApproveDialog. Collects a // substantive audit note (>= minChars) before confirming a decision action. interface RequiredNotesDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (text: string) => void; isPending?: boolean; title: string; description: string; label: string; placeholder: string; minChars: number; confirmLabel: string; destructive?: boolean; } export function RequiredNotesDialog({ open, onOpenChange, onConfirm, isPending, title, description, label, placeholder, minChars, confirmLabel, destructive, }: RequiredNotesDialogProps) { const [text, setText] = useState(""); const tooShort = text.trim().length < minChars; const handleConfirm = () => { if (!tooShort) { onConfirm(text.trim()); setText(""); } }; const handleOpenChange = (newOpen: boolean) => { if (!newOpen) setText(""); onOpenChange(newOpen); }; return ( {title} {description} {label} setText(e.target.value)} placeholder={placeholder} rows={4} /> {text.trim().length}/{minChars} characters minimum onOpenChange(false)}> Cancel {isPending ? "Working..." : confirmLabel} ); } // Create Branch Dialog interface CreateBranchDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (branchType: string) => void; isPending?: boolean; taskId: string; } export function CreateBranchDialog({ open, onOpenChange, onConfirm, isPending, }: CreateBranchDialogProps) { const [branchType, setBranchType] = useState("feature"); const handleConfirm = () => { onConfirm(branchType); }; // Reset branchType to 'feature' when dialog is dismissed without confirming const handleOpenChange = (newOpen: boolean) => { if (!newOpen) setBranchType("feature"); onOpenChange(newOpen); }; return ( Create Branch Create a new branch for this task. The branch name will be generated automatically. Branch Type feature bug chore docs hotfix Branch will be named: {branchType}/[team]/[task-id] onOpenChange(false)}> Cancel {isPending ? "Creating..." : "Create Branch"} ); } // Create PR Dialog interface CreatePRDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (title: string, body: string) => void; isPending?: boolean; defaultTitle?: string; } export function CreatePRDialog({ open, onOpenChange, onConfirm, isPending, defaultTitle = "", }: CreatePRDialogProps) { const [title, setTitle] = useState(defaultTitle); const [body, setBody] = useState(""); const handleConfirm = () => { if (title.trim()) { onConfirm(title.trim(), body.trim()); setTitle(""); setBody(""); } }; // On open: seed title from defaultTitle. On close without confirming: reset both fields to empty. const handleOpenChange = (newOpen: boolean) => { if (newOpen) { if (defaultTitle) setTitle(defaultTitle); } else { // Reset both fields when dismissed without confirming setTitle(""); setBody(""); } onOpenChange(newOpen); }; return ( Create Pull Request Create a pull request for this task's branch. Title setTitle(e.target.value)} placeholder="PR title..." /> Description setBody(e.target.value)} placeholder="Describe the changes..." rows={6} /> onOpenChange(false)}> Cancel {isPending ? "Creating..." : "Create PR"} ); }
{notes.trim().length}/{_CEO_NOTES_MIN} characters minimum
{text.trim().length}/{minChars} characters minimum
Branch will be named: {branchType}/[team]/[task-id]