2025-06-09 11:13:05 +00:00
|
|
|
"use client"
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
import { useState, useEffect } from "react"
|
2025-06-09 11:13:05 +00:00
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
import { Input } from "@/components/ui/input"
|
|
|
|
import { Label } from "@/components/ui/label"
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
import { Alert, AlertDescription } from "@/components/ui/alert"
|
2025-06-13 21:14:16 +00:00
|
|
|
import { Download, Search, Loader2, DollarSign, TrendingUp, Calculator } from "lucide-react"
|
2025-06-09 11:13:05 +00:00
|
|
|
|
|
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
|
|
|
|
|
2025-06-16 19:26:37 +00:00
|
|
|
function formatDateBr(dateStr: string) {
|
2025-06-13 21:14:16 +00:00
|
|
|
const [year, month, day] = dateStr.split("-");
|
|
|
|
return `${day}/${month}/${year}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatDateTime(raw?: string): string {
|
|
|
|
if (!raw || raw === "-") return "-"
|
|
|
|
const iso = raw.replace(" ", "T")
|
|
|
|
const date = new Date(iso)
|
|
|
|
if (isNaN(date.getTime())) return "-"
|
|
|
|
return (
|
|
|
|
date.toLocaleDateString("pt-BR", {
|
|
|
|
day: "2-digit",
|
|
|
|
month: "2-digit",
|
|
|
|
year: "numeric",
|
|
|
|
}) +
|
|
|
|
" " +
|
|
|
|
date.toLocaleTimeString("pt-BR", {
|
|
|
|
hour: "2-digit",
|
|
|
|
minute: "2-digit",
|
|
|
|
second: "2-digit",
|
|
|
|
hour12: false,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2025-06-09 11:13:05 +00:00
|
|
|
|
|
|
|
interface ClientTranscriptionData {
|
|
|
|
uniqueid: string
|
|
|
|
src: string
|
|
|
|
dst: string
|
|
|
|
start_call: string
|
|
|
|
total_billsec: number
|
|
|
|
total_min: string
|
2025-06-12 20:58:22 +00:00
|
|
|
client_total_cost: string
|
2025-06-09 11:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface HitTranscriptionData {
|
2025-06-12 20:58:22 +00:00
|
|
|
companyId: string // Empresa
|
|
|
|
uniqueid: string // Identificador da chamada
|
|
|
|
src: string // Origem
|
|
|
|
dst: string // Destino
|
|
|
|
total_billsec: number // Quantidade de segundos
|
|
|
|
qtd_token_input: number // Quantidade de tokens(input)
|
|
|
|
qtd_token_output: number // Quantidade de tokens(output)
|
2025-06-13 21:14:16 +00:00
|
|
|
total_min: number,
|
|
|
|
custo_hit: string // Custo HIT
|
2025-06-12 20:58:22 +00:00
|
|
|
client_total_cost: string // Custo Cliente
|
|
|
|
client_price: string // Preço Cliente por Minuto
|
|
|
|
start_call: string // Inicio
|
|
|
|
end_call: string // Fim
|
2025-06-09 11:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type TranscriptionData = ClientTranscriptionData | HitTranscriptionData
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
// Interface para informações de custo
|
|
|
|
interface CostInfo {
|
|
|
|
company_id: string
|
|
|
|
start_date: string
|
|
|
|
end_date: string
|
|
|
|
total_cost_hit: number
|
|
|
|
total_client_cost: number
|
|
|
|
}
|
|
|
|
|
|
|
|
// Interface para cotação do dólar
|
|
|
|
interface ExchangeRateResponse {
|
|
|
|
USDBRL: {
|
|
|
|
code: string
|
|
|
|
codein: string
|
|
|
|
name: string
|
|
|
|
high: string
|
|
|
|
low: string
|
|
|
|
varBid: string
|
|
|
|
pctChange: string
|
|
|
|
bid: string
|
|
|
|
ask: string
|
|
|
|
timestamp: string
|
|
|
|
create_date: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
interface PaginationInfo {
|
|
|
|
total: number
|
|
|
|
page: number
|
|
|
|
page_size: number
|
|
|
|
total_pages: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ApiResponse {
|
|
|
|
success: boolean
|
|
|
|
data: {
|
|
|
|
data: TranscriptionData[]
|
|
|
|
pagination: PaginationInfo
|
2025-06-13 21:14:16 +00:00
|
|
|
cost?: CostInfo
|
2025-06-09 11:13:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function TranscriptionTable() {
|
|
|
|
const [data, setData] = useState<TranscriptionData[]>([])
|
2025-06-13 21:14:16 +00:00
|
|
|
const [costInfo, setCostInfo] = useState<CostInfo | null>(null)
|
|
|
|
const [exchangeRate, setExchangeRate] = useState<number | null>(null)
|
2025-06-09 11:13:05 +00:00
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const [isExporting, setIsExporting] = useState(false)
|
|
|
|
const [error, setError] = useState("")
|
|
|
|
|
|
|
|
// Filtros
|
|
|
|
const [companyId, setCompanyId] = useState("")
|
|
|
|
const [startDate, setStartDate] = useState("")
|
|
|
|
const [endDate, setEndDate] = useState("")
|
|
|
|
const [who, setWho] = useState<"client" | "hit">("client")
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
// Estados para paginação
|
2025-06-09 11:13:05 +00:00
|
|
|
const [pagination, setPagination] = useState<PaginationInfo>({
|
|
|
|
total: 0,
|
|
|
|
page: 1,
|
|
|
|
page_size: 20,
|
|
|
|
total_pages: 0,
|
|
|
|
})
|
|
|
|
const [currentPage, setCurrentPage] = useState(1)
|
|
|
|
const [pageSize, setPageSize] = useState(20)
|
|
|
|
|
|
|
|
const getAuthHeaders = () => {
|
|
|
|
const token = localStorage.getItem("access_token")
|
|
|
|
return {
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
// Função para buscar a cotação do dólar
|
|
|
|
const fetchExchangeRate = async () => {
|
|
|
|
try {
|
|
|
|
const response = await fetch("https://economia.awesomeapi.com.br/json/last/USD-BRL")
|
|
|
|
if (response.ok) {
|
|
|
|
const data: ExchangeRateResponse = await response.json()
|
|
|
|
const rate = Number.parseFloat(data.USDBRL.ask) // Usando o valor de venda (ask)
|
|
|
|
setExchangeRate(rate)
|
|
|
|
return rate
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.log("Erro ao buscar cotação do dólar:", err)
|
|
|
|
// Usar uma cotação padrão em caso de erro
|
|
|
|
setExchangeRate(5.5)
|
|
|
|
return 5.5
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
const fetchData = async (page: number = currentPage) => {
|
|
|
|
if (!companyId || !startDate || !endDate) {
|
|
|
|
setError("Preencha todos os campos obrigatórios")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
setError("")
|
|
|
|
|
|
|
|
try {
|
2025-06-13 21:14:16 +00:00
|
|
|
// Buscar cotação do dólar em paralelo
|
|
|
|
const ratePromise = fetchExchangeRate()
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
const params = new URLSearchParams({
|
|
|
|
companyId,
|
|
|
|
startDate,
|
|
|
|
endDate,
|
|
|
|
who,
|
|
|
|
page: page.toString(),
|
|
|
|
page_size: pageSize.toString(),
|
|
|
|
})
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/usage/data/trascription?${params}`, {
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
const result: ApiResponse = await response.json()
|
|
|
|
if (result.success && result.data) {
|
|
|
|
setData(result.data.data || [])
|
|
|
|
setPagination(result.data.pagination)
|
2025-06-13 21:14:16 +00:00
|
|
|
setCostInfo(result.data.cost || null)
|
2025-06-09 11:13:05 +00:00
|
|
|
setCurrentPage(result.data.pagination.page)
|
|
|
|
} else {
|
|
|
|
setData([])
|
2025-06-13 21:14:16 +00:00
|
|
|
setCostInfo(null)
|
2025-06-09 11:13:05 +00:00
|
|
|
setPagination({ total: 0, page: 1, page_size: 20, total_pages: 0 })
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const errorData = await response.json()
|
|
|
|
setError(errorData.message || "Erro ao buscar dados")
|
|
|
|
}
|
2025-06-13 21:14:16 +00:00
|
|
|
|
|
|
|
// Aguardar a cotação do dólar
|
|
|
|
await ratePromise
|
2025-06-09 11:13:05 +00:00
|
|
|
} catch (err) {
|
2025-06-09 13:32:46 +00:00
|
|
|
console.log("====> Erro de conexão com o servidor: ", err)
|
2025-06-09 11:13:05 +00:00
|
|
|
setError("Erro de conexão com o servidor")
|
|
|
|
} finally {
|
|
|
|
setIsLoading(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const exportToExcel = async () => {
|
|
|
|
if (!companyId || !startDate || !endDate) {
|
|
|
|
setError("Preencha todos os campos obrigatórios")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsExporting(true)
|
|
|
|
setError("")
|
|
|
|
|
|
|
|
try {
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
companyId,
|
|
|
|
startDate,
|
|
|
|
endDate,
|
|
|
|
who,
|
|
|
|
})
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/usage/export/trascription?${params}`, {
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
const blob = await response.blob()
|
|
|
|
const url = window.URL.createObjectURL(blob)
|
|
|
|
const a = document.createElement("a")
|
|
|
|
a.href = url
|
|
|
|
a.download = `transcription-report-${who}-${startDate}-${endDate}.xlsx`
|
|
|
|
document.body.appendChild(a)
|
|
|
|
a.click()
|
|
|
|
window.URL.revokeObjectURL(url)
|
|
|
|
document.body.removeChild(a)
|
|
|
|
} else {
|
|
|
|
const errorData = await response.json()
|
|
|
|
setError(errorData.message || "Erro ao exportar dados")
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2025-06-09 13:32:46 +00:00
|
|
|
console.log("====> Erro de conexão com o servidor: ", err)
|
2025-06-09 11:13:05 +00:00
|
|
|
setError("Erro de conexão com o servidor")
|
|
|
|
} finally {
|
|
|
|
setIsExporting(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
// Funções de navegação de páginas
|
2025-06-09 11:13:05 +00:00
|
|
|
const goToPage = (page: number) => {
|
|
|
|
if (page >= 1 && page <= pagination.total_pages) {
|
|
|
|
setCurrentPage(page)
|
|
|
|
fetchData(page)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const goToFirstPage = () => goToPage(1)
|
|
|
|
const goToPreviousPage = () => goToPage(currentPage - 1)
|
|
|
|
const goToNextPage = () => goToPage(currentPage + 1)
|
|
|
|
const goToLastPage = () => goToPage(pagination.total_pages)
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
// Função para formatar valores monetários
|
|
|
|
const formatCurrency = (value: number, currency = "USD"): string => {
|
|
|
|
return new Intl.NumberFormat("pt-BR", {
|
|
|
|
style: "currency",
|
|
|
|
currency: currency,
|
|
|
|
minimumFractionDigits: 2,
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
}).format(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Função para converter USD para BRL
|
|
|
|
const convertUsdToBrl = (usdValue: number): number => {
|
|
|
|
if (!exchangeRate) return usdValue
|
|
|
|
return usdValue * exchangeRate
|
|
|
|
}
|
|
|
|
|
|
|
|
// Buscar cotação do dólar quando o componente for montado
|
|
|
|
useEffect(() => {
|
|
|
|
fetchExchangeRate()
|
|
|
|
}, [])
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
return (
|
|
|
|
<div className="space-y-6">
|
2025-06-13 21:14:16 +00:00
|
|
|
{/* Card de Informações de Custo */}
|
|
|
|
{costInfo && exchangeRate && (
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
<Card>
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
<CardTitle className="text-sm font-medium">Custo Total HIT</CardTitle>
|
|
|
|
<Calculator className="h-4 w-4 text-muted-foreground" />
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
<div className="text-2xl font-bold text-blue-600">
|
|
|
|
{formatCurrency(convertUsdToBrl(costInfo.total_cost_hit), "BRL")}
|
|
|
|
</div>
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
Empresa {costInfo.company_id} • {formatCurrency(costInfo.total_cost_hit, "USD")}
|
|
|
|
</p>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
<CardTitle className="text-sm font-medium">Custo Total Cliente</CardTitle>
|
|
|
|
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
<div className="text-2xl font-bold text-green-600">
|
|
|
|
{formatCurrency(costInfo.total_client_cost, "BRL")}
|
|
|
|
</div>
|
|
|
|
<p className="text-xs text-muted-foreground">
|
2025-06-16 19:26:37 +00:00
|
|
|
{formatDateBr(costInfo.start_date)} até {formatDateBr(costInfo.end_date)}
|
2025-06-13 21:14:16 +00:00
|
|
|
</p>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
<CardTitle className="text-sm font-medium">Margem</CardTitle>
|
|
|
|
<TrendingUp className="h-4 w-4 text-muted-foreground" />
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
<div className="text-2xl font-bold text-purple-600">
|
|
|
|
{formatCurrency(costInfo.total_client_cost - convertUsdToBrl(costInfo.total_cost_hit), "BRL")}
|
|
|
|
</div>
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
{(
|
|
|
|
((costInfo.total_client_cost - convertUsdToBrl(costInfo.total_cost_hit)) /
|
|
|
|
costInfo.total_client_cost) *
|
|
|
|
100
|
|
|
|
).toFixed(1)}
|
|
|
|
% de margem
|
|
|
|
</p>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
{/* Filtros */}
|
|
|
|
<Card>
|
|
|
|
<CardHeader>
|
|
|
|
<CardTitle>Filtros de Consulta</CardTitle>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-4">
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="companyId">ID da Empresa *</Label>
|
|
|
|
<Input
|
|
|
|
id="companyId"
|
|
|
|
value={companyId}
|
|
|
|
onChange={(e) => setCompanyId(e.target.value)}
|
|
|
|
placeholder="123"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="startDate">Data Inicial *</Label>
|
|
|
|
<Input id="startDate" type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="endDate">Data Final *</Label>
|
|
|
|
<Input id="endDate" type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="who">Tipo de Consulta *</Label>
|
|
|
|
<Select value={who} onValueChange={(value: "client" | "hit") => setWho(value)}>
|
|
|
|
<SelectTrigger>
|
|
|
|
<SelectValue />
|
|
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
|
|
<SelectItem value="client">Cliente</SelectItem>
|
|
|
|
<SelectItem value="hit">HIT</SelectItem>
|
|
|
|
</SelectContent>
|
|
|
|
</Select>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="pageSize">Registros por página</Label>
|
|
|
|
<Select
|
|
|
|
value={pageSize.toString()}
|
2025-06-13 21:14:16 +00:00
|
|
|
onValueChange={(value: string) => {
|
2025-06-09 11:13:05 +00:00
|
|
|
setPageSize(Number(value))
|
|
|
|
setCurrentPage(1)
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<SelectTrigger>
|
|
|
|
<SelectValue />
|
|
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
|
|
<SelectItem value="10">10</SelectItem>
|
|
|
|
<SelectItem value="20">20</SelectItem>
|
|
|
|
<SelectItem value="50">50</SelectItem>
|
|
|
|
<SelectItem value="100">100</SelectItem>
|
|
|
|
</SelectContent>
|
|
|
|
</Select>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label> </Label>
|
|
|
|
<div className="flex gap-2">
|
|
|
|
<Button onClick={() => fetchData(1)} disabled={isLoading} className="flex-1">
|
|
|
|
{isLoading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Search className="mr-2 h-4 w-4" />}
|
|
|
|
Buscar
|
|
|
|
</Button>
|
|
|
|
<Button onClick={exportToExcel} disabled={isExporting || data.length === 0} variant="outline">
|
|
|
|
{isExporting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Download className="h-4 w-4" />}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
<Alert variant="destructive">
|
|
|
|
<AlertDescription>{error}</AlertDescription>
|
|
|
|
</Alert>
|
|
|
|
)}
|
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
{/* Indicador de cotação do dólar */}
|
|
|
|
{exchangeRate && (
|
|
|
|
<div className="flex justify-end">
|
|
|
|
<div className="text-xs text-gray-500 bg-gray-50 px-2 py-1 rounded">
|
|
|
|
Cotação USD/BRL: R$ {exchangeRate.toFixed(4)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
{/* Tabela de Dados */}
|
|
|
|
<Card>
|
|
|
|
<CardHeader>
|
|
|
|
<CardTitle>
|
|
|
|
Resultados da Consulta
|
|
|
|
{pagination.total > 0 && (
|
|
|
|
<span className="text-sm font-normal text-gray-500 ml-2">
|
|
|
|
(Página {currentPage} de {pagination.total_pages} - {pagination.total} registros total)
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
</CardTitle>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
{data.length > 0 ? (
|
|
|
|
<div className="overflow-x-auto">
|
|
|
|
<Table>
|
|
|
|
<TableHeader>
|
|
|
|
<TableRow>
|
2025-06-12 20:58:22 +00:00
|
|
|
{who === "hit" ? (
|
2025-06-09 11:13:05 +00:00
|
|
|
<>
|
|
|
|
<TableHead>Empresa</TableHead>
|
2025-06-12 20:58:22 +00:00
|
|
|
<TableHead>Identificador da chamada</TableHead>
|
|
|
|
<TableHead>Origem</TableHead>
|
|
|
|
<TableHead>Destino</TableHead>
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableHead>Segundos</TableHead>
|
|
|
|
<TableHead>Minutos</TableHead>
|
|
|
|
<TableHead>Custo Cliente (R$)</TableHead>
|
|
|
|
<TableHead>Preço Cliente p/ Minuto (R$)</TableHead>
|
|
|
|
<TableHead>Custo HIT ($)</TableHead>
|
2025-06-12 20:58:22 +00:00
|
|
|
<TableHead>Início</TableHead>
|
|
|
|
<TableHead>Fim</TableHead>
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableHead>Tokens (Input)</TableHead>
|
|
|
|
<TableHead>Tokens (Output)</TableHead>
|
2025-06-12 20:58:22 +00:00
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<TableHead>ID Único</TableHead>
|
|
|
|
<TableHead>Origem</TableHead>
|
|
|
|
<TableHead>Destino</TableHead>
|
|
|
|
<TableHead>Início</TableHead>
|
|
|
|
<TableHead>Duração (s)</TableHead>
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableHead>Duração (m)</TableHead>
|
|
|
|
<TableHead>Custo (R$)</TableHead>
|
2025-06-09 11:13:05 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</TableRow>
|
|
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
{data.map((item, index) => (
|
2025-06-12 21:32:27 +00:00
|
|
|
<TableRow key={item.uniqueid || index}>
|
2025-06-12 20:58:22 +00:00
|
|
|
{who === "hit" ? (
|
2025-06-09 11:13:05 +00:00
|
|
|
<>
|
|
|
|
<TableCell>{(item as HitTranscriptionData).companyId || "-"}</TableCell>
|
2025-06-12 20:58:22 +00:00
|
|
|
<TableCell>{(item as HitTranscriptionData).uniqueid || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as HitTranscriptionData).src || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as HitTranscriptionData).dst || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as HitTranscriptionData).total_billsec || "-"}</TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
<TableCell>{(item as HitTranscriptionData).total_min || "-"}</TableCell>
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
{(item as HitTranscriptionData)?.client_total_cost
|
|
|
|
? `R$ ${Number((item as HitTranscriptionData).client_total_cost).toFixed(2)}`
|
2025-06-13 21:14:16 +00:00
|
|
|
: "-"}
|
|
|
|
</TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
<TableCell>
|
2025-06-13 21:14:16 +00:00
|
|
|
{(item as HitTranscriptionData).client_price
|
|
|
|
? `R$ ${(item as HitTranscriptionData).client_price}`
|
|
|
|
: "-"}
|
2025-06-09 11:13:05 +00:00
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
{(item as HitTranscriptionData)?.custo_hit
|
|
|
|
? `$ ${Number((item as HitTranscriptionData).custo_hit).toFixed(2)}`
|
2025-06-13 21:14:16 +00:00
|
|
|
: "-"}
|
2025-06-09 11:13:05 +00:00
|
|
|
</TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableCell>{formatDateTime((item as HitTranscriptionData).start_call || "-")}</TableCell>
|
|
|
|
<TableCell>{formatDateTime((item as HitTranscriptionData).end_call || "-")}</TableCell>
|
|
|
|
<TableCell>{(item as HitTranscriptionData).qtd_token_input || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as HitTranscriptionData).qtd_token_output || "-"}</TableCell>
|
2025-06-12 20:58:22 +00:00
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<TableCell>{(item as ClientTranscriptionData).uniqueid || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as ClientTranscriptionData).src || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as ClientTranscriptionData).dst || "-"}</TableCell>
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableCell>{formatDateTime((item as ClientTranscriptionData).start_call || "-")}</TableCell>
|
2025-06-12 20:58:22 +00:00
|
|
|
<TableCell>{(item as ClientTranscriptionData).total_billsec || "-"}</TableCell>
|
|
|
|
<TableCell>{(item as ClientTranscriptionData).total_min || "-"}</TableCell>
|
2025-06-13 21:14:16 +00:00
|
|
|
<TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
{(item as ClientTranscriptionData)?.client_total_cost
|
|
|
|
? `R$ ${Number((item as ClientTranscriptionData).client_total_cost).toFixed(2)}`
|
2025-06-13 21:14:16 +00:00
|
|
|
: "-"}
|
|
|
|
</TableCell>
|
2025-06-16 19:26:37 +00:00
|
|
|
|
|
|
|
|
2025-06-09 11:13:05 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</TableRow>
|
|
|
|
))}
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="text-center py-8 text-gray-500">
|
|
|
|
Nenhum dado encontrado. Use os filtros acima para buscar dados.
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Componente de Paginação */}
|
|
|
|
{pagination.total > 0 && (
|
|
|
|
<div className="flex items-center justify-between px-2 py-4">
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
<p className="text-sm text-gray-700">
|
|
|
|
Mostrando <span className="font-medium">{(currentPage - 1) * pagination.page_size + 1}</span> até{" "}
|
|
|
|
<span className="font-medium">{Math.min(currentPage * pagination.page_size, pagination.total)}</span>{" "}
|
|
|
|
de <span className="font-medium">{pagination.total}</span> resultados
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
<div className="flex items-center space-x-1">
|
|
|
|
<Button variant="outline" size="sm" onClick={goToFirstPage} disabled={currentPage === 1}>
|
|
|
|
Primeira
|
|
|
|
</Button>
|
|
|
|
<Button variant="outline" size="sm" onClick={goToPreviousPage} disabled={currentPage === 1}>
|
|
|
|
Anterior
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<div className="flex items-center space-x-1">
|
|
|
|
{/* Páginas numeradas */}
|
|
|
|
{Array.from({ length: Math.min(5, pagination.total_pages) }, (_, i) => {
|
|
|
|
let pageNum
|
|
|
|
if (pagination.total_pages <= 5) {
|
|
|
|
pageNum = i + 1
|
|
|
|
} else if (currentPage <= 3) {
|
|
|
|
pageNum = i + 1
|
|
|
|
} else if (currentPage >= pagination.total_pages - 2) {
|
|
|
|
pageNum = pagination.total_pages - 4 + i
|
|
|
|
} else {
|
|
|
|
pageNum = currentPage - 2 + i
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Button
|
|
|
|
key={pageNum}
|
|
|
|
variant={currentPage === pageNum ? "default" : "outline"}
|
|
|
|
size="sm"
|
|
|
|
onClick={() => goToPage(pageNum)}
|
|
|
|
className="w-10"
|
|
|
|
>
|
|
|
|
{pageNum}
|
|
|
|
</Button>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
variant="outline"
|
|
|
|
size="sm"
|
|
|
|
onClick={goToNextPage}
|
|
|
|
disabled={currentPage === pagination.total_pages}
|
|
|
|
>
|
|
|
|
Próxima
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
variant="outline"
|
|
|
|
size="sm"
|
|
|
|
onClick={goToLastPage}
|
|
|
|
disabled={currentPage === pagination.total_pages}
|
|
|
|
>
|
|
|
|
Última
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|