@@ -208,6 +210,9 @@ export const EnquiryForm = ({ isOpen, onOpenChange, callerPhone, onSaved }: Enqu
{isSaving ? 'Saving...' : 'Log Enquiry'}
-
+
+
+
+
);
};
diff --git a/src/pages/team-performance.tsx b/src/pages/team-performance.tsx
index 7543260..aa618e3 100644
--- a/src/pages/team-performance.tsx
+++ b/src/pages/team-performance.tsx
@@ -107,42 +107,78 @@ export const TeamPerformancePage = () => {
setAllAppointments(appts);
// Build per-agent metrics
- const agentPerfs: AgentPerf[] = teamAgents.map((agent: any) => {
- const agentCalls = calls.filter((c: any) => c.agentName === agent.name || c.agentName === agent.ozonetelagentid);
- const agentLeads = leads.filter((l: any) => l.assignedAgent === agent.name);
- const agentFollowUps = followUps.filter((f: any) => f.assignedAgent === agent.name);
- const agentAppts = agentCalls.filter((c: any) => c.callStatus === 'COMPLETED').length; // approximate
- const totalCalls = agentCalls.length;
- const inbound = agentCalls.filter((c: any) => c.direction === 'INBOUND').length;
- const missed = agentCalls.filter((c: any) => c.callStatus === 'MISSED').length;
+ let agentPerfs: AgentPerf[];
- const tb = agent.timeBreakdown;
- const idleSec = tb ? parseTime(tb.totalIdleTime ?? '0:0:0') : 0;
- const activeSec = tb ? parseTime(tb.totalBusyTime ?? '0:0:0') : 0;
- const wrapSec = tb ? parseTime(tb.totalWrapupTime ?? '0:0:0') : 0;
- const breakSec = tb ? parseTime(tb.totalPauseTime ?? '0:0:0') : 0;
+ if (teamAgents.length > 0) {
+ // Real Ozonetel data available
+ agentPerfs = teamAgents.map((agent: any) => {
+ const agentCalls = calls.filter((c: any) => c.agentName === agent.name || c.agentName === agent.ozonetelagentid);
+ const agentLeads = leads.filter((l: any) => l.assignedAgent === agent.name);
+ const agentFollowUps = followUps.filter((f: any) => f.assignedAgent === agent.name);
+ const agentAppts = agentCalls.filter((c: any) => c.callStatus === 'COMPLETED').length;
+ const totalCalls = agentCalls.length;
+ const inbound = agentCalls.filter((c: any) => c.direction === 'INBOUND').length;
+ const missed = agentCalls.filter((c: any) => c.callStatus === 'MISSED').length;
- return {
- name: agent.name ?? agent.ozonetelagentid,
- ozonetelagentid: agent.ozonetelagentid,
- npsscore: agent.npsscore,
- maxidleminutes: agent.maxidleminutes,
- minnpsthreshold: agent.minnpsthreshold,
- minconversionpercent: agent.minconversionpercent,
- calls: totalCalls,
- inbound,
- missed,
- followUps: agentFollowUps.length,
- leads: agentLeads.length,
- appointments: agentAppts,
- convPercent: totalCalls > 0 ? Math.round((agentAppts / totalCalls) * 100) : 0,
- idleMinutes: Math.round(idleSec / 60),
- activeMinutes: Math.round(activeSec / 60),
- wrapMinutes: Math.round(wrapSec / 60),
- breakMinutes: Math.round(breakSec / 60),
- timeBreakdown: tb,
- };
- });
+ const tb = agent.timeBreakdown;
+ const idleSec = tb ? parseTime(tb.totalIdleTime ?? '0:0:0') : 0;
+ const activeSec = tb ? parseTime(tb.totalBusyTime ?? '0:0:0') : 0;
+ const wrapSec = tb ? parseTime(tb.totalWrapupTime ?? '0:0:0') : 0;
+ const breakSec = tb ? parseTime(tb.totalPauseTime ?? '0:0:0') : 0;
+
+ return {
+ name: agent.name ?? agent.ozonetelagentid,
+ ozonetelagentid: agent.ozonetelagentid,
+ npsscore: agent.npsscore,
+ maxidleminutes: agent.maxidleminutes,
+ minnpsthreshold: agent.minnpsthreshold,
+ minconversionpercent: agent.minconversionpercent,
+ calls: totalCalls,
+ inbound,
+ missed,
+ followUps: agentFollowUps.length,
+ leads: agentLeads.length,
+ appointments: agentAppts,
+ convPercent: totalCalls > 0 ? Math.round((agentAppts / totalCalls) * 100) : 0,
+ idleMinutes: Math.round(idleSec / 60),
+ activeMinutes: Math.round(activeSec / 60),
+ wrapMinutes: Math.round(wrapSec / 60),
+ breakMinutes: Math.round(breakSec / 60),
+ timeBreakdown: tb,
+ };
+ });
+ } else {
+ // Fallback: build agent list from call records
+ const agentNames = [...new Set(calls.map((c: any) => c.agentName).filter(Boolean))] as string[];
+ agentPerfs = agentNames.map((name) => {
+ const agentCalls = calls.filter((c: any) => c.agentName === name);
+ const agentLeads = leads.filter((l: any) => l.assignedAgent === name);
+ const agentFollowUps = followUps.filter((f: any) => f.assignedAgent === name);
+ const completed = agentCalls.filter((c: any) => c.callStatus === 'COMPLETED').length;
+ const totalCalls = agentCalls.length;
+
+ return {
+ name,
+ ozonetelagentid: name,
+ npsscore: null,
+ maxidleminutes: null,
+ minnpsthreshold: null,
+ minconversionpercent: null,
+ calls: totalCalls,
+ inbound: agentCalls.filter((c: any) => c.direction === 'INBOUND').length,
+ missed: agentCalls.filter((c: any) => c.callStatus === 'MISSED').length,
+ followUps: agentFollowUps.length,
+ leads: agentLeads.length,
+ appointments: completed,
+ convPercent: totalCalls > 0 ? Math.round((completed / totalCalls) * 100) : 0,
+ idleMinutes: 0,
+ activeMinutes: 0,
+ wrapMinutes: 0,
+ breakMinutes: 0,
+ timeBreakdown: null,
+ };
+ });
+ }
setAgents(agentPerfs);
} catch (err) {
@@ -329,6 +365,9 @@ export const TeamPerformancePage = () => {
Time Breakdown
+ {teamAvg.active === 0 && teamAvg.idle === 0 && teamAvg.wrap === 0 && teamAvg.break_ === 0 && (
+
Time utilisation data unavailable — requires Ozonetel agent session data.
+ )}
@@ -378,6 +417,12 @@ export const TeamPerformancePage = () => {
Overall NPS
+ {agents.every(a => a.npsscore == null) ? (
+
+
NPS data unavailable — configure NPS scores on agent profiles.
+
+ ) : (
+ <>
{agents.filter(a => a.npsscore != null).map(a => (
@@ -390,6 +435,8 @@ export const TeamPerformancePage = () => {
))}
+ >
+ )}
Conversion Metrics