import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { request } from './api' // TEAMING PARTNERS AND KEY PERSONNEL COMMITMENTS. // // The gap this fills is market wide and named: subcontractor tooling forces a choice between managing // teaming DURING capture and tracking compliance AFTER award, so partner data ends up scattered across a // capture CRM, a proposal tool and a post award ERP. missionMATCH already tracks an opportunity across that // whole span, which is why the partner belongs on the capture record rather than the proposal. export interface TeamingPartner { id: string opportunity_id: string company_name: string partner_role: 'prime' | 'subcontractor' | 'joint_venture' status: 'identified' | 'in_discussion' | 'agreement_signed' | 'declined' uei?: string cage_code?: string set_asides: string[] scope_summary?: string agreement_signed_on?: string commitments: number created_at: string updated_at: string } export interface KeyPersonnelCommitment { id: string requirement_id: string requirement_title?: string candidate_id?: string candidate_name?: string teaming_partner_id?: string partner_company?: string partner_person_name?: string role_title?: string commitment_type: 'letter_of_intent' | 'contingent_offer' | 'employee' | 'partner_provided' status: 'proposed' | 'committed' | 'withdrawn' | 'superseded' signed_on?: string expires_on?: string notes?: string days_until_expiry?: number // lapsed is the fact a bid review most needs and the one nobody notices: a letter of intent signed for a // bid that slipped six months is not a commitment any more. lapsed?: boolean } export const PARTNER_ROLES = [ { value: 'prime', label: 'Prime' }, { value: 'subcontractor', label: 'Subcontractor' }, { value: 'joint_venture', label: 'Joint venture' }, ] export const PARTNER_STATUSES = [ { value: 'identified', label: 'Identified' }, { value: 'in_discussion', label: 'In discussion' }, { value: 'agreement_signed', label: 'Agreement signed' }, { value: 'declined', label: 'Declined' }, ] export const COMMITMENT_TYPES = [ { value: 'letter_of_intent', label: 'Letter of intent' }, { value: 'contingent_offer', label: 'Contingent offer' }, { value: 'employee', label: 'Already an employee' }, { value: 'partner_provided', label: 'Provided by a partner' }, ] export function partnerColor(status: string): string { if (status === 'agreement_signed') return 'var(--color-ok)' if (status === 'in_discussion') return 'var(--color-warn)' if (status === 'declined') return 'var(--color-risk)' return 'var(--color-muted)' } export function useTeamingPartners(oppID: string) { return useQuery({ queryKey: ['capture', oppID, 'partners'], queryFn: () => request<{ partners: TeamingPartner[]; count: number }>( `/opportunities/tracked/${encodeURIComponent(oppID)}/partners`, ), enabled: !!oppID, }) } export function useAddPartner(oppID: string) { const qc = useQueryClient() return useMutation({ mutationFn: (v: Partial) => request<{ id: string }>(`/opportunities/tracked/${encodeURIComponent(oppID)}/partners`, { method: 'POST', body: JSON.stringify(v), }), onSuccess: () => void qc.invalidateQueries({ queryKey: ['capture', oppID] }), }) } export function useUpdatePartner(oppID: string) { const qc = useQueryClient() return useMutation({ mutationFn: (v: { id: string } & Partial) => request<{ updated: boolean }>(`/teaming-partners/${encodeURIComponent(v.id)}`, { method: 'PATCH', body: JSON.stringify(v), }), onSuccess: () => void qc.invalidateQueries({ queryKey: ['capture', oppID] }), }) } export function useKeyPersonnel(oppID: string) { return useQuery({ queryKey: ['capture', oppID, 'key-personnel'], queryFn: () => request<{ commitments: KeyPersonnelCommitment[]; count: number; lapsed: number }>( `/opportunities/tracked/${encodeURIComponent(oppID)}/key-personnel`, ), enabled: !!oppID, }) } export function useAddCommitment(oppID: string) { const qc = useQueryClient() return useMutation({ mutationFn: (v: Record) => request<{ id: string }>('/key-personnel', { method: 'POST', body: JSON.stringify(v) }), onSuccess: () => void qc.invalidateQueries({ queryKey: ['capture', oppID] }), }) } export function useUpdateCommitment(oppID: string) { const qc = useQueryClient() return useMutation({ mutationFn: (v: { id: string } & Record) => request<{ updated: boolean }>(`/key-personnel/${encodeURIComponent(v.id)}`, { method: 'PATCH', body: JSON.stringify(v), }), onSuccess: () => void qc.invalidateQueries({ queryKey: ['capture', oppID] }), }) }