transcription-cost-usage-re.../frontend/components/transcription-table.tsx

450 lines
17 KiB
TypeScript
Raw Normal View History

2025-06-09 11:13:05 +00:00
"use client"
import { useState } from "react"
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"
import { Download, Search, Loader2 } from "lucide-react"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
// Primeiro, vamos atualizar as interfaces para os diferentes tipos de dados
// Substitua a interface TranscriptionData existente com estas interfaces:
interface ClientTranscriptionData {
uniqueid: string
src: string
dst: string
start_call: string
total_billsec: number
total_min: string
client_total_cost: string
2025-06-09 11:13:05 +00:00
}
interface HitTranscriptionData {
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)
custo_hit: string // Custo HIT
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
// Primeiro, vamos atualizar as interfaces para refletir a nova estrutura da API:
interface PaginationInfo {
total: number
page: number
page_size: number
total_pages: number
}
interface ApiResponse {
success: boolean
data: {
data: TranscriptionData[]
pagination: PaginationInfo
}
}
export default function TranscriptionTable() {
// Agora, atualize o estado para usar o tipo correto
// Substitua a linha do useState por:
const [data, setData] = useState<TranscriptionData[]>([])
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("")
// E atualize a definição do estado who:
const [who, setWho] = useState<"client" | "hit">("client")
// Adicionar estados para paginação após os estados existentes:
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",
}
}
// Atualizar a função fetchData para incluir parâmetros de paginação:
const fetchData = async (page: number = currentPage) => {
if (!companyId || !startDate || !endDate) {
setError("Preencha todos os campos obrigatórios")
return
}
setIsLoading(true)
setError("")
try {
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)
setCurrentPage(result.data.pagination.page)
} else {
setData([])
setPagination({ total: 0, page: 1, page_size: 20, total_pages: 0 })
}
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao buscar 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 {
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)
}
}
// Adicionar funções de navegação de páginas:
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)
return (
<div className="space-y-6">
{/* 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>
{/* Também vamos atualizar o Select para usar "hit" em vez de "company"
// Substitua o componente Select para o campo "who": */}
<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>
{/* Adicionar seletor de tamanho de página nos filtros: */}
<div className="space-y-2">
<Label htmlFor="pageSize">Registros por página</Label>
<Select
value={pageSize.toString()}
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>&nbsp;</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>
)}
{/* Tabela de Dados */}
<Card>
<CardHeader>
{/* Atualizar o título da tabela para mostrar informações de paginação: */}
<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>
{/* Agora, atualize a parte da tabela para exibir colunas diferentes com base no valor de who
// Substitua a seção da tabela com: */}
{data.length > 0 ? (
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
{who === "hit" ? (
2025-06-09 11:13:05 +00:00
<>
<TableHead>Empresa</TableHead>
<TableHead>Identificador da chamada</TableHead>
<TableHead>Origem</TableHead>
<TableHead>Destino</TableHead>
<TableHead>Quantidade de segundos</TableHead>
<TableHead>Quantidade de tokens (input)</TableHead>
<TableHead>Quantidade de tokens (output)</TableHead>
2025-06-09 11:13:05 +00:00
<TableHead>Custo HIT</TableHead>
<TableHead>Custo Cliente</TableHead>
<TableHead>Preço Cliente por Minuto</TableHead>
<TableHead>Início</TableHead>
<TableHead>Fim</TableHead>
</>
) : (
<>
<TableHead>ID Único</TableHead>
<TableHead>Origem</TableHead>
<TableHead>Destino</TableHead>
<TableHead>Início</TableHead>
<TableHead>Duração (s)</TableHead>
<TableHead>Total (min)</TableHead>
<TableHead>Custo Cliente</TableHead>
2025-06-09 11:13:05 +00:00
</>
)}
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow key={item.uniqueid || index}>
{who === "hit" ? (
2025-06-09 11:13:05 +00:00
<>
<TableCell>{(item as HitTranscriptionData).companyId || "-"}</TableCell>
<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>
<TableCell>{(item as HitTranscriptionData).qtd_token_input || "-"}</TableCell>
<TableCell>{(item as HitTranscriptionData).qtd_token_output || "-"}</TableCell>
2025-06-09 11:13:05 +00:00
<TableCell>
{(item as HitTranscriptionData).custo_hit ? `$${(item as HitTranscriptionData).custo_hit}` : "-"}
2025-06-09 11:13:05 +00:00
</TableCell>
<TableCell>{(item as HitTranscriptionData).client_total_cost || "-"}</TableCell>
2025-06-09 11:13:05 +00:00
<TableCell>
{(item as HitTranscriptionData).client_price ? `$${(item as HitTranscriptionData).client_price}` : "-"}
2025-06-09 11:13:05 +00:00
</TableCell>
<TableCell>{(item as HitTranscriptionData).start_call || "-"}</TableCell>
<TableCell>{(item as HitTranscriptionData).end_call || "-"}</TableCell>
</>
) : (
<>
<TableCell>{(item as ClientTranscriptionData).uniqueid || "-"}</TableCell>
<TableCell>{(item as ClientTranscriptionData).src || "-"}</TableCell>
<TableCell>{(item as ClientTranscriptionData).dst || "-"}</TableCell>
<TableCell>{(item as ClientTranscriptionData).start_call || "-"}</TableCell>
<TableCell>{(item as ClientTranscriptionData).total_billsec || "-"}</TableCell>
<TableCell>{(item as ClientTranscriptionData).total_min || "-"}</TableCell>
<TableCell>{(item as ClientTranscriptionData).client_total_cost || "-"}</TableCell>
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>
)
}