Call History & Reporting
Track and analyze all call activity across inbound queues, manual dialer, and campaign calls. View comprehensive call details, filter by multiple criteria, and export data for external analysis.
Quick Access
- 👤 Agent
- 👥 Manager
- 🔧 Admin
- 💻 Developer
Your Call History
Access your personal call history to review completed calls and track your activity.
Navigation: Dashboard → Call History
What You See: Only calls you handled
Team Call History
Monitor team performance and generate reports for your department.
Navigation: Dashboard → Call History
What You See: All calls from agents in your department(s)
System-Wide Call History
Full visibility into all call activity across your organization.
Navigation: Dashboard → Call History
What You See: All calls from all agents
API & Integration
Programmatic access to call history data for custom integrations.
API Endpoint: GET /api/call-history
Authentication: Sanctum Bearer Token
Overview
The Call History feature provides a unified interface for tracking all call types:
- Inbound Queue Calls - Customer calls routed through your inbound queues
- Manual Dialer Calls - Outbound calls initiated by agents
- Campaign Calls - Automated campaign outreach
Key Features
✅ Real-time Updates - Calls appear immediately after completion ✅ Advanced Filtering - Date ranges, agents, campaigns, direction, status ✅ Role-Based Access - Scoped visibility based on user permissions ✅ CSV Export - Download filtered results for analysis ✅ Detailed Call Records - Duration, status, pricing, recordings
- 👤 Agent View
- 👥 Manager View
- 🔧 Admin View
- 💻 Developer Guide
Agent View
Accessing Call History
- Log into your DutyCall dashboard
- Navigate to Call History in the left sidebar
- Your personal call history will load automatically
What Calls You Can See
Agents can only view their own calls. You'll see:
- Inbound queue calls you accepted
- Manual dialer calls you initiated
- Campaign calls assigned to you
You cannot see calls handled by other agents.
Using Filters
Date Range Filters
Quick filters for common time periods:
- Today - All calls from today
- Yesterday - All calls from yesterday
- Last 7 Days - Past week of activity
- Last 30 Days - Past month of activity
- This Month - Current calendar month
Custom Date Range:
- Click the date range dropdown
- Select "Custom Range"
- Choose start and end dates
- Click "Apply"
Search by Phone Number
- Enter a phone number in the search box
- Searches both caller numbers and your assigned numbers
- Results update automatically as you type
Filter by Direction
- Inbound - Calls from customers to you
- Outbound - Calls you made to customers
- All - Show both types
Filter by Status
- Completed - Successfully connected calls
- No Answer - Calls where contact didn't answer
- Busy - Contact line was busy
- Failed - Call connection failed
- All - Show all statuses
Viewing Call Details
Each call record shows:
| Column | Description |
|---|---|
| Date/Time | When the call occurred (with relative time) |
| Caller | Phone number and contact name (if available) |
| My Number | The Twilio number used |
| Direction | Inbound (blue badge) or Outbound (green badge) |
| Status | Call outcome with colored indicator |
| Duration | Call length in MM:SS format |
To view more details:
- Click the "View Details" button on any call
- See full call information including:
- Complete call timeline
- Recording playback (if available)
- Call SID for support requests
Exporting Your Calls
- Apply any filters you want (date range, status, etc.)
- Click the Export CSV button in the top right
- A CSV file will download with your filtered results
CSV includes:
- Date/Time of call
- Caller phone number
- Your assigned number
- Direction (Inbound/Outbound)
- Status
- Duration
- Price (if available)
Common Tasks
How do I find calls from a specific customer?
- Use the search box at the top
- Enter the customer's phone number (any format works)
- Results will filter to show only calls with that number
How do I see all my calls from yesterday?
- Click the date range dropdown
- Select "Yesterday"
- The list will update to show only yesterday's calls
How do I export my calls for the week?
- Click the date range dropdown
- Select "Last 7 Days"
- Click the "Export CSV" button
- Open the downloaded file in Excel or Google Sheets
Export your call history weekly to track your performance and identify patterns in customer interactions.
Manager View
Accessing Team Call History
- Log into your DutyCall dashboard
- Navigate to Call History in the left sidebar
- Team call history loads automatically
What Calls You Can See
Managers see all calls from agents in their department(s). You'll see:
- Inbound queue calls handled by your team
- Manual dialer calls made by your agents
- Campaign calls assigned to your department
You cannot see calls from agents outside your department(s).
Using the Agent Filter
The agent dropdown allows you to focus on specific team members:
- Click the Agent dropdown in the filters
- See a list of all agents in your department(s)
- Select an agent to view only their calls
- Select "All Agents" to see everyone
Agent Filter Shows:
- Agent name
- Number of calls (in parentheses)
- Real-time call counts
Advanced Filtering
Date Range Reports
Generate reports for specific time periods:
Weekly Reports:
- Select "Last 7 Days"
- Choose specific agent or "All Agents"
- Export CSV for team review
Monthly Reports:
- Select "This Month"
- Filter by campaign if needed
- Export for performance analysis
Custom Periods:
- Choose "Custom Range"
- Set reporting period (e.g., pay period)
- Apply filters and export
Campaign Filtering
Track campaign performance:
- Select campaign from dropdown
- See all calls for that campaign
- Filter by agent to see individual contribution
- Export for campaign ROI analysis
Multi-Filter Reports
Combine filters for detailed insights:
Example: Agent Performance Report
- Date Range: Last 30 Days
- Agent: Select specific agent
- Direction: Outbound
- Status: Completed
- Export CSV
Example: Campaign Effectiveness
- Date Range: Campaign dates
- Campaign: Select campaign
- Direction: Outbound
- Status: All
- Export and analyze completion rates
Team Reporting Use Cases
Daily Team Performance
Morning Briefing Report:
- Filter: Yesterday
- Agent: All Agents
- Export CSV
- Review in team meeting
Agent Productivity Analysis
Individual Agent Report:
- Filter: Last 30 Days
- Select specific agent
- Note metrics:
- Total calls
- Average duration
- Completion rate
- Export for 1-on-1 review
Department Metrics
Monthly Department Report:
- Filter: This Month
- Agent: All Agents
- Export CSV
- Analyze:
- Total call volume
- Peak call times
- Average handle time
- Success rates by agent
Exporting Team Reports
CSV Export Includes:
- Complete call details
- Agent assignments
- Campaign information
- Duration and pricing
Best Practices:
- Filter first, then export (smaller files)
- Include agent filter for individual reports
- Use date ranges for trend analysis
- Export regularly for historical tracking
Common Manager Tasks
How do I see which agent is making the most calls?
- Set date range (e.g., "This Month")
- Keep "All Agents" selected
- The table shows agent names - sort by count
- Or export CSV and analyze in Excel with pivot tables
How do I generate a weekly team report?
- Select "Last 7 Days" from date range
- Keep "All Agents" selected
- Add any other filters (campaign, direction)
- Click "Export CSV"
- Share CSV with team or leadership
How do I track a specific campaign's performance?
- Select the campaign from dropdown
- Set date range to campaign duration
- View completion rates in Status column
- Export CSV for detailed analysis
- Calculate conversion rates in spreadsheet
How do I monitor an agent's performance?
- Select agent from Agent dropdown
- Set date range (e.g., "Last 30 Days")
- Review metrics:
- Call volume
- Completion rates
- Average duration
- Export for performance review documentation
Set up a weekly routine: Every Monday morning, export last week's call history filtered by your department. Track trends month-over-month to identify coaching opportunities and celebrate successes.
Managers can only access calls from agents in their assigned department(s). To view calls from other departments, contact your admin to adjust department assignments.
Admin View
System-Wide Access
Admins have complete visibility into all call activity across the entire organization. You can:
- View calls from all agents
- Access all departments
- See all campaigns
- Export complete datasets
- Analyze system-wide metrics
Advanced Admin Features
Full Agent Access
Unlike managers (who see only their department), admins see all agents in the agent dropdown:
- All departments
- All roles
- Complete agent roster
Use Cases:
- Cross-department analysis
- Organization-wide reporting
- Agent comparison across teams
- System utilization metrics
Campaign Analysis
Complete Campaign Visibility:
- All active campaigns
- Historical campaigns
- Cross-department campaigns
- System-wide campaign metrics
Campaign ROI Analysis:
- Select campaign
- Set date range to campaign duration
- Export CSV
- Calculate:
- Total calls made
- Completion rate
- Average call duration
- Total cost (price column)
- Cost per completed call
Bulk Operations
System-Wide Exports:
Filter and export large datasets:
- All calls from specific month
- All agent activity
- All campaign results
- Complete audit trails
Example: Monthly System Report
Date Range: Last Month
Agent: All Agents
Campaign: All Campaigns
Export CSV → 50,000+ records
Status-Based Filtering
System Health Monitoring:
Track call failures across the system:
- Filter: Last 7 Days
- Status: Failed
- Review patterns:
- Specific agents struggling?
- System-wide issues?
- Campaign configuration problems?
Quality Assurance:
- Filter: Completed calls
- Sort by duration
- Identify outliers (too short/too long)
- Flag for call recording review
Admin Reporting Workflows
Daily System Health Check
Morning Routine:
- Filter: Yesterday
- Agent: All Agents
- Review:
- Total call volume
- Failure rates
- System performance issues
- Export for records
Weekly Executive Report
Every Monday:
- Filter: Last 7 Days
- Export complete dataset
- Analyze in Excel:
- Calls per agent
- Department performance
- Campaign effectiveness
- Cost analysis
Monthly Business Intelligence
End of Month:
- Filter: This Month
- Export full dataset
- Create executive dashboard:
- Total calls handled
- Average handle time
- Cost per call
- Agent productivity trends
- Campaign ROI
Configuration & Setup
System Settings
Admin-Only Configuration:
- Twilio webhook URLs
- Call recording settings
- Data retention policies
- Export permissions
Some features in Call History require admin permissions:
- View all departments
- Access historical data beyond 90 days
- Configure system-wide settings
- Manage data retention
Troubleshooting
Missing Calls
Diagnosis Steps:
- Check Twilio webhook configuration
- Review Laravel logs:
storage/logs/laravel.log - Verify database tables:
SELECT COUNT(*) FROM call_histories;
SELECT COUNT(*) FROM twilio_call_logs; - Test webhook endpoints
Performance Issues
Large Dataset Optimization:
- Use specific date ranges (avoid "All Time")
- Filter by department or agent first
- Export in smaller chunks
- Consider archiving old data
Role-Based Access Issues
Agent Can't See Calls:
- Verify user role in database
- Check department assignments
- Review role permissions
- Check queue assignments for inbound calls
Admins have access to all call data. Handle exports with care:
- Don't share raw CSV files publicly
- Redact sensitive information before distribution
- Follow company data protection policies
- Use secure channels for file sharing
Developer Guide
API Overview
The Call History API provides programmatic access to call records with role-based filtering, pagination, and export capabilities.
Base URL: https://your-domain.com/api
Authentication: Sanctum Bearer Token
Endpoints
Get Call History (Paginated)
GET /api/call-history
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 20 | Records per page (max: 100) |
from_date | date | - | Filter start date (YYYY-MM-DD) |
to_date | date | - | Filter end date (YYYY-MM-DD) |
agent_id | integer | - | Filter by agent ID |
campaign_id | integer | - | Filter by campaign ID |
direction | string | - | Filter by direction (inbound or outbound) |
status | string | - | Filter by status |
search | string | - | Search phone numbers, names, campaigns |
Example Request (cURL):
curl -X GET "https://your-domain.com/api/call-history?page=1&per_page=20&from_date=2025-10-01&direction=inbound" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Example Request (JavaScript):
const response = await fetch('https://your-domain.com/api/call-history?page=1&per_page=20', {
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
Example Request (PHP):
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://your-domain.com/api/call-history?page=1&per_page=20');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_ACCESS_TOKEN',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$data = json_decode($response, true);
curl_close($ch);
?>
Success Response (200 OK):
{
"success": true,
"data": [
{
"id": 1,
"call_sid": "CA6f56fcd0474068eaf4b9d4db40d01144",
"caller_number": "+15109309015",
"my_number": "+16282373889",
"contact_name": null,
"agent_id": 4,
"agent_name": "Agent User",
"campaign_id": null,
"campaign_name": null,
"direction": "inbound",
"status": "completed",
"duration": 98,
"created_at": "2025-10-09 13:38:02",
"price": null,
"source": "call_history"
}
],
"current_page": "1",
"per_page": "20",
"total": 1,
"last_page": 1,
"message": "Call history retrieved successfully"
}
Get Single Call Record
GET /api/call-history/{id}?source={source}
Path Parameters:
id- The call record ID
Query Parameters:
source- Data source:twilio_call_logorcall_history
Example Request:
curl -X GET "https://your-domain.com/api/call-history/123?source=call_history" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Export Call History (CSV)
GET /api/call-history/export/csv
Query Parameters: Same as list endpoint
Response: CSV file download
CSV Headers:
Date/Time, Caller, My Number, Agent, Campaign, Direction, Status, Duration, Price
Example Request:
curl -X GET "https://your-domain.com/api/call-history/export/csv?from_date=2025-10-01&to_date=2025-10-31" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
--output calls.csv
Role-Based Scoping
The API automatically filters results based on the authenticated user's role:
| Role | Access Level |
|---|---|
| Agent | Only their own calls |
| Department Manager | Calls from agents in their department(s) |
| Account Admin | All calls in their tenant |
| Super Admin | All calls across all tenants |
Implementation:
// Automatic scoping in controller
switch ($user->role) {
case 'agent':
$query->where('agent_id', $user->id);
break;
case 'dept_manager':
$agentIds = $this->getDepartmentAgentIds($user);
$query->whereIn('agent_id', $agentIds);
break;
// ... etc
}
Data Architecture
Data Sources
The API merges data from two database tables:
-
twilio_call_logs- Manual dialer and campaign calls- Direction: Outbound
- Contains: Campaign data, contact info, pricing
-
call_histories- Inbound queue calls- Direction: Inbound
- Contains: Queue data, agent assignments, call duration
Union Query
Both tables are queried and merged using SQL UNION:
$twilioQuery = TwilioCallLog::select([
'id',
'call_sid',
'to_number as caller_number',
'from_number as my_number',
// ... more fields
DB::raw("'twilio_call_log' as source")
]);
$callHistoryQuery = CallHistory::select([
'id',
'dial_call_sid as call_sid',
'caller_number',
'my_number',
// ... more fields
DB::raw("'call_history' as source")
]);
$unionQuery = $twilioQuery->union($callHistoryQuery);
Database Schema
call_histories table:
CREATE TABLE call_histories (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
agent_id BIGINT UNSIGNED,
my_number VARCHAR(255) NOT NULL,
caller_number VARCHAR(255) NOT NULL,
dial_call_sid VARCHAR(255),
campaign_id BIGINT UNSIGNED,
pick_up_time TIMESTAMP NOT NULL,
hang_up_time TIMESTAMP NOT NULL,
status VARCHAR(255) NOT NULL,
record_file VARCHAR(255),
created_at TIMESTAMP,
updated_at TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_pick_up_time (pick_up_time)
);
twilio_call_logs table:
CREATE TABLE twilio_call_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
campaign_id BIGINT UNSIGNED,
contact_id BIGINT UNSIGNED,
agent_id BIGINT UNSIGNED,
call_sid VARCHAR(255) NOT NULL UNIQUE,
from_number VARCHAR(255) NOT NULL,
to_number VARCHAR(255) NOT NULL,
status VARCHAR(255) NOT NULL,
direction VARCHAR(255) NOT NULL,
duration INTEGER,
start_time TIMESTAMP,
end_time TIMESTAMP,
price DECIMAL(10,4),
recording_url TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP,
INDEX idx_agent_id (agent_id),
INDEX idx_start_time (start_time)
);
Backend Implementation
Controller Structure
Location: /backend/app/Http/Controllers/Api/CallHistoryController.php
Key Methods:
index() - Get paginated call history
public function index(Request $request)
{
$user = auth()->user();
// Build queries for both tables
$twilioQuery = TwilioCallLog::select([...]);
$callHistoryQuery = CallHistory::select([...]);
// Apply role-based scoping
$this->applyScopingToQueries($twilioQuery, $callHistoryQuery, $user);
// Apply filters
$this->applyFilters($twilioQuery, $callHistoryQuery, $request);
// Union and paginate
$unionQuery = $twilioQuery->union($callHistoryQuery);
$calls = DB::table(DB::raw("({$unionQuery->toSql()}) as calls"))
->mergeBindings($unionQuery->getQuery())
->orderBy('created_at', 'desc')
->paginate($perPage);
return response()->json([
'success' => true,
'data' => $calls->items(),
'current_page' => (string) $calls->currentPage(),
'per_page' => (string) $calls->perPage(),
'total' => $calls->total(),
'last_page' => $calls->lastPage(),
'message' => 'Call history retrieved successfully'
]);
}
applyScopingToQueries() - Role-based filtering
private function applyScopingToQueries($twilioQuery, $callHistoryQuery, $user)
{
switch ($user->role) {
case 'agent':
$twilioQuery->where('agent_id', $user->id);
$callHistoryQuery->where('user_id', $user->id);
break;
case 'dept_manager':
$agentIds = DB::table('department_agents')
->join('departments', 'departments.id', '=', 'department_agents.department_id')
->where('departments.customer_id', $user->customer_id)
->pluck('department_agents.agent_id');
$twilioQuery->whereIn('agent_id', $agentIds);
$callHistoryQuery->whereIn('user_id', $agentIds);
break;
case 'account_admin':
$twilioQuery->where('customer_id', $user->customer_id);
$callHistoryQuery->whereHas('user', function($q) use ($user) {
$q->where('customer_id', $user->customer_id);
});
break;
case 'super_admin':
// No restrictions
break;
}
}
applyFilters() - Query filtering
private function applyFilters($twilioQuery, $callHistoryQuery, $request)
{
// Date range
if ($request->has('from_date')) {
$twilioQuery->whereDate('start_time', '>=', $request->from_date);
$callHistoryQuery->whereDate('pick_up_time', '>=', $request->from_date);
}
if ($request->has('to_date')) {
$twilioQuery->whereDate('start_time', '<=', $request->to_date);
$callHistoryQuery->whereDate('pick_up_time', '<=', $request->to_date);
}
// Agent filter
if ($request->has('agent_id')) {
$twilioQuery->where('agent_id', $request->agent_id);
$callHistoryQuery->where('user_id', $request->agent_id);
}
// Campaign filter
if ($request->has('campaign_id')) {
$twilioQuery->where('campaign_id', $request->campaign_id);
$callHistoryQuery->where('campaign_id', $request->campaign_id);
}
// Direction filter
if ($request->has('direction')) {
$twilioQuery->where('direction', $request->direction);
if ($request->direction === 'inbound') {
// Only call_histories has inbound calls
$twilioQuery->whereRaw('1 = 0'); // Exclude all
}
}
// Status filter
if ($request->has('status')) {
$twilioQuery->where('status', $request->status);
$callHistoryQuery->where('status', $request->status);
}
// Search
if ($request->has('search')) {
$search = $request->search;
$twilioQuery->where(function($q) use ($search) {
$q->where('to_number', 'like', "%{$search}%")
->orWhere('from_number', 'like', "%{$search}%");
});
$callHistoryQuery->where(function($q) use ($search) {
$q->where('caller_number', 'like', "%{$search}%")
->orWhere('my_number', 'like', "%{$search}%");
});
}
}
Webhook Integration
Inbound Queue Call Completion
Endpoint: POST /api/twilio/agent-call-complete
Triggered by: Twilio when agent ends queue call
Handler: TwilioWebhookController::handleAgentCallComplete()
public function handleAgentCallComplete(Request $request)
{
$callSid = $request->input('CallSid'); // Agent's WebRTC call
$dequeuedCallSid = $request->input('DequeuedCallSid'); // Caller's SID
$dequeuedCallDuration = $request->input('DequeuedCallDuration', 0);
// CRITICAL: Find queue entry by caller's SID (NOT agent's SID)
$queueEntry = QueueList::where('call_sid', $dequeuedCallSid)->first();
if ($queueEntry && $queueEntry->user_id) {
$callHistory = CallHistory::create([
'user_id' => $queueEntry->user_id,
'agent_id' => $queueEntry->user_id,
'my_number' => $queueEntry->my_number,
'caller_number' => $queueEntry->caller_number,
'dial_call_sid' => $dequeuedCallSid, // Use caller's SID
'pick_up_time' => $queueEntry->answered_at ?? $queueEntry->created_at,
'hang_up_time' => now(),
'status' => 'completed',
'record_file' => null,
]);
Log::info('Call history saved for queue call', [
'HistoryId' => $callHistory->id,
'AgentId' => $queueEntry->user_id,
'CallerSID' => $dequeuedCallSid
]);
}
return response('<Response></Response>', 200)
->header('Content-Type', 'application/xml');
}
Critical Implementation Notes:
- ✅ Use
DequeuedCallSid(caller's SID) notCallSid(agent's WebRTC SID) - ✅ Link to queue entry via caller's SID
- ✅ Calculate duration from pickup to hangup times
- ✅ Log successful saves for debugging
Frontend Integration
React Query Setup
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/teleman-api';
// Fetch call history
const { data, isLoading, error } = useQuery({
queryKey: ['callHistory', page, perPage, filters],
queryFn: () => api.getCallHistory({
page,
per_page: perPage,
from_date: filters.fromDate,
to_date: filters.toDate,
agent_id: filters.agentId,
campaign_id: filters.campaignId,
direction: filters.direction,
status: filters.status,
search: filters.search,
}),
staleTime: 5 * 60 * 1000, // 5 minutes
});
API Client Implementation
Location: /frontend/src/lib/teleman-api.ts
async getCallHistory(params: {
page?: number;
per_page?: number;
from_date?: string;
to_date?: string;
agent_id?: string;
campaign_id?: string;
direction?: string;
status?: string;
search?: string;
}): Promise<CallHistoryListResponse> {
const queryParams = new URLSearchParams(
Object.entries(params)
.filter(([_, value]) => value !== '' && value !== undefined)
.map(([key, value]) => [key, String(value)])
).toString();
const response = await this.client.get(`/api/call-history?${queryParams}`);
return response.data;
}
async exportCallHistory(params: any): Promise<void> {
const queryParams = new URLSearchParams(
Object.entries(params)
.filter(([_, value]) => value !== '' && value !== undefined)
.map(([key, value]) => [key, String(value)])
).toString();
const exportUrl = `${this.baseURL}/api/call-history/export/csv?${queryParams}`;
window.open(exportUrl, '_blank');
}
CRITICAL: Use /api/ prefix in all endpoints. The axios baseURL is http://localhost:8090 without /api/, so paths must include it.
TypeScript Definitions
Location: /frontend/src/lib/types.ts
export interface CallHistoryItem {
id: number;
call_sid: string;
caller_number?: string; // Optional - can be undefined
my_number?: string; // Optional - can be undefined
contact_name: string | null;
agent_id: number | null;
agent_name: string | null;
campaign_id: number | null;
campaign_name: string | null;
direction: 'inbound' | 'outbound';
status: string;
duration: number;
created_at?: string; // Optional - can be undefined
price: number | null;
source: 'twilio_call_log' | 'call_history';
}
export interface CallHistoryListResponse {
success: boolean;
data: CallHistoryItem[];
current_page: string;
per_page: string;
total: number;
last_page: number;
message: string;
}
IMPORTANT: Mark fields as optional (?) if they can be undefined in practice.
Setup & Configuration
Backend Setup
- Run Migrations:
cd backend
php artisan migrate
- Configure Environment:
# .env
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_PHONE_NUMBER=your_twilio_number
- Configure Twilio Webhooks:
In Twilio Console:
- Voice URL:
https://your-domain.com/api/twilio/inbound - Status Callback:
https://your-domain.com/api/twilio/status - TwiML App Voice URL:
https://your-domain.com/api/twilio/agent-dial-queue
- Update CORS:
// config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
- Add Routes:
// routes/api.php
Route::middleware(['auth:sanctum'])->group(function () {
Route::get('/call-history', [CallHistoryController::class, 'index']);
Route::get('/call-history/{id}', [CallHistoryController::class, 'show']);
Route::get('/call-history/export/csv', [CallHistoryController::class, 'exportCsv']);
});
Frontend Setup
- Install Dependencies:
cd frontend
npm install date-fns @tanstack/react-query
- Configure Environment:
# .env.local
NEXT_PUBLIC_API_URL=http://localhost:8090
- Add Navigation:
// src/components/navigation.tsx
{
name: 'Call History',
href: '/dashboard/call-history',
icon: ClockIcon,
roles: ['agent', 'dept_manager', 'account_admin', 'super_admin']
}
Performance Optimization
Database Indexing
Recommended indexes:
-- call_histories
CREATE INDEX idx_call_histories_user_id ON call_histories(user_id);
CREATE INDEX idx_call_histories_pick_up_time ON call_histories(pick_up_time);
CREATE INDEX idx_call_histories_status ON call_histories(status);
-- twilio_call_logs
CREATE INDEX idx_twilio_call_logs_agent_id ON twilio_call_logs(agent_id);
CREATE INDEX idx_twilio_call_logs_start_time ON twilio_call_logs(start_time);
CREATE INDEX idx_twilio_call_logs_campaign_id ON twilio_call_logs(campaign_id);
Query Optimization
Pagination limits:
- Default: 20 records per page
- Maximum: 100 records per page
- Large exports: Use CSV endpoint
React Query caching:
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
Debounced search:
const debouncedSearch = useMemo(
() => debounce((value: string) => {
setFilters({...filters, search: value});
}, 300),
[filters]
);
Troubleshooting
Common Issues
1. Date Parsing Errors (Safari)
Symptom: "Invalid Date" or "RangeError: Invalid time value"
Cause: Safari requires ISO 8601 format
Fix:
// Convert "YYYY-MM-DD HH:MM:SS" to ISO
const dateStr = call.created_at.replace(' ', 'T');
const date = new Date(dateStr);
2. 403 Forbidden on /api/agents
Symptom: Console shows 403 error
Cause: Agents cannot view agent list
Fix: Conditional loading
const { data: agents } = useQuery({
queryKey: ['agents'],
queryFn: () => api.getAgents(),
enabled: ['dept_manager', 'account_admin'].includes(user.role)
});
3. Calls Not Saving from Queue
Symptom: Queue calls complete but don't appear
Debug:
# Check webhook logs
tail -f storage/logs/laravel.log | grep "handleAgentCallComplete"
# Check database
sqlite3 database.sqlite "SELECT * FROM call_histories ORDER BY id DESC LIMIT 5;"
Fix: Verify webhook URL is correct and using DequeuedCallSid
4. SQLite vs MySQL Compatibility
Symptom: SQLSTATE[HY000]: General error: 1 no such column
Cause: Database-specific functions
Fix:
// MySQL
DB::raw('TIMESTAMPDIFF(SECOND, pick_up_time, hang_up_time) as duration')
// SQLite
DB::raw('(strftime("%s", hang_up_time) - strftime("%s", pick_up_time)) as duration')
Testing
API Testing (cURL)
# Get call history
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:8090/api/call-history?page=1&per_page=20"
# Get with filters
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:8090/api/call-history?from_date=2025-10-01&direction=inbound"
# Export CSV
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:8090/api/call-history/export/csv?from_date=2025-10-01" \
--output calls.csv
Manual Testing Checklist
As Agent:
- Login as agent
- Make inbound queue call
- Accept and complete call
- Verify call appears in history
- Verify only own calls visible
- Test date filters
- Test search
- Export CSV
As Manager:
- Login as manager
- Verify sees agent calls
- Test agent filter dropdown
- Test export CSV
- Verify role-based scoping
As Admin:
- Login as admin
- Verify sees all calls
- Test all filters
- Verify pagination
- Test bulk export
Support & Resources
File Locations:
- Controller:
/backend/app/Http/Controllers/Api/CallHistoryController.php - Webhook:
/backend/app/Http/Controllers/TwilioWebhookController.php - Routes:
/backend/routes/api.php - Frontend Page:
/frontend/src/app/dashboard/call-history/page.tsx - API Client:
/frontend/src/lib/teleman-api.ts
Related Documentation:
Related Documentation
- Inbound Queue System - Learn about inbound call routing
- Manual Dialer - Outbound calling setup
- Campaigns - Campaign management
- API Reference - Complete API documentation
Need Help?
- 💬 Support: Contact Support
- 📖 Docs: Documentation Home
- 🐛 Bug Reports: GitHub Issues