import { useQuery } from '@tanstack/react-query'; import { api } from '@/shared/lib/apiClient'; import { unwrapArray } from '@/shared/lib/apiShape'; /** * useKanban — Pipeline Pillar kanban board data * Returns leads grouped by stage. */ export function useKanban() { const query = useQuery({ queryKey: ['kanban'], queryFn: async () => { const payload = await api.get('/crm/pipeline/kanban?limit=250&offset=0'); return unwrapArray(payload, ['stages', 'kanban', 'columns', 'board']).map(normalizeStage); }, staleTime: 0, refetchInterval: 60_000, refetchOnMount: 'always', refetchOnWindowFocus: true, retry: 1, }); return { stages: query.data ?? [], isLoading: query.isLoading, isError: query.isError, error: query.error, refetch: query.refetch, }; } export interface KanbanStage { id: string; label: string; emoji: string; leads: KanbanLead[]; } export interface KanbanLead { id: string; name: string; location?: string; qdScore: number; qdDelta?: number; lastContactRelative: string; lastContactChannel: string; isVaultActive?: boolean; } interface RawKanbanStage { id?: string; stage?: string; status?: string; label?: string; name?: string; emoji?: string; leads?: unknown; items?: unknown; data?: unknown; records?: unknown; rows?: unknown; } function normalizeStage(stage: RawKanbanStage): KanbanStage { const id = String(stage.id ?? stage.stage ?? stage.status ?? 'new'); const leads = unwrapArray(stage, ['leads', 'items', 'data', 'records', 'rows']).map(normalizeLead); return { id, label: String(stage.label ?? stage.name ?? id.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())), emoji: String(stage.emoji ?? id.slice(0, 1).toUpperCase()), leads, }; } interface RawKanbanLead extends Partial { person_id?: string; lead_id?: string; client_id?: string; full_name?: string; client_name?: string; project_name?: string; projects?: string; qd_score?: number | string; score?: number | string; last_contact_relative?: string; last_contact_channel?: string; channel?: string; } function normalizeLead(lead: RawKanbanLead): KanbanLead { return { ...lead, id: String(lead.id ?? lead.person_id ?? lead.lead_id ?? lead.client_id ?? crypto.randomUUID()), name: String(lead.name ?? lead.full_name ?? lead.client_name ?? 'Unnamed Client'), location: lead.location ?? lead.project_name ?? lead.projects, qdScore: Number.isFinite(Number(lead.qdScore ?? lead.qd_score ?? lead.score)) ? Number(lead.qdScore ?? lead.qd_score ?? lead.score) : 0, lastContactRelative: String(lead.lastContactRelative ?? lead.last_contact_relative ?? 'No contact yet'), lastContactChannel: String(lead.lastContactChannel ?? lead.last_contact_channel ?? lead.channel ?? 'crm'), }; }