sud-life-insurance/src/api/meetings.ts
2026-07-03 14:52:45 +05:30

80 lines
3.0 KiB
TypeScript

// Derive a flat meeting/booking list from the shared lead recordview.
// One lead row carries at most one meeting (meeting_datetime + mode + address),
// owned by owner_agent. No separate agent/meeting table exists — the agent
// roster is whatever owner_agent values the leads carry.
import { useMemo } from 'react';
import { useLeads } from './leads';
import type { LeadRecord } from './types';
export interface Meeting {
leadId: string;
leadName: string;
/** Normalized agent display name, or 'Unassigned'. */
agent: string;
agentId: number | null;
/** ISO datetime of the slot. */
datetime: string;
/** Raw mode key (video | phone | branch | home_visit | …). */
mode: string;
/** Zoom link / branch name / address — may be empty. */
address?: string;
/** current_state_name — where the lead sits in the lifecycle. */
stage: string;
interest?: string;
}
const UNASSIGNED = 'Unassigned';
// owner_agent arrives as {id,name} (clean lookup rows), a bare string (legacy /
// test rows), or null. Flatten to a stable {name,id}.
function normAgent(raw: unknown): { name: string; id: number | null } {
if (raw && typeof raw === 'object') {
const o = raw as { id?: number; name?: string };
const name = (o.name ?? '').trim();
return { name: name || UNASSIGNED, id: typeof o.id === 'number' ? o.id : null };
}
if (typeof raw === 'string') {
const name = raw.trim();
return { name: name || UNASSIGNED, id: null };
}
return { name: UNASSIGNED, id: null };
}
function recordToMeeting(r: LeadRecord): Meeting | null {
if (!r.meeting_datetime) return null;
const dt = new Date(r.meeting_datetime);
if (Number.isNaN(dt.getTime())) return null;
const agent = normAgent(r.owner_agent);
const address = typeof r.meeting_address === 'string' ? r.meeting_address.trim() : '';
return {
leadId: String(r.instance_id),
leadName: r.lead_name ?? `Lead ${r.instance_id}`,
agent: agent.name,
agentId: agent.id,
datetime: r.meeting_datetime,
mode: (r.meeting_mode ?? '').toLowerCase(),
address: address || undefined,
stage: r.current_state_name ?? '—',
interest: r.product_interest ?? undefined,
};
}
export function useMeetings(): { meetings: Meeting[]; agents: string[]; loading: boolean; error: string | null } {
const { records, loading, error } = useLeads();
return useMemo(() => {
const meetings = records
.map(recordToMeeting)
.filter((m): m is Meeting => m !== null)
.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
// Roster ordered by meeting load, Unassigned always last.
const counts = new Map<string, number>();
for (const m of meetings) counts.set(m.agent, (counts.get(m.agent) ?? 0) + 1);
const agents = [...counts.keys()].sort((a, b) => {
if (a === UNASSIGNED) return 1;
if (b === UNASSIGNED) return -1;
return (counts.get(b) ?? 0) - (counts.get(a) ?? 0);
});
return { meetings, agents, loading, error };
}, [records, loading, error]);
}