80 lines
3.0 KiB
TypeScript
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]);
|
|
}
|