'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useParams } from 'next/navigation'; import { CheckCircle2, ChevronDown, ChevronRight } from 'lucide-react'; import { Checkbox } from '@/components/ui/checkbox'; import { Button } from '@/components/ui/button'; import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; import { cn } from '@/lib/utils'; interface QualificationRow { key: string; label: string; description: string | null; enabled: boolean; displayOrder: number; confirmed: boolean; confirmedAt: string | null; confirmedBy: string | null; notes: string | null; autoSatisfied: boolean; /** Human-readable explanation of what data drove auto-satisfaction * (e.g. "Desired: 60 × 25 × 6 ft"). Empty when not auto-satisfied. */ evidence: string; } interface QualificationResponse { data: { criteria: QualificationRow[]; fullyQualified: boolean; }; } /** * Per-interest qualification checklist. Hidden when the port has no * enabled criteria. When the rep has confirmed every enabled criterion AND * the deal is still in 'enquiry', a soft hint surfaces a Promote button * that advances the stage to 'qualified' through the standard transition * endpoint (no override; this is the canonical adjacent move). */ export function QualificationChecklist({ interestId, currentStage, }: { interestId: string; currentStage: string; }) { const params = useParams<{ portSlug: string }>(); const queryClient = useQueryClient(); // When the checklist is fully confirmed, default to collapsed and let // the rep expand on demand. `null` means "follow the auto-default"; // an explicit boolean reflects a rep click. const [manuallyExpanded, setManuallyExpanded] = useState(false); const { data, isLoading } = useQuery({ queryKey: ['interest-qualifications', interestId], queryFn: () => apiFetch(`/api/v1/interests/${interestId}/qualifications`), }); const toggleMutation = useMutation({ mutationFn: async (vars: { criterionKey: string; confirmed: boolean }) => apiFetch(`/api/v1/interests/${interestId}/qualifications`, { method: 'PUT', body: { criterionKey: vars.criterionKey, confirmed: vars.confirmed }, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['interest-qualifications', interestId] }); }, onError: (err) => toastError(err), }); const promoteMutation = useMutation({ mutationFn: async () => apiFetch(`/api/v1/interests/${interestId}/stage`, { method: 'POST', body: { pipelineStage: 'qualified' }, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['interests', interestId] }); queryClient.invalidateQueries({ queryKey: ['interests'] }); }, onError: (err) => toastError(err), }); if (isLoading) return null; if (!data) return null; const criteria = data.data.criteria; if (criteria.length === 0) return null; const fullyQualified = data.data.fullyQualified; const showPromoteHint = fullyQualified && currentStage === 'enquiry'; // Auto-collapse when fully confirmed - rep can expand to inspect. // Force-expanded whenever there's still an outstanding item. const expanded = manuallyExpanded || !fullyQualified; // Avoid referencing `params` in the JSX so the unused destructure passes // strict noUnused checks; it stays available for future deep-link hooks. void params; return (
{expanded ? ( ) : null} {showPromoteHint ? (

All criteria confirmed - this lead is ready to qualify.

) : null}
); }