import { useState, useEffect } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router-dom' import api from '@/services/api' import type { Proposal, Milestone } from '@/types' import dayjs from 'dayjs' import CopyableCode from '@/components/CopyableCode' export default function ProposalDetailPage() { const { id } = useParams() const [searchParams] = useSearchParams() const projectId = searchParams.get('project_id') const navigate = useNavigate() const [proposal, setProposal] = useState(null) const [milestones, setMilestones] = useState([]) const [showAccept, setShowAccept] = useState(false) const [selectedMilestone, setSelectedMilestone] = useState('') const [actionLoading, setActionLoading] = useState(false) const [error, setError] = useState('') // Edit state const [showEdit, setShowEdit] = useState(false) const [editTitle, setEditTitle] = useState('') const [editDescription, setEditDescription] = useState('') const [editLoading, setEditLoading] = useState(false) const fetchProposal = () => { if (!projectId) return api.get(`/projects/${projectId}/proposals/${id}`).then(({ data }) => setProposal(data)) } useEffect(() => { fetchProposal() }, [id, projectId]) const loadMilestones = () => { if (!projectId) return api.get(`/milestones?project_id=${projectId}`) .then(({ data }) => setMilestones(data.filter((m) => m.status === 'open'))) } const handleAccept = async () => { if (!selectedMilestone || !projectId) return setActionLoading(true) setError('') try { await api.post(`/projects/${projectId}/proposals/${id}/accept`, { milestone_id: selectedMilestone }) setShowAccept(false) fetchProposal() } catch (err: any) { setError(err.response?.data?.detail || 'Accept failed') } finally { setActionLoading(false) } } const handleReject = async () => { if (!projectId) return const reason = prompt('Reject reason (optional):') setActionLoading(true) setError('') try { await api.post(`/projects/${projectId}/proposals/${id}/reject`, { reason: reason || undefined }) fetchProposal() } catch (err: any) { setError(err.response?.data?.detail || 'Reject failed') } finally { setActionLoading(false) } } const openEditModal = () => { if (!proposal) return setEditTitle(proposal.title) setEditDescription(proposal.description || '') setError('') setShowEdit(true) } const handleEdit = async () => { if (!projectId) return setEditLoading(true) setError('') try { await api.patch(`/projects/${projectId}/proposals/${id}`, { title: editTitle, description: editDescription, }) setShowEdit(false) fetchProposal() } catch (err: any) { setError(err.response?.data?.detail || 'Update failed') } finally { setEditLoading(false) } } const handleReopen = async () => { if (!projectId) return setActionLoading(true) setError('') try { await api.post(`/projects/${projectId}/proposals/${id}/reopen`) fetchProposal() } catch (err: any) { setError(err.response?.data?.detail || 'Reopen failed') } finally { setActionLoading(false) } } if (!proposal) return
Loading...
const statusBadgeClass = (s: string) => { if (s === 'open') return 'status-open' if (s === 'accepted') return 'status-completed' if (s === 'rejected') return 'status-closed' return '' } return (

💡 {proposal.title} {proposal.proposal_code && }

{proposal.status}
{error &&
{error}
}

Details

Proposal Code: {proposal.proposal_code ? : '—'}
Status: {proposal.status}
Created By: {proposal.created_by_username || (proposal.created_by_id ? `User #${proposal.created_by_id}` : '—')}
Created: {dayjs(proposal.created_at).format('YYYY-MM-DD HH:mm')}
Updated: {proposal.updated_at ? dayjs(proposal.updated_at).format('YYYY-MM-DD HH:mm') : '—'}

Description

{proposal.description || 'No description'}

{/* Generated tasks (shown when accepted) */} {proposal.status === 'accepted' && proposal.generated_tasks && proposal.generated_tasks.length > 0 && (

Generated Tasks

{proposal.generated_tasks.map((gt) => (
navigate(`/tasks/${gt.task_code || gt.task_id}`)} style={{ cursor: 'pointer' }} >
{gt.task_type}/{gt.task_subtype} {gt.task_code && {gt.task_code}}

{gt.title}

))}
)} {/* Action buttons */}
{proposal.status === 'open' && ( <> )} {proposal.status === 'rejected' && ( )}
{/* Edit modal */} {showEdit && (
setShowEdit(false)}>
e.stopPropagation()}>

Edit Proposal