Files
Velocity-OS/webos/src/shared/hooks/useKanban.ts
Sagnik Ghosh 600716a69d
Some checks failed
Velocity-OS Deployment Pipeline / lint (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / notify-ingress (push) Has been cancelled
fix: complete Velocity-OS feature migration wiring
2026-05-02 22:42:26 +05:30

100 lines
2.9 KiB
TypeScript

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<unknown>('/crm/pipeline/kanban?limit=250&offset=0');
return unwrapArray<RawKanbanStage>(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<RawKanbanLead>(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<KanbanLead> {
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'),
};
}