Initial commit: Velocity-OS migration

This commit is contained in:
2026-05-01 12:32:19 +05:30
commit 407af828d4
283 changed files with 207782 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/shared/lib/apiClient';
/**
* useClient360 — fetch unified client entity
* Feeds: CRM lead data + QD score + pipeline stage + contact info
*/
export function useClient360(personId: string) {
const query = useQuery({
queryKey: ['client360', personId],
queryFn: () => api.get<Client360Data>(`/crm/leads/${personId}/360`),
staleTime: 30_000,
enabled: !!personId,
});
return { client: query.data, isLoading: query.isLoading, error: query.error };
}
/**
* useConversations — unified comms feed for a lead
*/
export function useConversations(personId: string) {
const qc = useQueryClient();
const query = useQuery({
queryKey: ['conversations', personId],
queryFn: () => api.get<ConversationEvent[]>(`/comms/threads/${personId}`),
staleTime: 10_000,
enabled: !!personId,
});
const sendWhatsApp = (text: string) =>
api.post(`/comms/send`, { person_id: personId, channel: 'whatsapp', text })
.then(() => qc.invalidateQueries({ queryKey: ['conversations', personId] }));
return { events: query.data ?? [], isLoading: query.isLoading, sendWhatsApp };
}
/**
* useClientProperties — linked property interests
*/
export function useClientProperties(personId: string) {
const query = useQuery({
queryKey: ['client-properties', personId],
queryFn: () => api.get<PropertyInterest[]>(`/crm/leads/${personId}/properties`),
staleTime: 60_000,
enabled: !!personId,
});
return { properties: query.data ?? [], isLoading: query.isLoading };
}
/**
* useClientTasks — tasks for a specific lead
*/
export function useClientTasks(personId: string) {
const qc = useQueryClient();
const query = useQuery({
queryKey: ['client-tasks', personId],
queryFn: () => api.get<Task[]>(`/crm/leads/${personId}/tasks`),
staleTime: 30_000,
enabled: !!personId,
});
const markDone = (taskId: string) =>
api.patch(`/crm/tasks/${taskId}`, { status: 'done' })
.then(() => qc.invalidateQueries({ queryKey: ['client-tasks', personId] }));
const snooze = (taskId: string) =>
api.patch(`/crm/tasks/${taskId}`, { status: 'snoozed' })
.then(() => qc.invalidateQueries({ queryKey: ['client-tasks', personId] }));
// Group tasks
const all = query.data ?? [];
const tasks = all.map(t => ({
...t,
group: t.status === 'done' ? 'completed'
: t.isDueToday ? 'today'
: 'upcoming',
})) as any[];
return { tasks, isLoading: query.isLoading, markDone, snooze };
}
// ── Types ────────────────────────────────────────────────────
export interface Client360Data {
id: string;
name: string;
location?: string;
primaryPhone?: string;
avatarUrl?: string;
qdScore: number;
qdDelta: number;
stageName: string;
stageEmoji: string;
lastContactRelative: string;
lastContactChannel: string;
aiInsight?: string;
extractedFacts?: Record<string, string>;
objections?: string[];
qdHistory?: { date: string; score: number; label?: string }[];
}
interface ConversationEvent {
id: string;
type: 'whatsapp' | 'call' | 'email';
timestamp: string;
timestampRelative: string;
messages?: { sender: 'client' | 'you'; text: string; status?: '✓' | '✓✓' }[];
duration?: string;
direction?: 'inbound' | 'outbound';
keyMoments?: string[];
hasTranscript?: boolean;
subject?: string;
}
interface PropertyInterest {
id: string;
projectName: string;
unitName: string;
config: string;
area: string;
price: string;
thumbnailUrl?: string;
isPrimary: boolean;
engagementLevel: 'High' | 'Medium' | 'Low';
}
interface Task {
id: string;
label: string;
dueAt?: string;
status: 'pending' | 'done' | 'snoozed';
isDueToday?: boolean;
isAIGenerated?: boolean;
}