Add web
This commit is contained in:
133
web/pages/CallLogs.tsx
Normal file
133
web/pages/CallLogs.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Download, Search, Calendar, Filter } from 'lucide-react';
|
||||
import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Badge } from '../components/UI';
|
||||
import { mockCallLogs } from '../services/mockData';
|
||||
|
||||
export const CallLogsPage: React.FC = () => {
|
||||
const [logs] = useState(mockCallLogs);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState<'all' | 'connected' | 'missed'>('all');
|
||||
const [sourceFilter, setSourceFilter] = useState<'all' | 'debug' | 'external'>('all');
|
||||
|
||||
const filteredLogs = logs.filter(log => {
|
||||
const matchesSearch = log.agentName.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesStatus = statusFilter === 'all' || log.status === statusFilter;
|
||||
const matchesSource = sourceFilter === 'all' || log.source === sourceFilter;
|
||||
return matchesSearch && matchesStatus && matchesSource;
|
||||
});
|
||||
|
||||
const handleExport = () => {
|
||||
// Generate CSV content
|
||||
const headers = ['ID', 'Agent', 'Source', 'Status', 'Start Time', 'Duration'];
|
||||
const rows = filteredLogs.map(log => [
|
||||
log.id,
|
||||
log.agentName,
|
||||
log.source,
|
||||
log.status,
|
||||
log.startTime,
|
||||
log.duration
|
||||
].join(','));
|
||||
const csvContent = "data:text/csv;charset=utf-8," + [headers.join(','), ...headers.join(',')].join('\n');
|
||||
const encodedUri = encodeURI(csvContent);
|
||||
const link = document.createElement("a");
|
||||
link.setAttribute("href", encodedUri);
|
||||
link.setAttribute("download", "call_logs.csv");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in py-2 pb-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold tracking-tight">视频通话记录</h1>
|
||||
<Button variant="outline" onClick={handleExport}>
|
||||
<Download className="mr-2 h-4 w-4" /> 导出记录
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 bg-card/50 p-4 rounded-lg border border-white/5 shadow-sm">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索代理小助手..."
|
||||
className="pl-9 border-0 bg-white/5"
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Filter className="h-4 w-4 text-muted-foreground" />
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card"
|
||||
value={sourceFilter}
|
||||
onChange={(e) => setSourceFilter(e.target.value as any)}
|
||||
>
|
||||
<option value="all">所有来源</option>
|
||||
<option value="debug">调试 (Debug)</option>
|
||||
<option value="external">外部测试 (External)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card"
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value as any)}
|
||||
>
|
||||
<option value="all">所有状态</option>
|
||||
<option value="connected">已接通</option>
|
||||
<option value="missed">未接通</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Calendar className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input type="date" className="pl-9 border-0 bg-white/5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-white/5 bg-card/40 backdrop-blur-md">
|
||||
<table className="w-full text-sm">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>编号</TableHead>
|
||||
<TableHead>代理小助手</TableHead>
|
||||
<TableHead>来源</TableHead>
|
||||
<TableHead>接听状态</TableHead>
|
||||
<TableHead>通话接通时间</TableHead>
|
||||
<TableHead>通话时长</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<tbody>
|
||||
{filteredLogs.map(log => (
|
||||
<TableRow key={log.id}>
|
||||
<TableCell className="font-mono text-xs text-muted-foreground">#{log.id}</TableCell>
|
||||
<TableCell className="font-medium">{log.agentName}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{log.source === 'debug' ? '调试' : '外部'}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={log.status === 'connected' ? 'success' : 'warning'}>
|
||||
{log.status === 'connected' ? '已接通' : '未接通'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{log.startTime}</TableCell>
|
||||
<TableCell>{log.duration}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{filteredLogs.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell className="text-center py-6 text-muted-foreground">暂无记录</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user