import type { ChangeEvent } from 'react' import { useEffect, useState } from 'react' import { ArrowLeft, Save } from 'lucide-react' import { Link, useParams } from 'react-router-dom' import { KeyPersonnelPanel, TeamingPanel } from '../../screens/TeamingPanels' import { ToolHeader } from '../../components/ToolHeader' import { Panel } from '../../components/ui' import { slateBrand } from './brand' import { useTrackedOpportunity, useUpdateTrackedOpportunity } from './captureQueries' import { inputCls, PRIME_OR_SUB, SOURCE_LABEL, STATUSES, toDateInput } from './captureConstants' type Form = { title: string agency_name: string office_name: string solicitation_number: string notice_type: string naics_code: string psc_code: string set_aside_code: string place_of_performance: string incumbent_name: string source_url: string response_deadline: string prime_or_sub: string status: string } const EMPTY: Form = { title: '', agency_name: '', office_name: '', solicitation_number: '', notice_type: '', naics_code: '', psc_code: '', set_aside_code: '', place_of_performance: '', incumbent_name: '', source_url: '', response_deadline: '', prime_or_sub: '', status: 'identified', } export function OpportunityDetail() { const { id = '' } = useParams() const q = useTrackedOpportunity(id) const update = useUpdateTrackedOpportunity() const [form, setForm] = useState
(EMPTY) const [saved, setSaved] = useState(false) useEffect(() => { const o = q.data if (!o) return setForm({ title: o.title, agency_name: o.agency_name, office_name: o.office_name, solicitation_number: o.solicitation_number, notice_type: o.notice_type, naics_code: o.naics_code, psc_code: o.psc_code, set_aside_code: o.set_aside_code, place_of_performance: o.place_of_performance, incumbent_name: o.incumbent_name, source_url: o.source_url, response_deadline: toDateInput(o.response_deadline), prime_or_sub: o.prime_or_sub, status: o.status || 'identified', }) }, [q.data]) const set = (k: keyof Form) => (e: ChangeEvent) => { setForm((f) => ({ ...f, [k]: e.target.value })) setSaved(false) } const save = () => { update.mutate({ id, patch: { ...form } }, { onSuccess: () => setSaved(true) }) } const field = (label: string, k: keyof Form, placeholder = '') => ( ) return (
Back to capture {/* THE PHASE 2 PATH, in the order it is walked: documents in, scope reviewed, requirements built, and amendments watched. These four screens existed as API routes with no way to reach them, which is the same shape of defect as the promotion routes that were built and never mounted. */} {q.isLoading ? (

Loading...

) : q.isError ? (

Opportunity not found.

) : (

{form.title || 'Untitled opportunity'}

Source {SOURCE_LABEL[q.data?.source_type ?? ''] ?? q.data?.source_type}

{field('Title', 'title', 'Data Engineering Support Services')}
{field('Agency or customer', 'agency_name')} {field('Office', 'office_name')}
{field('Solicitation or reference', 'solicitation_number')} {field('Notice type', 'notice_type')}
{field('NAICS', 'naics_code')} {field('PSC', 'psc_code')} {field('Set aside', 'set_aside_code')}
{field('Place of performance', 'place_of_performance')}
{field('Incumbent', 'incumbent_name')} {field('Source URL', 'source_url')}
{update.isError &&

{(update.error as Error).message}

}
{saved && Saved}
)} {/* TEAMING AND KEY PERSONNEL sit on the CAPTURE record, not the proposal, because that is when the decisions are made. A teaming agreement is negotiated while the pursuit is being shaped, and a key person is committed before the bid goes out. Hanging either off the proposal would mean they could only be recorded after the moment they mattered. */}
) }