435 lines
16 KiB
TypeScript
435 lines
16 KiB
TypeScript
"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
|
|
}
|
|
|
|
interface HitTranscriptionData {
|
|
uniqueid: string
|
|
src: string
|
|
dst: string
|
|
start_call: string
|
|
end_call: string
|
|
total_billsec: number
|
|
companyId: string
|
|
custo_HIT: number
|
|
price: number
|
|
client_price: string
|
|
}
|
|
|
|
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) {
|
|
console.log("====> Erro de conexão com o servidor: ", err)
|
|
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) {
|
|
console.log("====> Erro de conexão com o servidor: ", err)
|
|
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) => {
|
|
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>
|
|
)}
|
|
|
|
{/* 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>
|
|
<TableHead>ID Único</TableHead>
|
|
<TableHead>Origem</TableHead>
|
|
<TableHead>Destino</TableHead>
|
|
<TableHead>Início</TableHead>
|
|
{who === "hit" && <TableHead>Fim</TableHead>}
|
|
<TableHead>Duração (s)</TableHead>
|
|
{who === "client" ? (
|
|
<TableHead>Total (min)</TableHead>
|
|
) : (
|
|
<>
|
|
<TableHead>Empresa</TableHead>
|
|
<TableHead>Custo HIT</TableHead>
|
|
<TableHead>Preço</TableHead>
|
|
<TableHead>Preço Cliente</TableHead>
|
|
</>
|
|
)}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data.map((item, index) => (
|
|
<TableRow key={item.uniqueid || index}>
|
|
<TableCell>{item.uniqueid || "-"}</TableCell>
|
|
<TableCell>{item.src || "-"}</TableCell>
|
|
<TableCell>{item.dst || "-"}</TableCell>
|
|
<TableCell>{item.start_call || "-"}</TableCell>
|
|
{who === "hit" && <TableCell>{(item as HitTranscriptionData).end_call || "-"}</TableCell>}
|
|
<TableCell>{item.total_billsec || "-"}</TableCell>
|
|
{who === "client" ? (
|
|
<TableCell>{(item as ClientTranscriptionData).total_min || "-"}</TableCell>
|
|
) : (
|
|
<>
|
|
<TableCell>{(item as HitTranscriptionData).companyId || "-"}</TableCell>
|
|
<TableCell>
|
|
{(item as HitTranscriptionData).custo_HIT
|
|
? `$${(item as HitTranscriptionData).custo_HIT.toFixed(6)}`
|
|
: "-"}
|
|
</TableCell>
|
|
<TableCell>
|
|
{(item as HitTranscriptionData).price
|
|
? `$${(item as HitTranscriptionData).price.toFixed(4)}`
|
|
: "-"}
|
|
</TableCell>
|
|
<TableCell>
|
|
{(item as HitTranscriptionData).client_price
|
|
? `$${(item as HitTranscriptionData).client_price}`
|
|
: "-"}
|
|
</TableCell>
|
|
</>
|
|
)}
|
|
</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>
|
|
)
|
|
}
|