'use client'; import { format, formatDistanceToNowStrict } from 'date-fns'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { CheckCircle2, Circle, FileSignature, Send, Wallet } from 'lucide-react'; import type { DetailTab } from '@/components/shared/detail-layout'; import { Button } from '@/components/ui/button'; import { NotesList } from '@/components/shared/notes-list'; import { InlineEditableField } from '@/components/shared/inline-editable-field'; import { InlineTagEditor } from '@/components/shared/inline-tag-editor'; import { RecommendationList } from '@/components/interests/recommendation-list'; import { InterestTimeline } from '@/components/interests/interest-timeline'; import { InterestDocumentsTab } from '@/components/interests/interest-documents-tab'; import { InterestFilesTab } from '@/components/interests/interest-files-tab'; import { LEAD_CATEGORIES } from '@/lib/constants'; import { apiFetch } from '@/lib/api/client'; import { cn } from '@/lib/utils'; type InterestPatchField = 'leadCategory' | 'source' | 'notes'; const LEAD_CATEGORY_OPTIONS = LEAD_CATEGORIES.map((c) => ({ value: c, label: c.replace(/_/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase()), })); interface InterestTabsOptions { interestId: string; currentUserId?: string; interest: { leadCategory: string | null; source: string | null; eoiStatus: string | null; contractStatus: string | null; depositStatus: string | null; reservationStatus: string | null; dateFirstContact: string | null; dateLastContact: string | null; dateEoiSent: string | null; dateEoiSigned: string | null; dateContractSent: string | null; dateContractSigned: string | null; dateDepositReceived: string | null; reminderEnabled: boolean; reminderDays: number | null; reminderLastFired: string | null; notes: string | null; tags?: Array<{ id: string; name: string; color: string }>; }; } function useInterestPatch(interestId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (patch: Partial>) => apiFetch(`/api/v1/interests/${interestId}`, { method: 'PATCH', body: patch, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['interests', interestId] }); }, }); } function useStageMutation(interestId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async ({ stage, reason }: { stage: string; reason?: string }) => apiFetch(`/api/v1/interests/${interestId}/stage`, { method: 'PATCH', body: { pipelineStage: stage, reason }, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['interests', interestId] }); qc.invalidateQueries({ queryKey: ['interests'] }); }, }); } function EditableRow({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); } function InfoRow({ label, value }: { label: string; value?: string | null }) { if (!value) return null; return (
{label}
{value}
); } function formatDate(date: string | null) { if (!date) return null; return format(new Date(date), 'MMM d, yyyy'); } function relativeDate(date: string | null) { if (!date) return null; return `${formatDistanceToNowStrict(new Date(date))} ago`; } interface MilestoneSectionProps { title: string; icon: React.ComponentType<{ className?: string }>; /** Lifecycle for this milestone, in chronological order. */ steps: Array<{ label: string; date: string | null; /** Stage to advance to when the user clicks the action button for this step. */ advanceStage?: string; /** Optional override for the action label. */ actionLabel?: string; }>; status: string | null; onAdvance: (stage: string) => void; isPending: boolean; } /** * One milestone section (EOI / Deposit / Contract) — shows a vertical lifecycle * with completed steps checked, the next step exposing a quick "mark as…" * button that bumps the pipeline stage. Each stage flip auto-stamps its date * via the service layer (interests.service.ts). When external systems wire in * (Documenso webhook, paid invoice → deposit, etc.), they patch the same * stage endpoint and these checkmarks light up automatically. */ function MilestoneSection({ title, icon: Icon, steps, status, onAdvance, isPending, }: MilestoneSectionProps) { const firstUnsetIdx = steps.findIndex((s) => !s.date); return (

{title}

{status ? ( {status.replace(/_/g, ' ')} ) : null}
    {steps.map((step, i) => { const done = !!step.date; const isNext = !done && i === firstUnsetIdx; return (
  1. {done ? ( ) : ( )}
    {step.label} {step.date ? ( {formatDate(step.date)} · {relativeDate(step.date)} ) : null}
    {isNext && step.advanceStage ? ( ) : null}
  2. ); })}
); } function OverviewTab({ interestId, interest, }: { interestId: string; interest: InterestTabsOptions['interest']; }) { const mutation = useInterestPatch(interestId); const stageMutation = useStageMutation(interestId); const save = (field: InterestPatchField) => async (next: string | null) => { await mutation.mutateAsync({ [field]: next }); }; const advance = (stage: string) => stageMutation.mutate({ stage, reason: 'Marked from overview' }); return (
{/* Sales-process milestones — the heart of the system. Each section is a mini lifecycle that auto-completes as actions happen on the platform (Documenso webhook, paid deposit invoice, signed contract). Until the automation lands, salespeople nudge stages forward via the inline buttons here, which auto-stamp the milestone date server-side. */}
{/* Lead & Source (editable) */}

Lead

{/* Contact dates (read-only — kept compact next to Lead) */}

Contact

{interest.reservationStatus ? ( ) : null}
{/* Reminder */} {interest.reminderEnabled && (

Reminder

)} {/* Notes (editable, multiline) */}

Notes

{/* Tags */}

Tags

); } export function getInterestTabs({ interestId, currentUserId, interest, }: InterestTabsOptions): DetailTab[] { return [ { id: 'overview', label: 'Overview', content: , }, { id: 'notes', label: 'Notes', content: ( ), }, { id: 'documents', label: 'Documents', content: , }, { id: 'files', label: 'Files', content: , }, { id: 'recommendations', label: 'Recommendations', content: , }, { id: 'activity', label: 'Activity', content: , }, ]; }